From ff507ba3a6e642b1097c376a58414db44195acf6 Mon Sep 17 00:00:00 2001 From: Ivan Lediaev Date: Fri, 3 May 2024 15:43:32 +0200 Subject: [PATCH] fix: handling of multiple block anchors in transform This commit fixes handling of multiple block anchors in block anchor plugin and adds a new test case for handling multiple anchor tags in the input. Additionally, the block-anchor plugin code has been refactored for better readability and maintainability. --- .../plugins/block-anchor/block-anchor.ts | 27 +++++++++++++------ test/__snapshots__/block-anchor.test.ts.snap | 6 +++++ test/block-anchor.test.ts | 8 ++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/transform/plugins/block-anchor/block-anchor.ts b/src/transform/plugins/block-anchor/block-anchor.ts index 4b8fdab9..b57481ed 100644 --- a/src/transform/plugins/block-anchor/block-anchor.ts +++ b/src/transform/plugins/block-anchor/block-anchor.ts @@ -4,13 +4,22 @@ import Token from 'markdown-it/lib/token'; const pattern = /^{%[^\S\r\n]*anchor[^\S\r\n]+([\w-]+)[^\S\r\n]*%}/; export const TOKEN_NAME = 'anchor'; -function matchOpenToken(tokens: Token[], i: number) { +function isParagraph(tokens: Token[], i: number) { return ( tokens[i].type === 'paragraph_open' && tokens[i + 1].type === 'inline' && - tokens[i + 2].type === 'paragraph_close' && - tokens[i + 1].children?.length === 1 && - tokens[i + 1].children?.[0].type === 'text' && + tokens[i + 2].type === 'paragraph_close' + ); +} + +function hasSingleChildWithText(tokens: Token[], i: number) { + return tokens[i + 1].children?.length === 1 && tokens[i + 1].children?.[0].type === 'text'; +} + +function matchOpenToken(tokens: Token[], i: number) { + return ( + isParagraph(tokens, i) && + hasSingleChildWithText(tokens, i) && pattern.exec(tokens[i + 1].children?.[0].content as string) ); } @@ -25,7 +34,7 @@ function createAnchorToken(state: StateCore, anchorId: string, position: number) export function replaceTokens(state: StateCore) { const blockTokens = state.tokens; - // i hate the idea of splicing the array while we're iterating over it + // I hate the idea of splicing the array while we're iterating over it // so first lets find all the places where we will need to splice it and then actually do the splicing const splicePointsMap: Map = new Map(); for (let i = 0; i < blockTokens.length; i++) { @@ -37,9 +46,11 @@ export function replaceTokens(state: StateCore) { splicePointsMap.set(i, match[1]); } - splicePointsMap.forEach((anchorId, position) => { - blockTokens.splice(position, 3, createAnchorToken(state, anchorId, position)); - }); + Array.from(splicePointsMap) + .sort(([keyA], [keyB]) => keyB - keyA) + .forEach(([position, anchorId]) => { + blockTokens.splice(position, 3, createAnchorToken(state, anchorId, position)); + }); } export function renderTokens(tokens: Token[], idx: number) { diff --git a/test/__snapshots__/block-anchor.test.ts.snap b/test/__snapshots__/block-anchor.test.ts.snap index 10ef1bdd..b5152334 100644 --- a/test/__snapshots__/block-anchor.test.ts.snap +++ b/test/__snapshots__/block-anchor.test.ts.snap @@ -5,6 +5,12 @@ exports[`block-anchor does not parse produce an anchor if there is content befor " `; +exports[`block-anchor handles multiple anchors in the input 1`] = ` +"

Some content

+

Some more content

+
" +`; + exports[`block-anchor parses anchors surrounded by other blocks 1`] = ` "

HeadingHeading


paragraph with content

diff --git a/test/block-anchor.test.ts b/test/block-anchor.test.ts index dace532c..1a588338 100644 --- a/test/block-anchor.test.ts +++ b/test/block-anchor.test.ts @@ -38,4 +38,12 @@ describe('block-anchor', function () { expect(actual).toMatchSnapshot(); }); + + it('handles multiple anchors in the input', () => { + const input = + '{%anchor first-anchor%}\n\nSome content\n\n{%anchor second-anchor%}\n\nSome more content\n\n{%anchor third-anchor%}'; + const actual = compile(parse(input)); + + expect(actual).toMatchSnapshot(); + }); });