Skip to content

Commit 5437321

Browse files
Merge pull request #679 from AdguardTeam/fix/672
Fix #672: Prevent duplicate heading IDs in Docusaurus
2 parents 042dce5 + 45e8ed3 commit 5437321

File tree

7 files changed

+712
-371
lines changed

7 files changed

+712
-371
lines changed

.markdownlint-cli2.jsonc

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"globs": ["**/*.md"],
3+
"config": {
4+
"ul-indent": { "indent": 4 },
5+
"ul-style": { "style": "dash" },
6+
"emphasis-style": { "style": "asterisk" },
7+
"no-duplicate-heading": { "siblings_only": true },
8+
"no-inline-html": {
9+
"allowed_elements": ["br", "details", "iframe", "summary"]
10+
},
11+
"no-trailing-spaces": { "br_spaces": 0 },
12+
"line-length": false,
13+
"no-bare-urls": false,
14+
"no-emphasis-as-heading": false,
15+
"link-fragments": false,
16+
"no-duplicate-docusaurus-ids": true
17+
},
18+
"customRules": ["./markdownlint-custom/rule-no-duplicate-docusaurus-ids.js"],
19+
"ignores": ["node_modules", "i18n"]
20+
}

.markdownlint.json

-18
This file was deleted.

.markdownlintignore

-2
This file was deleted.

docs/general/ad-filtering/create-own-filters.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ By default, such rules do not work for document requests. This means that the `|
6565

6666
- `https://example.org/banner/img`
6767

68-
### Basic rule modifiers {#basic-rule-modifiers}
68+
### Basic rule modifiers {#basic-rule-modifiers-examples}
6969

7070
Filtering rules support numerous modifiers that allow you to fine-tune the rule behavior. Here is an example of a rule with some simple modifiers.
7171

@@ -236,7 +236,7 @@ Safari Converter supports a substantial subset of [basic rules](#basic-rules) an
236236
- `$replace`
237237
- `$urltransform`
238238
239-
#### Cosmetic rules
239+
#### Cosmetic rules {#cosmetic-rules-safari-limitations}
240240
241241
Safari Converter supports most of the [cosmetic rules](#cosmetic-rules) although only element hiding rules with basic CSS selectors are supported natively via Safari Content Blocking, everything else needs to be interpreted by an additional extension.
242242
@@ -261,7 +261,7 @@ For scriptlet rules, it is **very important** that they are run as early as poss
261261
262262
:::
263263
264-
#### HTML filtering rules
264+
#### HTML filtering rules {#html-filtering-rules-safari-limitations}
265265
266266
[HTML filtering rules](#html-filtering-rules) are **not supported** and will not be supported in the future. Unfortunately, Safari does not provide necessary technical capabilities to implement them.
267267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const { createSlugger, parseMarkdownHeadingId } = require('@docusaurus/utils');
2+
3+
/**
4+
* Custom rule for markdownlint to prevent duplicate heading IDs as generated by Docusaurus.
5+
*
6+
* @type {import("markdownlint").Rule}
7+
*/
8+
module.exports = {
9+
names: ['no-duplicate-docusaurus-ids'],
10+
description: 'Prevent duplicate heading IDs as generated by Docusaurus',
11+
tags: ['headings', 'anchors', 'docusaurus'],
12+
parser: 'micromark',
13+
function(params, onError) {
14+
const { tokens } = params.parsers.micromark;
15+
const slugger = createSlugger();
16+
const slugMap = new Map();
17+
18+
for (const token of tokens) {
19+
if (token.type !== 'atxHeading' && token.type !== 'setextHeading') {
20+
continue;
21+
}
22+
23+
const textNode = token.children.find(
24+
(child) => child.type === 'atxHeadingText' || child.type === 'setextHeadingText',
25+
);
26+
27+
if (!textNode) {
28+
continue;
29+
}
30+
31+
const { text, id } = parseMarkdownHeadingId(textNode.text);
32+
// If ID provided (e.g. `# title {#id}`), use it, otherwise generate slug from text
33+
const slug = id || slugger.slug(text);
34+
35+
if (!slugMap.has(slug)) {
36+
slugMap.set(slug, []);
37+
}
38+
39+
slugMap.get(slug).push({ lineNumber: token.startLine, text: textNode.text });
40+
}
41+
42+
for (const [slug, entries] of slugMap.entries()) {
43+
if (entries.length > 1) {
44+
for (const { lineNumber, text } of entries) {
45+
onError({
46+
lineNumber,
47+
detail: `Duplicate heading ID: "${slug}", causing conflict in ToC`,
48+
context: text,
49+
});
50+
}
51+
}
52+
}
53+
},
54+
};

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"serve": "docusaurus serve",
1313
"write-translations": "docusaurus write-translations",
1414
"write-heading-ids": "docusaurus write-heading-ids",
15-
"lint:md": "markdownlint .",
15+
"lint:md": "markdownlint-cli2",
1616
"crowdin": "crowdin",
1717
"crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download --export-only-approved"
1818
},
@@ -45,7 +45,8 @@
4545
]
4646
},
4747
"devDependencies": {
48-
"markdownlint": "^0.29.0",
49-
"markdownlint-cli": "^0.35.0"
48+
"@docusaurus/utils": "^2.2.0",
49+
"markdownlint": "^0.37.4",
50+
"markdownlint-cli2": "^0.17.2"
5051
}
5152
}

0 commit comments

Comments
 (0)