Skip to content

Commit cac2d00

Browse files
committed
allow linkification of different heading levels
1 parent 1a029fa commit cac2d00

File tree

12 files changed

+1888
-24
lines changed

12 files changed

+1888
-24
lines changed

src/format/fixtures/generateFixtures.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ async function run() {
1919
project: "svelte",
2020
docs_type: "docs",
2121
dir: "docs",
22+
level: 3,
2223
});
2324
await fs.writeFile(output_path, `export default ${JSON.stringify(contents)}`);
2425
}

src/format/format_api.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ console.log('boo')
4747
project: "svelte",
4848
docs_type: "docs",
4949
dir: "docs/boo",
50+
level: 3,
5051
});
5152

5253
// console.log(x);

src/format/format_api.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface Format {
3838
docs_type: docs_type;
3939
dir: string;
4040
seen_slugs?: Map<string, number>;
41+
level: number;
4142
}
4243

4344
// MDAST == Markdown AST
@@ -82,6 +83,7 @@ export async function format({
8283
docs_type,
8384
dir,
8485
seen_slugs = new Map(),
86+
level,
8587
}: Format): Promise<custom_vfile> {
8688
const sections: section[] = [];
8789
const section_title = file.toLowerCase().endsWith("readme.md")
@@ -103,7 +105,8 @@ export async function format({
103105
dir,
104106
file_type: file.toLowerCase().endsWith("readme.md") ? "readme" : "other",
105107
docs_type,
106-
prev_level: 3,
108+
base_level: level,
109+
prev_level: level,
107110
slugs: [],
108111
},
109112
});

