Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(html-comment): fix html comments from being visible in the rich editor #211

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/rich-text/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,30 @@ const nodes: {

pre: genHtmlBlockNodeSpec("pre"),

/**
* Defines an uneditable html_comment node; Only appears when a user has written an html comment block
* i.e. `<!-- comment -->` or `<!-- comment\n continued -->` but not `<!-- comment --> other text`
*/
html_comment: {
content: "text*",
attrs: { content: { default: "" } },
group: "block",
atom: true,
inline: false,
selectable: false,
parseDOM: [{ tag: "div.html_comment" }],
toDOM(node) {
return [
"div",
{
class: "html_comment",
hidden: true,
},
node.attrs.content,
];
},
},

/**
* Defines an uneditable html_block node; Only appears when a user has written a "complicated" html_block
* i.e. anything not resembling `<tag>content</tag>` or `<tag />`
Expand Down
64 changes: 64 additions & 0 deletions src/shared/markdown-it/html-comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import MarkdownIt from "markdown-it/lib";
import StateBlock from "markdown-it/lib/rules_block/state_block";

const HTML_COMMENT_OPEN_TAG = /<!--/;
const HTML_COMMENT_CLOSE_TAG = /-->/;

function getLineText(state: StateBlock, line: number): string {
const pos = state.bMarks[line] + state.tShift[line];
const max = state.eMarks[line];
return state.src.slice(pos, max).trim();
}

function html_comment(
state: StateBlock,
startLine: number,
endLine: number,
silent: boolean
) {
if (!state.md.options.html) {
return false;
}

let lineText = getLineText(state, startLine);

// check if the open tag "<!--" is the first element in the line
if (!HTML_COMMENT_OPEN_TAG.test(lineText.slice(0, 4))) {
return false;
}

let nextLine = startLine + 1;
while (nextLine < endLine) {
if (HTML_COMMENT_CLOSE_TAG.test(lineText)) {
break;
}
lineText = getLineText(state, nextLine);
nextLine++;
}

// check if the first close tag "-->" occurence is the last element in the line
if (HTML_COMMENT_CLOSE_TAG.exec(lineText).index + 3 !== lineText.length) {
return false;
}

if (silent) {
return true;
}

state.line = nextLine;

const token = state.push("html_comment", "", 0);
token.map = [startLine, nextLine];
token.content = state.getLines(startLine, nextLine, state.blkIndent, true);

return true;
}

/**
* Parses out HTML comments blocks
* (HTML comments inlined with other text/elements are not parsed by this plugin)
* @param md
*/
export function htmlComment(md: MarkdownIt): void {
md.block.ruler.before("html_block", "html_comment", html_comment);
}
11 changes: 10 additions & 1 deletion src/shared/markdown-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { spoiler } from "./markdown-it/spoiler";
import { stackLanguageComments } from "./markdown-it/stack-language-comments";
import { tagLinks } from "./markdown-it/tag-link";
import { tight_list } from "./markdown-it/tight-list";
import { htmlComment } from "./markdown-it/html-comment";
import type { CommonmarkParserFeatures } from "./view";

// extend the default markdown parser's tokens and add our own
Expand All @@ -27,7 +28,12 @@ const customMarkdownParserTokens: MarkdownParser["tokens"] = {
content: token.content,
}),
},

html_comment: {
node: "html_comment",
getAttrs: (token: Token) => ({
content: token.content,
}),
},
html_block: {
node: "html_block",
getAttrs: (token: Token) => ({
Expand Down Expand Up @@ -311,6 +317,9 @@ export function createDefaultMarkdownItInstance(
// ensure we can tell the difference between the different types of hardbreaks
defaultMarkdownItInstance.use(hardbreak_markup);

// parse html comments
defaultMarkdownItInstance.use(htmlComment);

// TODO should always exist, so remove the check once the param is made non-optional
externalPluginProvider?.alterMarkdownIt(defaultMarkdownItInstance);

Expand Down
5 changes: 5 additions & 0 deletions src/shared/markdown-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ const customMarkdownSerializerNodes: MarkdownSerializerNodes = {
state.write(node.attrs.content as string);
},

html_comment(state, node) {
state.write(node.attrs.content as string);
state.closeBlock(node);
},

html_block(state, node) {
state.write(node.attrs.content as string);
state.closeBlock(node);
Expand Down
65 changes: 65 additions & 0 deletions test/shared/markdown-it/html-comment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import MarkdownIt from "markdown-it/lib";
import { htmlComment } from "../../../src/shared/markdown-it/html-comment";

function createParser() {
const instance = new MarkdownIt("default", { html: true });
instance.use(htmlComment);
return instance;
}

describe("html-comment markdown-it plugin", () => {
it("should add the html comment block rule to the instance", () => {
const instance = createParser();
const blockRulesNames = instance.block.ruler
.getRules("")
.map((r) => r.name);
expect(blockRulesNames).toContain("html_comment");
});

it("should detect single line html comment blocks", () => {
const singleLineComment = "<!-- an html comment -->";
const instance = createParser();
const tokens = instance.parse(singleLineComment, {});

expect(tokens).toHaveLength(1);
expect(tokens[0].type).toBe("html_comment");
expect(tokens[0].content).toBe(singleLineComment);
expect(tokens[0].map).toEqual([0, 1]);
});

it("should detect multiline html comment blocks", () => {
const multilineComment = `<!-- an html comment\n over multiple lines -->`;
const instance = createParser();
const tokens = instance.parse(multilineComment, {});

expect(tokens).toHaveLength(1);
expect(tokens[0].type).toBe("html_comment");
expect(tokens[0].content).toBe(multilineComment);
expect(tokens[0].map).toEqual([0, 2]);
});

it("should detect indented html comment blocks", () => {
const indentedComment = ` <!--\n an html comment\n 2 space indented\n -->`;
const instance = createParser();
const tokens = instance.parse(indentedComment, {});

expect(tokens).toHaveLength(1);
expect(tokens[0].type).toBe("html_comment");
expect(tokens[0].content).toBe(indentedComment);
expect(tokens[0].map).toEqual([0, 4]);
});

it.each([
"other text <!-- an html comment -->",
"<!-- an html comment --> other text",
"<!-- an html comment --> <div>other element</div> <!-- an html comment -->",
])(
"should ignore html comments inlined with other element/text (test #%#)",
(inlinedHtmlComment) => {
const instance = createParser();
const tokens = instance.parse(inlinedHtmlComment, {});

expect(tokens.map((t) => t.type)).not.toContain("html_comment");
}
);
});
13 changes: 13 additions & 0 deletions test/shared/markdown-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ describe("SOMarkdownParser", () => {
});
});

it("should support html comments", () => {
const doc = markdownParser.parse(`<!-- an html comment -->`);
expect(doc).toMatchNodeTree({
childCount: 1,
content: [
{
"type.name": "html_comment",
"attrs.content": "<!-- an html comment -->",
},
],
});
});

it.skip("should support single block html without nesting", () => {
const doc = markdownParser.parse("<h1>test</h1>");

Expand Down
3 changes: 3 additions & 0 deletions test/shared/markdown-serializer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ describe("markdown-serializer", () => {
/* Tables */
`| foo | bar |\n| --- | --- |\n| baz | bim |`,
`| abc | def | ghi |\n|:---:|:--- | ---:|\n| foo | bar | baz |`,
/* Comments */
`<!-- an html comment -->`,
`<!-- an html comment\n over multiple lines -->`,
/* Marks */
`*test*`,
`_test_`,
Expand Down