Skip to content

Commit ff507ba

Browse files
committed
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.
1 parent 0679e67 commit ff507ba

File tree

3 files changed

+33
-8
lines changed

3 files changed

+33
-8
lines changed

src/transform/plugins/block-anchor/block-anchor.ts

+19-8
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@ import Token from 'markdown-it/lib/token';
44
const pattern = /^{%[^\S\r\n]*anchor[^\S\r\n]+([\w-]+)[^\S\r\n]*%}/;
55
export const TOKEN_NAME = 'anchor';
66

7-
function matchOpenToken(tokens: Token[], i: number) {
7+
function isParagraph(tokens: Token[], i: number) {
88
return (
99
tokens[i].type === 'paragraph_open' &&
1010
tokens[i + 1].type === 'inline' &&
11-
tokens[i + 2].type === 'paragraph_close' &&
12-
tokens[i + 1].children?.length === 1 &&
13-
tokens[i + 1].children?.[0].type === 'text' &&
11+
tokens[i + 2].type === 'paragraph_close'
12+
);
13+
}
14+
15+
function hasSingleChildWithText(tokens: Token[], i: number) {
16+
return tokens[i + 1].children?.length === 1 && tokens[i + 1].children?.[0].type === 'text';
17+
}
18+
19+
function matchOpenToken(tokens: Token[], i: number) {
20+
return (
21+
isParagraph(tokens, i) &&
22+
hasSingleChildWithText(tokens, i) &&
1423
pattern.exec(tokens[i + 1].children?.[0].content as string)
1524
);
1625
}
@@ -25,7 +34,7 @@ function createAnchorToken(state: StateCore, anchorId: string, position: number)
2534

2635
export function replaceTokens(state: StateCore) {
2736
const blockTokens = state.tokens;
28-
// i hate the idea of splicing the array while we're iterating over it
37+
// I hate the idea of splicing the array while we're iterating over it
2938
// so first lets find all the places where we will need to splice it and then actually do the splicing
3039
const splicePointsMap: Map<number, string> = new Map();
3140
for (let i = 0; i < blockTokens.length; i++) {
@@ -37,9 +46,11 @@ export function replaceTokens(state: StateCore) {
3746

3847
splicePointsMap.set(i, match[1]);
3948
}
40-
splicePointsMap.forEach((anchorId, position) => {
41-
blockTokens.splice(position, 3, createAnchorToken(state, anchorId, position));
42-
});
49+
Array.from(splicePointsMap)
50+
.sort(([keyA], [keyB]) => keyB - keyA)
51+
.forEach(([position, anchorId]) => {
52+
blockTokens.splice(position, 3, createAnchorToken(state, anchorId, position));
53+
});
4354
}
4455

4556
export function renderTokens(tokens: Token[], idx: number) {

test/__snapshots__/block-anchor.test.ts.snap

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ exports[`block-anchor does not parse produce an anchor if there is content befor
55
"
66
`;
77

8+
exports[`block-anchor handles multiple anchors in the input 1`] = `
9+
"<hr id=\\"first-anchor\\" class=\\"visually-hidden\\" /><p>Some content</p>
10+
<hr id=\\"second-anchor\\" class=\\"visually-hidden\\" /><p>Some more content</p>
11+
<hr id=\\"third-anchor\\" class=\\"visually-hidden\\" />"
12+
`;
13+
814
exports[`block-anchor parses anchors surrounded by other blocks 1`] = `
915
"<h1 id=\\"heading\\"><a href=\\"#heading\\" class=\\"yfm-anchor\\" aria-hidden=\\"true\\"><span class=\\"visually-hidden\\">Heading</span></a>Heading</h1>
1016
<hr id=\\"my-anchor\\" class=\\"visually-hidden\\" /><p>paragraph with content</p>

test/block-anchor.test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,12 @@ describe('block-anchor', function () {
3838

3939
expect(actual).toMatchSnapshot();
4040
});
41+
42+
it('handles multiple anchors in the input', () => {
43+
const input =
44+
'{%anchor first-anchor%}\n\nSome content\n\n{%anchor second-anchor%}\n\nSome more content\n\n{%anchor third-anchor%}';
45+
const actual = compile(parse(input));
46+
47+
expect(actual).toMatchSnapshot();
48+
});
4149
});

0 commit comments

Comments
 (0)