Skip to content

Commit 08286db

Browse files
fix/site: Strip code fences line-by-line when building the TOC
The headings computed field removed fenced code blocks with the non-greedy regex /```[\s\S]*?```/g. Any inline triple-backtick run in prose (e.g. `"true``` on batch-spec-yaml-reference.mdx line 376) was taken as a fence opener, flipping every later fence pairing. YAML `# comment` lines inside fences then leaked into the TOC as headings with anchors that don't exist, while real headings were dropped. Walk the body line by line instead: a fence opens on a line starting with 3+ backticks or tildes and closes on a line of the same character at least as long, matching how the MDX renderer treats fences. On /batch-changes/batch-spec-yaml-reference this removes 12 bogus TOC entries and restores 13 real ones. No other page's headings change. Amp-Thread-ID: https://ampcode.com/threads/T-01a07599-bfc6-773f-abc1-7e0cd96db912 Co-authored-by: Amp <amp@ampcode.com>
1 parent c85fbb3 commit 08286db

1 file changed

Lines changed: 30 additions & 8 deletions

File tree

contentlayer.config.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,35 @@ import {searchMetadata} from './src/data/search';
99
import GithubSlugger from 'github-slugger';
1010
import {visit} from 'unist-util-visit';
1111

12+
// A fence opener/closer is a run of 3+ backticks or tildes at the start of a line.
13+
const regXFenceLine = /^\s*(`{3,}|~{3,})/;
14+
15+
// Remove fenced code blocks so `# comment` lines inside them are not mistaken for
16+
// headings. Walks line by line: a naive /```[\s\S]*?```/ regex also matches inline
17+
// backtick runs in prose (e.g. `"true```), which flips every later fence pairing.
18+
function stripFencedCodeBlocks(markdown: string): string {
19+
const keptLines: string[] = [];
20+
let openFence: string | undefined;
21+
for (const line of markdown.split('\n')) {
22+
const fence = line.match(regXFenceLine)?.[1];
23+
if (openFence) {
24+
const closesOpenFence =
25+
fence !== undefined &&
26+
fence[0] === openFence[0] &&
27+
fence.length >= openFence.length &&
28+
line.trim() === fence;
29+
if (closesOpenFence) {
30+
openFence = undefined;
31+
}
32+
} else if (fence) {
33+
openFence = fence;
34+
} else {
35+
keptLines.push(line);
36+
}
37+
}
38+
return keptLines.join('\n');
39+
}
40+
1241
export const Post = defineDocumentType(() => ({
1342
name: 'Post',
1443
filePathPattern: `**/*.mdx`,
@@ -28,17 +57,10 @@ export const Post = defineDocumentType(() => ({
2857
type: 'json',
2958
resolve: async doc => {
3059
const regXHeader = /^ *(?<flag>#{1,6})\s+(?<content>.+)/gm;
31-
const regXCodeBlock = /```[\s\S]*?```/g;
3260
const slugger = new GithubSlugger();
3361

34-
// Ignore content within code blocks – No headings there
35-
const bodyWithoutCodeBlocks = doc.body.raw.replace(
36-
regXCodeBlock,
37-
''
38-
);
39-
4062
const headings = Array.from(
41-
bodyWithoutCodeBlocks.matchAll(regXHeader)
63+
stripFencedCodeBlocks(doc.body.raw).matchAll(regXHeader)
4264
).map(({groups}) => {
4365
const flag = groups?.flag;
4466
// Handles headings with links eg:

0 commit comments

Comments
 (0)