src/format/headings.test.ts

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ _headings("transforms and formats headings", async () => {
3838
contents: `### hello`,
3939
data: {
4040
dir: "blog",
41+
docs_type: "docs",
4142
sections,
4243
section_stack: [sections],
4344
section_title: "section",
45+
base_level: 3,
4446
prev_level: 3,
4547
slugs: [],
4648
seen_slugs: new Map(),
@@ -64,9 +66,11 @@ _headings("transforms and formats multi-level headings", async () => {
6466
`,
6567
data: {
6668
dir: "blog",
69+
docs_type: "blog",
6770
sections,
6871
section_stack: [sections],
6972
section_title: "section",
73+
base_level: 3,
7074
prev_level: 3,
7175
slugs: [],
7276
seen_slugs: new Map(),
@@ -94,9 +98,11 @@ _headings("transforms and formats multi-level headings", async () => {
9498
`,
9599
data: {
96100
dir: "blog",
101+
docs_type: "blog",
97102
sections,
98103
section_stack: [sections],
99104
section_title: "section",
105+
base_level: 3,
100106
prev_level: 3,
101107
slugs: [],
102108
seen_slugs: new Map(),
@@ -107,13 +113,13 @@ _headings("transforms and formats multi-level headings", async () => {
107113

108114
assert.equal(
109115
output.contents,
110-
`<h3><span id="section-subsection-1" class="offset-anchor"></span><a href="blog#section-subsection-1" class="anchor" aria-hidden></a>subsection</h3>
111-
<h4><span id="section-subsection-1-subsubsection" class="offset-anchor"></span><a href="blog#section-subsection-1-subsubsection" class="anchor" aria-hidden></a>subsubsection</h4>
112-
<h5><span id="section-subsection-1-subsubsection-subsubsubsection" class="offset-anchor" data-scrollignore></span><a href="blog#section-subsection-1-subsubsection-subsubsubsection" class="anchor" aria-hidden></a>subsubsubsection</h5>`
116+
`<h3><span id="section-subsection" class="offset-anchor"></span><a href="blog#section-subsection" class="anchor" aria-hidden></a>subsection</h3>
117+
<h4><span id="section-subsection-subsubsection" class="offset-anchor"></span><a href="blog#section-subsection-subsubsection" class="anchor" aria-hidden></a>subsubsection</h4>
118+
<h5><span id="section-subsection-subsubsection-subsubsubsection" class="offset-anchor" data-scrollignore></span><a href="blog#section-subsection-subsubsection-subsubsubsection" class="anchor" aria-hidden></a>subsubsubsection</h5>`
113119
);
114120
});
115121

116-
_headings.only("transforms and formats multi-level headings", async () => {
122+
_headings("transforms and formats multi-level headings", async () => {
117123
const sections: unknown[] = [];
118124
const src = vFile({
119125
contents: `### subsection
@@ -133,9 +139,11 @@ _headings.only("transforms and formats multi-level headings", async () => {
133139
`,
134140
data: {
135141
dir: "blog",
142+
docs_type: "blog",
136143
sections,
137144
section_stack: [sections],
138145
section_title: "section",
146+
base_level: 3,
139147
prev_level: 3,
140148
slugs: [],
141149
seen_slugs: new Map(),
@@ -156,6 +164,54 @@ _headings.only("transforms and formats multi-level headings", async () => {
156164
);
157165
});
158166

167+
_headings(
168+
"transforms and formats multi-level headings: level 2 headings",
169+
async () => {
170+
const sections: unknown[] = [];
171+
const src = vFile({
172+
contents: `## subsection
173+
174+
### subsubsection
175+
176+
#### subsubsubsection
177+
178+
## one
179+
180+
### two
181+
182+
#### three
183+
184+
### four
185+
186+
`,
187+
data: {
188+
dir: "blog",
189+
docs_type: "blog",
190+
sections,
191+
section_stack: [sections],
192+
section_title: "section",
193+
base_level: 2,
194+
prev_level: 2,
195+
slugs: [],
196+
seen_slugs: new Map(),
197+
},
198+
});
199+
200+
const output = await linkify_only(src);
201+
202+
assert.equal(
203+
output.contents,
204+
`<h2><span id="section-subsection" class="offset-anchor"></span><a href="blog#section-subsection" class="anchor" aria-hidden></a>subsection</h2>
205+
<h3><span id="section-subsection-subsubsection" class="offset-anchor"></span><a href="blog#section-subsection-subsubsection" class="anchor" aria-hidden></a>subsubsection</h3>
206+
<h4><span id="section-subsection-subsubsection-subsubsubsection" class="offset-anchor" data-scrollignore></span><a href="blog#section-subsection-subsubsection-subsubsubsection" class="anchor" aria-hidden></a>subsubsubsection</h4>
207+
<h2><span id="section-one" class="offset-anchor"></span><a href="blog#section-one" class="anchor" aria-hidden></a>one</h2>
208+
<h3><span id="section-one-two" class="offset-anchor"></span><a href="blog#section-one-two" class="anchor" aria-hidden></a>two</h3>
209+
<h4><span id="section-one-two-three" class="offset-anchor" data-scrollignore></span><a href="blog#section-one-two-three" class="anchor" aria-hidden></a>three</h4>
210+
<h3><span id="section-one-four" class="offset-anchor"></span><a href="blog#section-one-four" class="anchor" aria-hidden></a>four</h3>`
211+
);
212+
}
213+
);
214+
159215
strip("strips leading level 1 headings", async () => {
160216
const src = `
161217

src/format/headings.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ const make_slug = make_session_slug_processor({
1717
separator: SLUG_SEPARATOR,
1818
});
1919

20+
// TODO: This should use the `base_level` setting
2021
/**
2122
* The documentation only allows heading level from 3 to 5 inclusive. This plugin
2223
* validates that rule is always followed.
2324
*/
2425
export function validate_headings(): Transformer {
25-
return function transformer(tree) {
26+
return function transformer(tree, { data }: custom_vfile) {
27+
if (data.docs_type !== "docs") return;
2628
visit(tree, "heading", (node: Heading) => {
2729
if (node.depth < 3 || node.depth > 5)
2830
throw new Error(
@@ -63,6 +65,8 @@ type Heading_with_hProps = Heading & {
6365

6466
export function linkify_headings(): Transformer {
6567
return function (tree, { data }: custom_vfile) {
68+
if (data.docs_type !== "docs" && data.docs_type !== "blog") return;
69+
6670
visit(tree, "heading", (node: Heading_with_hProps) => {
6771
const prev_section = data.section_stack[data.section_stack.length - 1];
6872

@@ -75,7 +79,7 @@ export function linkify_headings(): Transformer {
7579
const title_text = tree_to_string(node);
7680

7781
let slug = make_slug(
78-
node.depth === 3
82+
node.depth === data.base_level
7983
? [data.section_title, title_text].join(" ")
8084
: [data.slugs[data.slugs.length - 1], title_text].join(" "),
8185
data.seen_slugs
@@ -85,6 +89,9 @@ export function linkify_headings(): Transformer {
8589

8690
// We keep a 'section_stack' to keep track of the section structure
8791
if (node.depth > data.prev_level) {
92+
// TODO: check that prev_section[prev_section.length - 1] exists
93+
// skipping heading levels can cause problems here
94+
// maybe check current level against prev_level to validate?
8895
data.section_stack.push(
8996
prev_section[prev_section.length - 1].sections || []
9097
);
@@ -122,8 +129,9 @@ export function linkify_headings(): Transformer {
122129
},
123130
};
124131

125-
//@ts-ignore
126-
if (node.depth > 4) span_node.properties["data-scrollignore"] = true;
132+
if (node.depth > data.base_level + 1)
133+
//@ts-ignore
134+
span_node.properties["data-scrollignore"] = true;
127135

128136
node.data.hChildren = [span_node, a_node];
129137

src/format/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type custom_vfile = VFile & {
1414
data: {
1515
sections: section[];
1616
section_stack: section[][];
17+
base_level: number;
1718
prev_level: number;
1819
section_title: string;
1920
section_slug: string;

src/fs/get_content.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { promises as fs } from "fs";
22
import * as path from "path";
33

4-
type SimpleFile = {
4+
export type SimpleFile = {
55
name: string;
66
content: SimpleFile[] | string;
77
};

src/fs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { get_docs, DocFiles } from "./get_content";
1+
export { get_docs, DocFiles, SimpleFile } from "./get_content";

src/transform/docs.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@ import {
88
examples_out_list,
99
} from "./fixtures/examples";
1010

11-
import { transform_docs, transform_examples } from "./docs";
11+
import {
12+
tutorials_in,
13+
tutorials_out_list,
14+
tutorials_out_full,
15+
} from "./fixtures/tutorials";
16+
17+
import {
18+
transform_docs,
19+
transform_examples,
20+
transform_tutorials,
21+
} from "./docs";
1222

1323
const _docs = suite("transform_docs");
1424

@@ -22,4 +32,10 @@ _docs("transforms examples", async () => {
2232
assert.equal(output, { list: examples_out_list, full: examples_out_full });
2333
});
2434

35+
_docs("transforms examples", async () => {
36+
const output = await transform_tutorials(tutorials_in, "svelte");
37+
// console.log(JSON.stringify(output.full, null, 2));
38+
assert.equal(output, { list: tutorials_out_list, full: tutorials_out_full });
39+
});
40+
2541
_docs.run();

0 commit comments

Comments
 (0)