discourse/plugins/spoiler-alert/assets/javascripts/lib/rich-editor-extension.js
Renato Atilio 968b851c1b
FEATURE: add spoiler (plugin) rich editor extension (#31717)
Continues the work done on
https://github.com/discourse/discourse/pull/30815.

Adds `spoiler` and `inline_spoiler` nodes, parsers, and serializers, and
a click handler to toggle its blurriness in the editor.
2025-03-11 20:13:52 -03:00

84 lines
2.4 KiB
JavaScript
Vendored

const SPOILER_NODES = ["inline_spoiler", "spoiler"];
/** @type {RichEditorExtension} */
const extension = {
nodeSpec: {
spoiler: {
attrs: { blurred: { default: true } },
group: "block",
content: "block+",
parseDOM: [{ tag: "div.spoiled" }],
toDOM: () => ["div", { class: "spoiled" }, 0],
},
inline_spoiler: {
attrs: { blurred: { default: true } },
group: "inline",
inline: true,
content: "inline*",
parseDOM: [{ tag: "span.spoiled" }],
toDOM: () => ["span", { class: "spoiled" }, 0],
},
},
parse: {
bbcode_spoiler: { block: "inline_spoiler" },
wrap_bbcode(state, token) {
if (token.nesting === 1 && token.attrGet("class") === "spoiler") {
state.openNode(state.schema.nodes.spoiler);
return true;
} else if (token.nesting === -1 && state.top().type.name === "spoiler") {
state.closeNode();
return true;
}
},
},
serializeNode: {
spoiler(state, node) {
state.write("[spoiler]\n");
state.renderContent(node);
state.write("[/spoiler]\n\n");
},
inline_spoiler(state, node) {
state.write("[spoiler]");
state.renderInline(node);
state.write("[/spoiler]");
},
},
plugins({ pmState: { Plugin }, pmView: { Decoration, DecorationSet } }) {
return new Plugin({
props: {
decorations(state) {
return this.getState(state);
},
handleClickOn(view, pos, node, nodePos) {
if (SPOILER_NODES.includes(node.type.name)) {
const decoSet = this.getState(view.state) || DecorationSet.empty;
const isBlurred =
decoSet.find(nodePos, nodePos + node.nodeSize).length > 0;
const newDeco = isBlurred
? decoSet.remove(decoSet.find(nodePos, nodePos + node.nodeSize))
: decoSet.add(view.state.doc, [
Decoration.node(nodePos, nodePos + node.nodeSize, {
class: "spoiler-blurred",
}),
]);
view.dispatch(view.state.tr.setMeta(this, newDeco));
return true;
}
},
},
state: {
init() {
return DecorationSet.empty;
},
apply(tr, set) {
return tr.getMeta(this) || set.map(tr.mapping, tr.doc);
},
},
});
},
};
export default extension;