Skip to content

Commit eb6c1bb

Browse files
authored
🔀 Merge pull request #24 from heading-ids
✨ Add IDs to headings
2 parents 8b5c29f + ad12266 commit eb6c1bb

17 files changed

+1616
-2789
lines changed

‎README.md‎

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Use Markdoc defaults out of the box or configure Markdoc schema to your needs.
1616
- [Relative imports](#relative-imports)
1717
- [Preprocessor Options](#preprocessor-options)
1818
- [Functions](#functions)
19+
- [Heading IDs](#heading-ids)
1920
- [Nodes](#nodes)
2021
- [Partials](#partials)
2122
- [Tags](#tags)
@@ -135,15 +136,15 @@ Get it from the data you defined in `+page.ts`:
135136
## Customize Markdoc
136137

137138
To add additional features to the syntax of your files, customize your Markdoc schema.
138-
You can add the following extensions:
139+
Use any of the following extensions:
139140

140141
- [Nodes](#nodes)
141142
- [Tags](#tags)
142143
- [Variables](#variables)
143144
- [Functions](#functions)
144145
- [Partials](#partials)
145146

146-
You can customize schema in two ways:
147+
Customize schema in one of two ways:
147148

148149
- **For a single extension** or simple extensions, pass directly to the preprocessor options.
149150
- **For multiple extensions at once** or more complex configurations,
@@ -242,7 +243,7 @@ export default nodes;
242243

243244
### Relative imports
244245

245-
You can use relative imports to import definitions from either `.js` or `.ts` files.
246+
Use relative imports to import definitions from either `.js` or `.ts` files.
246247
Just remember to include the file extension.
247248

248249
For example, if you define custom functions in `src/lib/functions.js`,
@@ -264,21 +265,22 @@ const config = {
264265

265266
## Preprocessor Options
266267

267-
| Option | Type | Default | Description |
268-
| ----------------- | ------------------- | -------------------------------- | ------------------------------------------------------------------------- |
269-
| `comments` | boolean | `true` | Enable [Markdown comments](https://spec.commonmark.org/0.30/#example-624) |
270-
| `components` | string | `"$lib/components"` | Svelte components directory for custom nodes and tags |
271-
| `extensions` | string[] | `[".mdoc", ".md"]` | Files to process with Markdoc |
272-
| `functions` | Config['functions'] | - | [Functions config](#functions) |
273-
| `layout` | string | - | Default layout for all processed Markdown files |
274-
| `linkify` | boolean | `false` | Auto-convert bare URLs to links |
275-
| `nodes` | Config['nodes'] | - | [Nodes config](#nodes) |
276-
| `partials` | string | - | [Partials](#partials) directory path |
277-
| `schema` | string | `["./markdoc", "./src/markdoc"]` | Schema directory path |
278-
| `tags` | Config['tags'] | - | [Tags config](#tags) |
279-
| `typographer` | boolean | `false` | Enable [typography replacements](#typographer) |
280-
| `validationLevel` | ValidationLevel | `"error"` | [Validation strictness level](#validation-level) |
281-
| `variables` | Config['variables'] | - | [Variables config](#variables) |
268+
| Option | Type | Default | Description |
269+
| ----------------- | ---------------------------------------- | -------------------------------- | ------------------------------------------------------------------------- |
270+
| `comments` | boolean | `true` | Enable [Markdown comments](https://spec.commonmark.org/0.30/#example-624) |
271+
| `components` | string | `"$lib/components"` | Svelte components directory for custom nodes and tags |
272+
| `extensions` | string[] | `[".mdoc", ".md"]` | Files to process with Markdoc |
273+
| `functions` | Config['functions'] | - | [Functions config](#functions) |
274+
| `headingIds` | boolean \| `((value: string) => string)` | `false` | Add IDs to headings without them and include them in export |
275+
| `layout` | string | - | Default layout for all processed Markdown files |
276+
| `linkify` | boolean | `false` | Auto-convert bare URLs to links |
277+
| `nodes` | Config['nodes'] | - | [Nodes config](#nodes) |
278+
| `partials` | string | - | [Partials](#partials) directory path |
279+
| `schema` | string | `["./markdoc", "./src/markdoc"]` | Schema directory path |
280+
| `tags` | Config['tags'] | - | [Tags config](#tags) |
281+
| `typographer` | boolean | `false` | Enable [typography replacements](#typographer) |
282+
| `validationLevel` | ValidationLevel | `"error"` | [Validation strictness level](#validation-level) |
283+
| `variables` | Config['variables'] | - | [Variables config](#variables) |
282284

283285
### Functions
284286

@@ -303,7 +305,7 @@ const functions: Config["functions"] = {
303305
export default functions;
304306
```
305307

306-
Then you can use the custom function in a Markdown file:
308+
Then use the custom function in a Markdown file:
307309

308310
```markdown
309311
---
@@ -313,6 +315,38 @@ title: Hello World
313315
This is a {% uppercase(markdown) %} file that is processed by `markdoc-svelte`.
314316
```
315317

318+
### Heading IDs
319+
320+
If you want to build a table of contents for a page or just have links to specific headings, set `headingIds` to `true`.
321+
Add unique IDs in the original file with [annotations](https://markdoc.dev/docs/syntax#annotations)
322+
or have them generated automatically.
323+
Each heading element in the generated HTML has an `id` attribute to link to directly.
324+
325+
Each page then also exports a `headings` property: a list of all headings with their text, level, and ID.
326+
Use the list to generate a [table of contents](#page-table-of-contents).
327+
328+
#### Custom ID creator (slugifier)
329+
330+
By default, the preprocessor uses the [`slug` package](https://www.npmjs.com/package/slug).
331+
If you have requirements for ID creation, pass a function to the `headingIds` option.
332+
This function is used to generate the IDs.
333+
334+
```javascript
335+
import { markdocPreprocess } from "markdoc-svelte";
336+
337+
const customSlugger = (str: string): string => str.replaceAll(/[^a-z]/gi, "-");
338+
339+
/** @type {import('@sveltejs/kit').Config} */
340+
const config = {
341+
extensions: [".svelte", ".mdoc"],
342+
preprocess: [
343+
markdocPreprocess({
344+
headingIds: customSlugger,
345+
}),
346+
],
347+
};
348+
```
349+
316350
### Nodes
317351

318352
[Nodes](https://markdoc.dev/docs/nodes) are elements built into Markdown from the CommonMark specification.
@@ -373,6 +407,45 @@ Now your EnhancedImage component handles images added through standard Markdown
373407
![A cat sleeping on a balcony](awesome-cat.png)
374408
```
375409

410+
#### Relative paths to images
411+
412+
If you're using relative paths to your images in Markdoc,
413+
adjust the custom node to account for this.
414+
For example, you could have the custom node as follows:
415+
416+
```typescript
417+
import type { Config } from "markdoc-svelte";
418+
import { markdocPreprocess } from "markdoc-svelte";
419+
420+
const nodes: Config["nodes"] = {
421+
image: {
422+
render: "EnhancedImage",
423+
attributes: {
424+
// Include the default image attributes
425+
...Markdoc.nodes.image.attributes,
426+
},
427+
transform(node, config) {
428+
// Get the original src
429+
let src = node.attributes.src;
430+
431+
// Use base path if passed as variable
432+
const basePath = config.variables.basePath || "";
433+
434+
// Rewrite relative paths to absolute
435+
if (src.startsWith("./") || src.startsWith("../")) {
436+
src = path.posix.join(basePath, src);
437+
}
438+
439+
// Return a new node with modified src attribute
440+
return new Markdoc.Tag("EnhancedImage", {
441+
...node.attributes,
442+
src,
443+
});
444+
},
445+
},
446+
};
447+
```
448+
376449
### Partials
377450

378451
[Partials](https://markdoc.dev/docs/partials) are ways to reuse content across files (through [transclusion](https://en.wikipedia.org/wiki/Transclusion)).
@@ -406,7 +479,7 @@ This is a file that is processed by `markdoc-svelte`.
406479
### Tags
407480

408481
[Tags](https://markdoc.dev/docs/tags) are ways to extend Markdown syntax to do more.
409-
You can add functionality through Svelte components
482+
Use them to add functionality through Svelte components.
410483

411484
For example, you might want to create a custom Callout tag to highlight information on a page
412485
(these are also known as admonitions).
@@ -456,7 +529,7 @@ Then create a Callout component for tag in `src/lib/components/Callout.svelte`:
456529
</div>
457530
```
458531

459-
Then you can use the Callout tag in a Markdoc file:
532+
Then use the Callout tag in a Markdoc file:
460533

461534
```markdown
462535
---
@@ -479,7 +552,7 @@ Defaults to false.
479552

480553
The preprocessor validates whether the Markdoc is valid.
481554
By default, it throws an error on files for issues at the `error` or `critical` level.
482-
To debug, you can set the level to a lower level to stop the build for any errors at that level or above.
555+
To debug, set the level to a lower level to stop the build for any errors at that level or above.
483556
Possible values in ascending order: `debug`, `info`, `warning`, `error`, `critical`.
484557

485558
### Variables
@@ -502,7 +575,7 @@ const variables: Config["variables"] = {
502575
export default variables
503576
```
504577

505-
Then you can use the variable in a Markdoc file:
578+
Then use the variable in a Markdoc file:
506579

507580
```markdown
508581
---
@@ -520,21 +593,22 @@ Markdoc has a few Markdown syntax limitations, see [Markdoc FAQ](https://markdoc
520593

521594
### @sveltejs/enhanced-img
522595

523-
To use the [enhanced-img plugin](https://svelte.dev/docs/kit/images#sveltejs-enhanced-img) with Markdown images, you can customize the default images Node with a custom Svelte component.
524-
See the example [custom node](#nodes).
596+
To use the [enhanced-img plugin](https://svelte.dev/docs/kit/images#sveltejs-enhanced-img) with Markdown images,
597+
customize the default images Nnde with a custom Svelte component.
598+
See the example [custom node](#nodes) including the option for relative paths to images.
525599

526600
### Page table of contents
527601

528-
Each proccessed page automatically exports a `headings` property with all headings on the page and IDs for each.
529-
Add IDs with [annotations](https://markdoc.dev/docs/syntax#annotations) or they are generated automatically.
602+
When you have the [`headingIds` option](#heading-ids) enabled,
603+
each proccessed page automatically exports a `headings` property with all headings on the page and IDs for each.
530604
Use this list to generate a table of contents for the page, as in the following example:
531605

532606
```svelte
533607
<script lang="ts">
534608
let { data } = $props();
535609
const { frontmatter, headings } = data.page;
536610
537-
// Filter only h1 and h2 headings
611+
// Include only h1 and h2 headings
538612
const filteredHeadings = headings?.filter((heading) => heading.level <= 2) ?? [];
539613
</script>
540614
@@ -579,7 +653,7 @@ export const load = async () => {
579653
slug: module.slug,
580654
frontmatter: module.frontmatter,
581655
};
582-
})
656+
}),
583657
);
584658
return { content };
585659
};

0 commit comments

Comments
 (0)