summaryrefslogtreecommitdiff
path: root/packages/integrations/markdoc/src/runtime.ts
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2023-05-17 09:13:10 -0400
committerGravatar GitHub <noreply@github.com> 2023-05-17 09:13:10 -0400
commitfb84622af04f795de8d17f24192de105f70fe910 (patch)
tree11a99efdb90c17207d3adc1095e88fa8daddd7e4 /packages/integrations/markdoc/src/runtime.ts
parentc91e837e961043e92253148f0f4291856653b993 (diff)
downloadastro-fb84622af04f795de8d17f24192de105f70fe910.tar.gz
astro-fb84622af04f795de8d17f24192de105f70fe910.tar.zst
astro-fb84622af04f795de8d17f24192de105f70fe910.zip
[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
Diffstat (limited to 'packages/integrations/markdoc/src/runtime.ts')
-rw-r--r--packages/integrations/markdoc/src/runtime.ts78
1 files changed, 78 insertions, 0 deletions
diff --git a/packages/integrations/markdoc/src/runtime.ts b/packages/integrations/markdoc/src/runtime.ts
new file mode 100644
index 000000000..dadb73cd6
--- /dev/null
+++ b/packages/integrations/markdoc/src/runtime.ts
@@ -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;
+}