-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Markdoc]
headings
and heading IDs (#7095)
* deps: markdown-remark * wip: heading-ids function * chore: add `@astrojs/markdoc` to external * feat: `headings` support * fix: allow `render` config on headings * fix: nonexistent `userConfig` * test: headings, toc, astro component render * docs: README * chore: changeset * refactor: expose Markdoc helpers from runtime * fix: bad named exports (commonjsssss) * refactor: defaultNodes -> nodes * deps: github-slugger * fix: reset slugger cache on each render * fix: bad astroNodes import * docs: explain headingSlugger export * docs: add back double stringify comment * chore: bump to minor for internal exports change
- Loading branch information
1 parent
c91e837
commit fb84622
Showing
24 changed files
with
542 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@astrojs/markdoc': minor | ||
'astro': patch | ||
--- | ||
|
||
Generate heading `id`s and populate the `headings` property for all Markdoc files |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Markdoc, { type RenderableTreeNode, type Schema } from '@markdoc/markdoc'; | ||
import { getTextContent } from '../runtime.js'; | ||
import Slugger from 'github-slugger'; | ||
|
||
export const headingSlugger = new Slugger(); | ||
|
||
function getSlug(attributes: Record<string, any>, children: RenderableTreeNode[]): string { | ||
if (attributes.id && typeof attributes.id === 'string') { | ||
return attributes.id; | ||
} | ||
const textContent = attributes.content ?? getTextContent(children); | ||
let slug = headingSlugger.slug(textContent); | ||
|
||
if (slug.endsWith('-')) slug = slug.slice(0, -1); | ||
return slug; | ||
} | ||
|
||
export const heading: Schema = { | ||
children: ['inline'], | ||
attributes: { | ||
id: { type: String }, | ||
level: { type: Number, required: true, default: 1 }, | ||
}, | ||
transform(node, config) { | ||
const { level, ...attributes } = node.transformAttributes(config); | ||
const children = node.transformChildren(config); | ||
|
||
|
||
const slug = getSlug(attributes, children); | ||
|
||
const render = config.nodes?.heading?.render ?? `h${level}`; | ||
const tagProps = | ||
// For components, pass down `level` as a prop, | ||
// alongside `__collectHeading` for our `headings` collector. | ||
// Avoid accidentally rendering `level` as an HTML attribute otherwise! | ||
typeof render === 'function' | ||
? { ...attributes, id: slug, __collectHeading: true, level } | ||
: { ...attributes, id: slug }; | ||
|
||
return new Markdoc.Tag(render, tagProps, children); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { heading } from './heading.js'; | ||
export { headingSlugger } from './heading.js'; | ||
|
||
export const nodes = { heading }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import type { MarkdownHeading } from '@astrojs/markdown-remark'; | ||
import Markdoc, { | ||
type RenderableTreeNode, | ||
type ConfigType as MarkdocConfig, | ||
} from '@markdoc/markdoc'; | ||
import type { ContentEntryModule } from 'astro'; | ||
import { nodes as astroNodes } from './nodes/index.js'; | ||
|
||
/** Used to reset Slugger cache on each build at runtime */ | ||
export { headingSlugger } from './nodes/index.js'; | ||
export { default as Markdoc } from '@markdoc/markdoc'; | ||
|
||
export function applyDefaultConfig( | ||
config: MarkdocConfig, | ||
entry: ContentEntryModule | ||
): MarkdocConfig { | ||
return { | ||
...config, | ||
variables: { | ||
entry, | ||
...config.variables, | ||
}, | ||
nodes: { | ||
...astroNodes, | ||
...config.nodes, | ||
}, | ||
// TODO: Syntax highlighting | ||
}; | ||
} | ||
|
||
/** | ||
* Get text content as a string from a Markdoc transform AST | ||
*/ | ||
export function getTextContent(childNodes: RenderableTreeNode[]): string { | ||
let text = ''; | ||
for (const node of childNodes) { | ||
if (typeof node === 'string' || typeof node === 'number') { | ||
text += node; | ||
} else if (typeof node === 'object' && Markdoc.Tag.isTag(node)) { | ||
text += getTextContent(node.children); | ||
} | ||
} | ||
return text; | ||
} | ||
|
||
const headingLevels = [1, 2, 3, 4, 5, 6] as const; | ||
|
||
/** | ||
* Collect headings from Markdoc transform AST | ||
* for `headings` result on `render()` return value | ||
*/ | ||
export function collectHeadings(children: RenderableTreeNode[]): MarkdownHeading[] { | ||
let collectedHeadings: MarkdownHeading[] = []; | ||
for (const node of children) { | ||
if (typeof node !== 'object' || !Markdoc.Tag.isTag(node)) continue; | ||
|
||
if (node.attributes.__collectHeading === true && typeof node.attributes.level === 'number') { | ||
collectedHeadings.push({ | ||
slug: node.attributes.id, | ||
depth: node.attributes.level, | ||
text: getTextContent(node.children), | ||
}); | ||
continue; | ||
} | ||
|
||
for (const level of headingLevels) { | ||
if (node.name === 'h' + level) { | ||
collectedHeadings.push({ | ||
slug: node.attributes.id, | ||
depth: level, | ||
text: getTextContent(node.children), | ||
}); | ||
} | ||
} | ||
collectedHeadings.concat(collectHeadings(node.children)); | ||
} | ||
return collectedHeadings; | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/integrations/markdoc/test/fixtures/headings-custom/astro.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { defineConfig } from 'astro/config'; | ||
import markdoc from '@astrojs/markdoc'; | ||
|
||
// https://astro.build/config | ||
export default defineConfig({ | ||
integrations: [markdoc()], | ||
}); |
11 changes: 11 additions & 0 deletions
11
packages/integrations/markdoc/test/fixtures/headings-custom/markdoc.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { defineMarkdocConfig, nodes } from '@astrojs/markdoc/config'; | ||
import Heading from './src/components/Heading.astro'; | ||
|
||
export default defineMarkdocConfig({ | ||
nodes: { | ||
heading: { | ||
...nodes.heading, | ||
render: Heading, | ||
} | ||
} | ||
}); |
9 changes: 9 additions & 0 deletions
9
packages/integrations/markdoc/test/fixtures/headings-custom/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "@test/headings-custom", | ||
"version": "0.0.0", | ||
"private": true, | ||
"dependencies": { | ||
"@astrojs/markdoc": "workspace:*", | ||
"astro": "workspace:*" | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/integrations/markdoc/test/fixtures/headings-custom/src/components/Heading.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
type Props = { | ||
level: number; | ||
id: string; | ||
}; | ||
const { level, id }: Props = Astro.props; | ||
const Tag = `h${level}`; | ||
--- | ||
|
||
<Tag data-custom-heading {id}> | ||
<slot /> | ||
</Tag> |
11 changes: 11 additions & 0 deletions
11
packages/integrations/markdoc/test/fixtures/headings-custom/src/content/docs/headings.mdoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Level 1 heading | ||
|
||
## Level **2 heading** | ||
|
||
### Level _3 heading_ | ||
|
||
#### Level [4 heading](/with-a-link) | ||
|
||
##### Level 5 heading with override {% #id-override %} | ||
|
||
###### Level 6 heading |
Oops, something went wrong.