FEATURE: add html-inline rich editor extension

This commit is contained in:
Renato Atilio
2025-02-04 17:30:43 -03:00
parent 0e61565b2b
commit 8deec29539
2 changed files with 121 additions and 1 deletions

View File

@@ -0,0 +1,119 @@
const HTML_INLINE_MARKS = {
s: "strikethrough",
strike: "strikethrough",
strong: "strong",
b: "strong",
em: "em",
i: "em",
code: "code",
};
const ALLOWED_INLINE = [
"kbd",
"sup",
"sub",
"small",
"big",
"del",
"ins",
"mark",
];
const ALL_ALLOWED_TAGS = [...Object.keys(HTML_INLINE_MARKS), ...ALLOWED_INLINE];
/** @type {RichEditorExtension} */
const extension = {
nodeSpec: {
// TODO(renato): this node is hard to get past when at the end of a block
// and is added to a newline unintentionally, investigate
html_inline: {
group: "inline",
inline: true,
isolating: true,
content: "inline*",
attrs: { tag: {} },
parseDOM: ALLOWED_INLINE.map((tag) => ({ tag })),
toDOM: (node) => [node.attrs.tag, 0],
},
},
parse: {
// TODO(renato): it breaks if it's missing an end tag
html_inline: (state, token) => {
const openMatch = token.content.match(/^<([a-z]+)>$/);
const closeMatch = token.content.match(/^<\/([a-z]+)>$/);
if (openMatch) {
const tagName = openMatch[1];
const markName = HTML_INLINE_MARKS[tagName];
if (markName) {
state.openMark(state.schema.marks[markName].create());
return;
}
if (ALLOWED_INLINE.includes(tagName)) {
state.openNode(state.schema.nodes.html_inline, {
tag: tagName,
});
}
return;
}
if (closeMatch) {
const tagName = closeMatch[1];
const markName = HTML_INLINE_MARKS[tagName];
if (markName) {
state.closeMark(state.schema.marks[markName].create());
return;
}
if (ALLOWED_INLINE.includes(tagName)) {
state.closeNode();
}
}
},
},
serializeNode: {
html_inline(state, node) {
state.write(`<${node.attrs.tag}>`);
state.renderInline(node);
state.write(`</${node.attrs.tag}>`);
},
},
inputRules: {
match: new RegExp(`<(${ALL_ALLOWED_TAGS.join("|")})>$`, "i"),
handler: (state, match, start, end) => {
const tag = match[1];
const markName = HTML_INLINE_MARKS[tag];
const tr = state.tr;
if (markName) {
tr.delete(start, end);
tr.insertText(" ");
tr.addMark(start, start + 1, state.schema.marks[markName].create());
tr.removeStoredMark(state.schema.marks[markName]);
} else {
tr.replaceWith(
start,
end,
state.schema.nodes.html_inline.create({ tag }, [
state.schema.text(" "),
])
);
start += 1;
}
tr.insertText(" ");
tr.setSelection(
state.selection.constructor.create(tr.doc, start, start + 1)
);
return tr;
},
},
};
export default extension;

View File

@@ -1,4 +1,5 @@
import { registerRichEditorExtension } from "discourse/lib/composer/rich-editor-extensions";
import htmlInline from "./html-inline";
/**
* List of default extensions
@@ -6,6 +7,6 @@ import { registerRichEditorExtension } from "discourse/lib/composer/rich-editor-
*
* @type {RichEditorExtension[]}
*/
const defaultExtensions = [];
const defaultExtensions = [htmlInline];
defaultExtensions.forEach(registerRichEditorExtension);