From 5bef5ef9796de70062f7fb307ae93dc6c0f5257f Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 02:03:48 -0400 Subject: [PATCH 1/8] only collect meta lines for the current snippet --- plugins/official/stack-snippets/src/schema.ts | 125 +++++++++++------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index 1cae7ad7..e511f5b3 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -8,6 +8,7 @@ import { RawContext, validateMetaLines, validSnippetRegex, + MetaLine, } from "./common"; import { Node as ProseMirrorNode, NodeSpec } from "prosemirror-model"; @@ -86,6 +87,9 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( } let rawMetaLines: RawContext[] = []; + let inSnippet = false; + let snippetBegin: MetaLine | null = null; + let currentLangLines: RawContext[] = []; //Next up, we want to find and test all the blocks we find. for (let i = startLine; i < endLine; i++) { @@ -97,58 +101,85 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( if (!validSnippetRegex.test(line)) { continue; } - rawMetaLines = [...rawMetaLines, { line, index: i }]; - } - const metaLines = rawMetaLines.map(mapMetaLine).filter((m) => m != null); - const validationResult = validateMetaLines(metaLines); + const metaLine = mapMetaLine({ line, index: i }); + if (!metaLine) { + continue; + } + + if (metaLine.type === "begin") { + if (inSnippet) { + // Found a new begin while still in a snippet - invalid state + return false; + } + inSnippet = true; + snippetBegin = metaLine; + rawMetaLines = [{ line, index: i }]; + currentLangLines = []; + } else if (metaLine.type === "lang" && inSnippet) { + currentLangLines.push({ line, index: i }); + rawMetaLines.push({ line, index: i }); + } else if (metaLine.type === "end" && inSnippet) { + rawMetaLines.push({ line, index: i }); + + const metaLines = rawMetaLines.map(mapMetaLine).filter((m) => m != null); + const validationResult = validateMetaLines(metaLines); + + //We now know this is a valid snippet. Last call before we start processing + if (silent || !validationResult.valid) { + return validationResult.valid; + } + + // Create the snippet tokens + const openToken = state.push("stack_snippet_open", "code", 1); + // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) + openToken.attrSet("id", Utils.generateRandomId()); + if (!snippetBegin || snippetBegin.type !== "begin") { + return false; + } + openToken.attrSet("hide", snippetBegin.hide); + openToken.attrSet("console", snippetBegin.console); + openToken.attrSet("babel", snippetBegin.babel); + openToken.attrSet("babelPresetReact", snippetBegin.babelPresetReact); + openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); + + // Sort and process language blocks + const langSort = currentLangLines.sort((a, b) => a.index - b.index); + + for (let j = 0; j < langSort.length; j++) { + const langMeta = mapMetaLine(langSort[j]); + if (!langMeta || langMeta.type !== "lang") continue; + + //Use the beginning of the next block to establish the end of this one, or the end of the snippet + const langEnd = + j + 1 == langSort.length ? i : langSort[j + 1].index; + //Start after the header of the lang block (+1) and the following empty line (+1) + //End on the beginning of the next metaLine, less the preceding empty line (-1) + //All lang blocks are forcefully indented 4 spaces, so cleave those away. + const langBlock = state.getLines( + langSort[j].index + 2, + langEnd - 1, + 4, + false + ); + const langToken = state.push("stack_snippet_lang", "code", 1); + langToken.content = langBlock; + langToken.map = [langSort[j].index, langEnd]; + langToken.attrSet("language", langMeta.language); + } + + state.push("stack_snippet_close", "code", -1); + state.line = i + 1; - //We now know this is a valid snippet. Last call before we start processing - if (silent || !validationResult.valid) { - return validationResult.valid; + return true; + } } - //A valid block must start with a begin and end, so cleave the opening and closing from the lines - const begin = metaLines.shift(); - if (begin.type !== "begin") return false; - const end = metaLines.pop(); - if (end.type !== "end") return false; - - //The rest must be langs, sort them by index - const langSort = metaLines - .filter((m) => m.type == "lang") //Not strictly necessary, but useful for typing - .sort((a, b) => a.index - b.index); - if (!langSort.every((l) => l.type === "lang")) return false; - - const openToken = state.push("stack_snippet_open", "code", 1); - // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) - openToken.attrSet("id", Utils.generateRandomId()); - openToken.attrSet("hide", begin.hide); - openToken.attrSet("console", begin.console); - openToken.attrSet("babel", begin.babel); - openToken.attrSet("babelPresetReact", begin.babelPresetReact); - openToken.attrSet("babelPresetTS", begin.babelPresetTS); - - for (let i = 0; i < langSort.length; i++) { - //Use the beginning of the next block to establish the end of this one, or the end of the snippet - const langEnd = - i + 1 == langSort.length ? end.index : langSort[i + 1].index; - //Start after the header of the lang block (+1) and the following empty line (+1) - //End on the beginning of the next metaLine, less the preceding empty line (-1) - //All lang blocks are forcefully indented 4 spaces, so cleave those away. - const langBlock = state.getLines( - langSort[i].index + 2, - langEnd - 1, - 4, - false - ); - const langToken = state.push("stack_snippet_lang", "code", 1); - langToken.content = langBlock; - langToken.map = [langSort[i].index, langEnd]; - langToken.attrSet("language", langSort[i].language); + // If we're still in a snippet at the end, it means we never found an end marker + if (inSnippet) { + return false; } - state.push("stack_snippet_close", "code", -1); - state.line = end.index + 1; + return true; }; From f3226bfecc51478105662cebc7b5e7baed92a4e4 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 02:05:56 -0400 Subject: [PATCH 2/8] format --- plugins/official/stack-snippets/src/schema.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index e511f5b3..cd1db3b1 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -121,14 +121,16 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( rawMetaLines.push({ line, index: i }); } else if (metaLine.type === "end" && inSnippet) { rawMetaLines.push({ line, index: i }); - - const metaLines = rawMetaLines.map(mapMetaLine).filter((m) => m != null); + + const metaLines = rawMetaLines + .map(mapMetaLine) + .filter((m) => m != null); const validationResult = validateMetaLines(metaLines); - //We now know this is a valid snippet. Last call before we start processing - if (silent || !validationResult.valid) { - return validationResult.valid; - } + //We now know this is a valid snippet. Last call before we start processing + if (silent || !validationResult.valid) { + return validationResult.valid; + } // Create the snippet tokens const openToken = state.push("stack_snippet_open", "code", 1); @@ -140,12 +142,15 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( openToken.attrSet("hide", snippetBegin.hide); openToken.attrSet("console", snippetBegin.console); openToken.attrSet("babel", snippetBegin.babel); - openToken.attrSet("babelPresetReact", snippetBegin.babelPresetReact); + openToken.attrSet( + "babelPresetReact", + snippetBegin.babelPresetReact + ); openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); // Sort and process language blocks const langSort = currentLangLines.sort((a, b) => a.index - b.index); - + for (let j = 0; j < langSort.length; j++) { const langMeta = mapMetaLine(langSort[j]); if (!langMeta || langMeta.type !== "lang") continue; From 4b1995a170c29817c0cd66ca314d7dedc49325c5 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 10:46:08 -0400 Subject: [PATCH 3/8] revert indentation --- plugins/official/stack-snippets/src/schema.ts | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index cd1db3b1..7b81b6e6 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -102,83 +102,83 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( continue; } - const metaLine = mapMetaLine({ line, index: i }); - if (!metaLine) { - continue; - } + const metaLine = mapMetaLine({ line, index: i }); + if (!metaLine) { + continue; + } - if (metaLine.type === "begin") { - if (inSnippet) { - // Found a new begin while still in a snippet - invalid state - return false; - } - inSnippet = true; - snippetBegin = metaLine; - rawMetaLines = [{ line, index: i }]; - currentLangLines = []; - } else if (metaLine.type === "lang" && inSnippet) { - currentLangLines.push({ line, index: i }); - rawMetaLines.push({ line, index: i }); - } else if (metaLine.type === "end" && inSnippet) { - rawMetaLines.push({ line, index: i }); + if (metaLine.type === "begin") { + if (inSnippet) { + // Found a new begin while still in a snippet - invalid state + return false; + } + inSnippet = true; + snippetBegin = metaLine; + rawMetaLines = [{ line, index: i }]; + currentLangLines = []; + } else if (metaLine.type === "lang" && inSnippet) { + currentLangLines.push({ line, index: i }); + rawMetaLines.push({ line, index: i }); + } else if (metaLine.type === "end" && inSnippet) { + rawMetaLines.push({ line, index: i }); - const metaLines = rawMetaLines - .map(mapMetaLine) - .filter((m) => m != null); - const validationResult = validateMetaLines(metaLines); + const metaLines = rawMetaLines + .map(mapMetaLine) + .filter((m) => m != null); + const validationResult = validateMetaLines(metaLines); - //We now know this is a valid snippet. Last call before we start processing - if (silent || !validationResult.valid) { - return validationResult.valid; - } + //We now know this is a valid snippet. Last call before we start processing + if (silent || !validationResult.valid) { + return validationResult.valid; + } - // Create the snippet tokens - const openToken = state.push("stack_snippet_open", "code", 1); - // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) - openToken.attrSet("id", Utils.generateRandomId()); - if (!snippetBegin || snippetBegin.type !== "begin") { - return false; - } - openToken.attrSet("hide", snippetBegin.hide); - openToken.attrSet("console", snippetBegin.console); - openToken.attrSet("babel", snippetBegin.babel); - openToken.attrSet( - "babelPresetReact", - snippetBegin.babelPresetReact - ); - openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); + // Create the snippet tokens + const openToken = state.push("stack_snippet_open", "code", 1); + // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) + openToken.attrSet("id", Utils.generateRandomId()); + if (!snippetBegin || snippetBegin.type !== "begin") { + return false; + } + openToken.attrSet("hide", snippetBegin.hide); + openToken.attrSet("console", snippetBegin.console); + openToken.attrSet("babel", snippetBegin.babel); + openToken.attrSet( + "babelPresetReact", + snippetBegin.babelPresetReact + ); + openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); - // Sort and process language blocks - const langSort = currentLangLines.sort((a, b) => a.index - b.index); + // Sort and process language blocks + const langSort = currentLangLines.sort((a, b) => a.index - b.index); - for (let j = 0; j < langSort.length; j++) { - const langMeta = mapMetaLine(langSort[j]); - if (!langMeta || langMeta.type !== "lang") continue; + for (let j = 0; j < langSort.length; j++) { + const langMeta = mapMetaLine(langSort[j]); + if (!langMeta || langMeta.type !== "lang") continue; - //Use the beginning of the next block to establish the end of this one, or the end of the snippet - const langEnd = - j + 1 == langSort.length ? i : langSort[j + 1].index; - //Start after the header of the lang block (+1) and the following empty line (+1) - //End on the beginning of the next metaLine, less the preceding empty line (-1) - //All lang blocks are forcefully indented 4 spaces, so cleave those away. - const langBlock = state.getLines( - langSort[j].index + 2, - langEnd - 1, - 4, - false - ); - const langToken = state.push("stack_snippet_lang", "code", 1); - langToken.content = langBlock; - langToken.map = [langSort[j].index, langEnd]; - langToken.attrSet("language", langMeta.language); - } + //Use the beginning of the next block to establish the end of this one, or the end of the snippet + const langEnd = + j + 1 == langSort.length ? i : langSort[j + 1].index; + //Start after the header of the lang block (+1) and the following empty line (+1) + //End on the beginning of the next metaLine, less the preceding empty line (-1) + //All lang blocks are forcefully indented 4 spaces, so cleave those away. + const langBlock = state.getLines( + langSort[j].index + 2, + langEnd - 1, + 4, + false + ); + const langToken = state.push("stack_snippet_lang", "code", 1); + langToken.content = langBlock; + langToken.map = [langSort[j].index, langEnd]; + langToken.attrSet("language", langMeta.language); + } - state.push("stack_snippet_close", "code", -1); - state.line = i + 1; + state.push("stack_snippet_close", "code", -1); + state.line = i + 1; - return true; - } + return true; } +} // If we're still in a snippet at the end, it means we never found an end marker if (inSnippet) { From bfcdb6deedf4d069e8b0d93b532d58ea5d33fbee Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 11:06:08 -0400 Subject: [PATCH 4/8] trying to fix the git compare --- plugins/official/stack-snippets/src/schema.ts | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index 7b81b6e6..cd1db3b1 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -102,83 +102,83 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( continue; } - const metaLine = mapMetaLine({ line, index: i }); - if (!metaLine) { - continue; - } - - if (metaLine.type === "begin") { - if (inSnippet) { - // Found a new begin while still in a snippet - invalid state - return false; + const metaLine = mapMetaLine({ line, index: i }); + if (!metaLine) { + continue; } - inSnippet = true; - snippetBegin = metaLine; - rawMetaLines = [{ line, index: i }]; - currentLangLines = []; - } else if (metaLine.type === "lang" && inSnippet) { - currentLangLines.push({ line, index: i }); - rawMetaLines.push({ line, index: i }); - } else if (metaLine.type === "end" && inSnippet) { - rawMetaLines.push({ line, index: i }); - const metaLines = rawMetaLines - .map(mapMetaLine) - .filter((m) => m != null); - const validationResult = validateMetaLines(metaLines); + if (metaLine.type === "begin") { + if (inSnippet) { + // Found a new begin while still in a snippet - invalid state + return false; + } + inSnippet = true; + snippetBegin = metaLine; + rawMetaLines = [{ line, index: i }]; + currentLangLines = []; + } else if (metaLine.type === "lang" && inSnippet) { + currentLangLines.push({ line, index: i }); + rawMetaLines.push({ line, index: i }); + } else if (metaLine.type === "end" && inSnippet) { + rawMetaLines.push({ line, index: i }); - //We now know this is a valid snippet. Last call before we start processing - if (silent || !validationResult.valid) { - return validationResult.valid; - } + const metaLines = rawMetaLines + .map(mapMetaLine) + .filter((m) => m != null); + const validationResult = validateMetaLines(metaLines); - // Create the snippet tokens - const openToken = state.push("stack_snippet_open", "code", 1); - // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) - openToken.attrSet("id", Utils.generateRandomId()); - if (!snippetBegin || snippetBegin.type !== "begin") { - return false; - } - openToken.attrSet("hide", snippetBegin.hide); - openToken.attrSet("console", snippetBegin.console); - openToken.attrSet("babel", snippetBegin.babel); - openToken.attrSet( - "babelPresetReact", - snippetBegin.babelPresetReact - ); - openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); + //We now know this is a valid snippet. Last call before we start processing + if (silent || !validationResult.valid) { + return validationResult.valid; + } - // Sort and process language blocks - const langSort = currentLangLines.sort((a, b) => a.index - b.index); + // Create the snippet tokens + const openToken = state.push("stack_snippet_open", "code", 1); + // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) + openToken.attrSet("id", Utils.generateRandomId()); + if (!snippetBegin || snippetBegin.type !== "begin") { + return false; + } + openToken.attrSet("hide", snippetBegin.hide); + openToken.attrSet("console", snippetBegin.console); + openToken.attrSet("babel", snippetBegin.babel); + openToken.attrSet( + "babelPresetReact", + snippetBegin.babelPresetReact + ); + openToken.attrSet("babelPresetTS", snippetBegin.babelPresetTS); - for (let j = 0; j < langSort.length; j++) { - const langMeta = mapMetaLine(langSort[j]); - if (!langMeta || langMeta.type !== "lang") continue; + // Sort and process language blocks + const langSort = currentLangLines.sort((a, b) => a.index - b.index); - //Use the beginning of the next block to establish the end of this one, or the end of the snippet - const langEnd = - j + 1 == langSort.length ? i : langSort[j + 1].index; - //Start after the header of the lang block (+1) and the following empty line (+1) - //End on the beginning of the next metaLine, less the preceding empty line (-1) - //All lang blocks are forcefully indented 4 spaces, so cleave those away. - const langBlock = state.getLines( - langSort[j].index + 2, - langEnd - 1, - 4, - false - ); - const langToken = state.push("stack_snippet_lang", "code", 1); - langToken.content = langBlock; - langToken.map = [langSort[j].index, langEnd]; - langToken.attrSet("language", langMeta.language); - } + for (let j = 0; j < langSort.length; j++) { + const langMeta = mapMetaLine(langSort[j]); + if (!langMeta || langMeta.type !== "lang") continue; - state.push("stack_snippet_close", "code", -1); - state.line = i + 1; + //Use the beginning of the next block to establish the end of this one, or the end of the snippet + const langEnd = + j + 1 == langSort.length ? i : langSort[j + 1].index; + //Start after the header of the lang block (+1) and the following empty line (+1) + //End on the beginning of the next metaLine, less the preceding empty line (-1) + //All lang blocks are forcefully indented 4 spaces, so cleave those away. + const langBlock = state.getLines( + langSort[j].index + 2, + langEnd - 1, + 4, + false + ); + const langToken = state.push("stack_snippet_lang", "code", 1); + langToken.content = langBlock; + langToken.map = [langSort[j].index, langEnd]; + langToken.attrSet("language", langMeta.language); + } + + state.push("stack_snippet_close", "code", -1); + state.line = i + 1; - return true; + return true; + } } -} // If we're still in a snippet at the end, it means we never found an end marker if (inSnippet) { From b3477cffc23f3687a5e28bab9bc653e4e263da83 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 12:41:05 -0400 Subject: [PATCH 5/8] increment state.line for invalid cases --- plugins/official/stack-snippets/src/schema.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/official/stack-snippets/src/schema.ts b/plugins/official/stack-snippets/src/schema.ts index cd1db3b1..eca7ec22 100644 --- a/plugins/official/stack-snippets/src/schema.ts +++ b/plugins/official/stack-snippets/src/schema.ts @@ -110,13 +110,18 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( if (metaLine.type === "begin") { if (inSnippet) { // Found a new begin while still in a snippet - invalid state + state.line = i + 1; return false; } inSnippet = true; snippetBegin = metaLine; rawMetaLines = [{ line, index: i }]; currentLangLines = []; - } else if (metaLine.type === "lang" && inSnippet) { + } else if (metaLine.type === "lang") { + if (!inSnippet) { + state.line = i + 1; + return false; + } currentLangLines.push({ line, index: i }); rawMetaLines.push({ line, index: i }); } else if (metaLine.type === "end" && inSnippet) { @@ -129,6 +134,7 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( //We now know this is a valid snippet. Last call before we start processing if (silent || !validationResult.valid) { + state.line = i + 1; return validationResult.valid; } @@ -137,6 +143,7 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch) openToken.attrSet("id", Utils.generateRandomId()); if (!snippetBegin || snippetBegin.type !== "begin") { + state.line = i + 1; return false; } openToken.attrSet("hide", snippetBegin.hide); @@ -182,10 +189,11 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = ( // If we're still in a snippet at the end, it means we never found an end marker if (inSnippet) { + state.line = endLine; return false; } - return true; + return false; }; export const stackSnippetRichTextNodeSpec: { [name: string]: NodeSpec } = { From c33ec4734931559ee33c64e54b42fd9a572f7053 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 12:58:26 -0400 Subject: [PATCH 6/8] add test --- .../test/markdownit-plugin.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/official/stack-snippets/test/markdownit-plugin.test.ts b/plugins/official/stack-snippets/test/markdownit-plugin.test.ts index 3ef541a1..31a12125 100644 --- a/plugins/official/stack-snippets/test/markdownit-plugin.test.ts +++ b/plugins/official/stack-snippets/test/markdownit-plugin.test.ts @@ -3,6 +3,9 @@ import { stackSnippetPlugin } from "../src/schema"; import { invalidSnippetRenderCases, validSnippetRenderCases, + validBegin, + validJs, + validEnd, } from "./stack-snippet-helpers"; describe("stackSnippetPlugin (Markdown-it)", () => { @@ -46,4 +49,35 @@ describe("stackSnippetPlugin (Markdown-it)", () => { } } ); + + it("should correctly parse multiple consecutive snippets", () => { + const multipleSnippets = `${validBegin}${validJs}${validEnd} + +Some text in between snippets. + +${validBegin}${validJs}${validEnd}`; + + const tokens = mdit.parse(multipleSnippets, {}); + + // We expect: + // - First snippet: open + lang + close (3 tokens) + // - Paragraph with text (3 tokens: paragraph_open, inline, paragraph_close) + // - Second snippet: open + lang + close (3 tokens) + expect(tokens).toHaveLength(9); + + // First snippet + expect(tokens[0].type).toBe("stack_snippet_open"); + expect(tokens[1].type).toBe("stack_snippet_lang"); + expect(tokens[2].type).toBe("stack_snippet_close"); + + // Text in between + expect(tokens[3].type).toBe("paragraph_open"); + expect(tokens[4].type).toBe("inline"); + expect(tokens[5].type).toBe("paragraph_close"); + + // Second snippet + expect(tokens[6].type).toBe("stack_snippet_open"); + expect(tokens[7].type).toBe("stack_snippet_lang"); + expect(tokens[8].type).toBe("stack_snippet_close"); + }); }); From 711fa1f01001cd81e4c9f6e8fd4fbb6bd8771eea Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 12:59:55 -0400 Subject: [PATCH 7/8] add changeset --- .changeset/rich-aliens-fold.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rich-aliens-fold.md diff --git a/.changeset/rich-aliens-fold.md b/.changeset/rich-aliens-fold.md new file mode 100644 index 00000000..62c710a2 --- /dev/null +++ b/.changeset/rich-aliens-fold.md @@ -0,0 +1,5 @@ +--- +"@stackoverflow/stacks-editor": patch +--- + +fix parsing of multiple snippets From 4cf31d125955ec03c5136044579c570fc8b95f00 Mon Sep 17 00:00:00 2001 From: Aliza Berger Date: Wed, 28 May 2025 13:06:16 -0400 Subject: [PATCH 8/8] format --- .../stack-snippets/test/markdownit-plugin.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/official/stack-snippets/test/markdownit-plugin.test.ts b/plugins/official/stack-snippets/test/markdownit-plugin.test.ts index 31a12125..13266764 100644 --- a/plugins/official/stack-snippets/test/markdownit-plugin.test.ts +++ b/plugins/official/stack-snippets/test/markdownit-plugin.test.ts @@ -56,25 +56,25 @@ describe("stackSnippetPlugin (Markdown-it)", () => { Some text in between snippets. ${validBegin}${validJs}${validEnd}`; - + const tokens = mdit.parse(multipleSnippets, {}); - + // We expect: // - First snippet: open + lang + close (3 tokens) // - Paragraph with text (3 tokens: paragraph_open, inline, paragraph_close) // - Second snippet: open + lang + close (3 tokens) expect(tokens).toHaveLength(9); - + // First snippet expect(tokens[0].type).toBe("stack_snippet_open"); expect(tokens[1].type).toBe("stack_snippet_lang"); expect(tokens[2].type).toBe("stack_snippet_close"); - + // Text in between expect(tokens[3].type).toBe("paragraph_open"); expect(tokens[4].type).toBe("inline"); expect(tokens[5].type).toBe("paragraph_close"); - + // Second snippet expect(tokens[6].type).toBe("stack_snippet_open"); expect(tokens[7].type).toBe("stack_snippet_lang");