FEATURE: add html-block rich editor extension (#31181)

Continues the work done on
https://github.com/discourse/discourse/pull/30815.

Adds an `html_block` node and its parsing/serialization logic.

It's rendered as a code block with HTML syntax highlighting, and
serialized as-is to the Markdown output.
This commit is contained in:
Renato Atilio 2025-03-06 20:52:18 -03:00 committed by GitHub
parent 03429b0f8f
commit 71303a509f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 0 deletions

View File

@ -0,0 +1,35 @@
/** @type {RichEditorExtension} */
const extension = {
nodeSpec: {
html_block: {
attrs: { params: { default: "html" } },
group: "block",
content: "text*",
code: true,
defining: true,
marks: "",
isolating: true,
selectable: true,
draggable: true,
parseDOM: [{ tag: "pre.html-block", preserveWhitespace: "full" }],
toDOM() {
return ["pre", { class: "html-block" }, ["code", 0]];
},
},
},
parse: {
html_block: (state, token) => {
state.openNode(state.schema.nodes.html_block);
state.addText(token.content.trim());
state.closeNode();
},
},
serializeNode: {
html_block: (state, node) => {
state.renderContent(node);
state.write("\n\n");
},
},
};
export default extension;

View File

@ -3,6 +3,7 @@ import codeBlock from "./code-block";
import emoji from "./emoji";
import hashtag from "./hashtag";
import heading from "./heading";
import htmlBlock from "./html-block";
import htmlInline from "./html-inline";
import image from "./image";
import link from "./link";
@ -31,6 +32,7 @@ const defaultExtensions = [
strikethrough,
underline,
htmlInline,
htmlBlock,
table,
markdownPaste,
];

View File

@ -0,0 +1,38 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { testMarkdown } from "discourse/tests/helpers/rich-editor-helper";
module(
"Integration | Component | prosemirror-editor - html block extension",
function (hooks) {
setupRenderingTest(hooks);
Object.entries({
"unclosed html block": [
"<div>Hello World\n\n",
'<pre class="html-block"><code>&lt;div&gt;Hello World</code></pre>',
"<div>Hello World\n\n",
],
"html block with attributes": [
'Hey\n\n<div class="test">Hello World</div>\nYou',
'<p>Hey</p><pre class="html-block"><code>&lt;div class="test"&gt;Hello World&lt;/div&gt;\nYou</code></pre>',
'Hey\n\n<div class="test">Hello World</div>\nYou\n\n',
],
"html block with multiple lines": [
"<div>\n <p>Hello</p>\n <p>World</p>\n</div>",
'<pre class="html-block"><code>&lt;div&gt;\n &lt;p&gt;Hello&lt;/p&gt;\n &lt;p&gt;World&lt;/p&gt;\n&lt;/div&gt;</code></pre>',
"<div>\n <p>Hello</p>\n <p>World</p>\n</div>\n\n",
],
"html block multiple times": [
"<div>1</div>\n\nA\n\n<div>2</div>",
'<pre class="html-block"><code>&lt;div&gt;1&lt;/div&gt;</code></pre><p>A</p><pre class="html-block"><code>&lt;div&gt;2&lt;/div&gt;</code></pre>',
"<div>1</div>\n\nA\n\n<div>2</div>\n\n",
],
}).forEach(([name, [markdown, html, expectedMarkdown]]) => {
test(name, async function (assert) {
this.siteSettings.rich_editor = true;
await testMarkdown(assert, markdown, html, expectedMarkdown);
});
});
}
);