@terrafirmagreg-team/kit 0.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/package.json +15 -27
  2. package/src/components/blocks/nav-title.tsx +22 -25
  3. package/src/components/outline/dropdown.tsx +3 -2
  4. package/src/components/outline/index.ts +2 -1
  5. package/src/components/outline/list.tsx +3 -2
  6. package/src/components/outline/outline.tsx +4 -3
  7. package/src/components/outline/popover.tsx +3 -2
  8. package/src/components/page-actions.tsx +10 -9
  9. package/src/components/relative-link.tsx +1 -0
  10. package/src/components/toc/base.tsx +2 -1
  11. package/src/components/toc/block.tsx +1 -0
  12. package/src/components/toc/clerk.tsx +2 -1
  13. package/src/components/toc/default.tsx +3 -2
  14. package/src/components/version-menu.tsx +19 -17
  15. package/src/components/views/dataset.tsx +11 -2
  16. package/src/components/views/section.tsx +88 -44
  17. package/src/components/workspace/content.tsx +1 -0
  18. package/src/components/workspace/index.ts +1 -0
  19. package/src/components/workspace/pagination.tsx +2 -1
  20. package/src/components/workspace/workspace.tsx +1 -0
  21. package/src/hooks/use-catalog.ts +1 -0
  22. package/src/hooks/use-current-dataset.ts +15 -5
  23. package/src/hooks/use-dataset-accent.ts +1 -0
  24. package/src/hooks/use-nav-options.ts +20 -17
  25. package/src/hooks/use-section-names.ts +40 -0
  26. package/src/hooks/{toc.ts → use-toc.ts} +1 -0
  27. package/src/hooks/use-version-pref.ts +1 -0
  28. package/src/hooks/use-versioned-dataset.ts +21 -0
  29. package/src/lib/breadcrumb.ts +3 -16
  30. package/src/lib/config.ts +30 -28
  31. package/src/lib/dataset-links.ts +1 -0
  32. package/src/lib/datasets.ts +98 -68
  33. package/src/lib/landing.ts +4 -2
  34. package/src/lib/layout.ts +1 -0
  35. package/src/lib/locale/renderer.ts +49 -0
  36. package/src/lib/order.ts +1 -0
  37. package/src/lib/page.ts +3 -2
  38. package/src/lib/pin-tree.ts +11 -8
  39. package/src/lib/preview/lookup.ts +8 -5
  40. package/src/lib/section/schema.ts +2 -6
  41. package/src/lib/section/source.ts +59 -22
  42. package/src/lib/sources.ts +1 -0
  43. package/src/lib/tree.ts +1 -0
  44. package/src/lib/urls.ts +1 -0
  45. package/src/locales/en_us.json +7 -10
  46. package/src/locales/ru_ru.json +7 -10
  47. package/src/locales.ts +1 -0
  48. package/src/types/embeds.ts +54 -0
  49. package/src/types/index.ts +13 -9
  50. package/src/{components/outline/types.ts → types/outline.ts} +1 -0
  51. package/src/components/docs-image.tsx +0 -49
  52. package/src/components/search/dialog.tsx +0 -145
  53. package/src/lib/legacy.ts +0 -18
  54. package/src/lib/location.ts +0 -63
  55. package/src/lib/markdown/dom.ts +0 -90
  56. package/src/lib/markdown/index.ts +0 -13
  57. package/src/lib/markdown/providers.ts +0 -25
  58. package/src/lib/mdx/accordions.ts +0 -93
  59. package/src/lib/mdx/banner.ts +0 -52
  60. package/src/lib/mdx/directive-utils.ts +0 -125
  61. package/src/lib/mdx/directives.ts +0 -141
  62. package/src/lib/mdx/game-export.ts +0 -44
  63. package/src/lib/mdx/html-style.ts +0 -135
  64. package/src/lib/mdx/preview.ts +0 -252
  65. package/src/lib/mdx/references.ts +0 -56
  66. package/src/lib/mdx/tabs.ts +0 -80
  67. package/src/lib/mdx-components.tsx +0 -139
  68. package/src/lib/pack-assets.ts +0 -45
  69. package/src/lib/preview/index.ts +0 -8
  70. package/src/lib/preview/og.tsx +0 -115
  71. package/src/lib/preview/resolvers.ts +0 -98
  72. package/src/lib/search/config.ts +0 -13
  73. package/src/lib/search/filters.ts +0 -22
  74. package/src/lib/search/index.ts +0 -19
  75. package/src/lib/search/indexes.ts +0 -43
  76. package/src/lib/search/merge.ts +0 -22
  77. package/src/lib/search/server.ts +0 -18
  78. package/src/lib/search/sources.ts +0 -19
  79. package/src/lib/search/structured.ts +0 -23
  80. package/src/types/mdx.ts +0 -55
  81. package/src/types/turndown-plugin-gfm.d.ts +0 -9
@@ -1,125 +0,0 @@
1
-
2
- import type {
3
- DirectiveNode,
4
- JsxAttribute,
5
- JsxAttributeValue,
6
- MdastNode,
7
- } from '@terrafirmagreg-team/kit/types/mdx';
8
-
9
- export type {
10
- DirectiveNode,
11
- JsxAttribute,
12
- JsxAttributeValue,
13
- JsxElement,
14
- MdastNode,
15
- } from '@terrafirmagreg-team/kit/types/mdx';
16
-
17
- export function walkContainers(
18
- tree: MdastNode,
19
- fn: (node: DirectiveNode) => boolean | void,
20
- ): void {
21
- for (const child of tree.children ?? []) {
22
- if (child.type !== 'containerDirective') {
23
- walkContainers(child, fn);
24
- continue;
25
- }
26
- const directive = child as DirectiveNode;
27
- if (fn(directive) !== false) walkContainers(directive, fn);
28
- }
29
- }
30
-
31
- const DIRECTIVE_TYPES = new Set([
32
- 'containerDirective',
33
- 'leafDirective',
34
- 'textDirective',
35
- ]);
36
-
37
- export function walkDirectives(
38
- tree: MdastNode,
39
- fn: (node: DirectiveNode) => boolean | void,
40
- ): void {
41
- for (const child of tree.children ?? []) {
42
- if (DIRECTIVE_TYPES.has(child.type)) {
43
- if (fn(child as DirectiveNode) !== false) walkDirectives(child, fn);
44
- } else {
45
- walkDirectives(child, fn);
46
- }
47
- }
48
- }
49
-
50
- export function toJsxAttributes(node: DirectiveNode): JsxAttribute[] {
51
- return Object.entries(node.attributes ?? {}).map(([name, value]) => ({
52
- type: 'mdxJsxAttribute',
53
- name,
54
- value: value ?? true,
55
- }));
56
- }
57
-
58
- function labelText(node: MdastNode): string {
59
- const t = node.type;
60
- const v = (node as { value?: unknown }).value;
61
- if (typeof v === 'string') return v;
62
- if (t === 'textDirective' || t === 'leafDirective') {
63
- const d = node as DirectiveNode;
64
- const inner = (d.children ?? []).map(labelText).join('');
65
- const attrs = Object.entries(d.attributes ?? {})
66
- .map(([k, v]) => (v == null ? k : v === true ? k : `${k}="${v}"`))
67
- .join(' ');
68
- const prefix = t === 'leafDirective' ? '::' : ':';
69
- return `${prefix}${d.name ?? ''}${inner ? `[${inner}]` : ''}${attrs ? `{${attrs}}` : ''}`;
70
- }
71
- return (node.children ?? []).map(labelText).join('');
72
- }
73
-
74
- export function extractDirectiveLabel(node: DirectiveNode): string {
75
- for (const child of node.children ?? []) {
76
- if (child.type === 'paragraph' && child.data?.directiveLabel) {
77
- return (child.children ?? []).map(labelText).join('');
78
- }
79
- }
80
- return '';
81
- }
82
-
83
- export function extractLeafLabel(node: MdastNode): string {
84
- return (node.children ?? []).map(labelText).join('');
85
- }
86
-
87
- export function contentChildren(node: DirectiveNode): MdastNode[] {
88
- return (node.children ?? []).filter(
89
- (child) =>
90
- !(child.type === 'paragraph' && child.data?.directiveLabel),
91
- );
92
- }
93
-
94
- export function jsxAttr(name: string, value: string | boolean): JsxAttribute {
95
- return { type: 'mdxJsxAttribute', name, value };
96
- }
97
-
98
- export function jsxExpression(expression: unknown): JsxAttributeValue {
99
- return {
100
- type: 'mdxJsxAttributeValueExpression',
101
- value: '',
102
- data: {
103
- estree: {
104
- type: 'Program',
105
- body: [{ type: 'ExpressionStatement', expression }],
106
- sourceType: 'script',
107
- },
108
- },
109
- };
110
- }
111
-
112
- export function estreeString(value: string): unknown {
113
- return { type: 'Literal', value, raw: JSON.stringify(value) };
114
- }
115
-
116
- export function estreeStringArray(labels: string[]): unknown {
117
- return {
118
- type: 'ArrayExpression',
119
- elements: labels.map(estreeString),
120
- };
121
- }
122
-
123
- export function estreeBoolean(value: boolean): unknown {
124
- return { type: 'Literal', value, raw: String(value) };
125
- }
@@ -1,141 +0,0 @@
1
- import {
2
- extractLeafLabel,
3
- jsxExpression,
4
- toJsxAttributes,
5
- walkDirectives,
6
- type DirectiveNode,
7
- type MdastNode,
8
- } from './directive-utils.ts';
9
- import { gradientImage } from '@terrafirmagreg-team/ui/lib/mdx/gradient';
10
-
11
- export function remarkDirectives() {
12
- return (tree: MdastNode) => {
13
- walkDirectives(tree, (node) => {
14
- const { type, name } = node;
15
- if (type === 'textDirective' && name === 'gradient') {
16
- Object.assign(node, {
17
- type: 'mdxJsxTextElement',
18
- name: 'span',
19
- attributes: [
20
- styleAttr(gradientStyle(node)),
21
- { type: 'mdxJsxAttribute', name: 'data-md', value: 'gradient' },
22
- ],
23
- });
24
- return;
25
- }
26
- if (
27
- name === 'glow' &&
28
- (type === 'textDirective' ||
29
- type === 'leafDirective' ||
30
- type === 'containerDirective')
31
- ) {
32
- Object.assign(node, {
33
- type:
34
- type === 'textDirective'
35
- ? 'mdxJsxTextElement'
36
- : 'mdxJsxFlowElement',
37
- name: 'span',
38
- attributes: [
39
- styleAttr(glowStyle(node)),
40
- { type: 'mdxJsxAttribute', name: 'data-md', value: 'glow' },
41
- ],
42
- });
43
- return type === 'containerDirective' ? undefined : false;
44
- }
45
- if (type === 'leafDirective' && name === 'recipe') {
46
- Object.assign(node, {
47
- type: 'mdxJsxFlowElement',
48
- name: 'Recipe',
49
- attributes: [
50
- {
51
- type: 'mdxJsxAttribute',
52
- name: 'id',
53
- value: extractLeafLabel(node).trim(),
54
- },
55
- ...toJsxAttributes(node),
56
- ],
57
- children: [],
58
- });
59
- return false;
60
- }
61
- return undefined;
62
- });
63
- };
64
- }
65
-
66
- function estreeObject(entries: [string, string][]): unknown {
67
- return {
68
- type: 'ObjectExpression',
69
- properties: entries.map(([key, value]) => ({
70
- type: 'Property',
71
- key: { type: 'Identifier', name: key },
72
- value: { type: 'Literal', value, raw: JSON.stringify(value) },
73
- kind: 'init',
74
- computed: false,
75
- shorthand: false,
76
- })),
77
- };
78
- }
79
-
80
- function styleAttr(entries: [string, string][]) {
81
- return {
82
- type: 'mdxJsxAttribute' as const,
83
- name: 'style',
84
- value: jsxExpression(estreeObject(entries)),
85
- };
86
- }
87
-
88
- function attr(node: DirectiveNode, name: string) {
89
- const value = node.attributes?.[name];
90
- return typeof value === 'string' && value ? value : undefined;
91
- }
92
-
93
- function hasFlag(node: DirectiveNode, name: string) {
94
- return node.attributes?.[name] !== undefined;
95
- }
96
-
97
- function gradientStyle(node: DirectiveNode): [string, string][] {
98
- return [
99
- ['backgroundImage', gradientImage(attrObject(node))],
100
- ['WebkitBackgroundClip', 'text'],
101
- ['backgroundClip', 'text'],
102
- ['WebkitTextFillColor', 'transparent'],
103
- ['color', 'transparent'],
104
- ];
105
- }
106
-
107
- function glowStyle(node: DirectiveNode): [string, string][] {
108
- const from = attr(node, 'from') ?? '#fa18cf';
109
- const to = attr(node, 'to') ?? '#ff7967';
110
- const gradient = gradientImage(attrObject(node));
111
- const boxBg = `radial-gradient(color-mix(in srgb, ${from} 18%, transparent), color-mix(in srgb, ${to} 8%, transparent))`;
112
- const style: [string, string][] = [
113
- ['display', 'block'],
114
- ['borderRadius', '20px'],
115
- ['border', '1px solid transparent'],
116
- ['padding', '0.625rem 1rem'],
117
- ['marginBottom', '1.5rem'],
118
- ['textAlign', 'center'],
119
- ['backgroundImage', hasFlag(node, 'gradient') ? `${gradient}, ${boxBg}` : boxBg],
120
- ['boxShadow', `0 10px 20px color-mix(in srgb, ${from} 25%, transparent)`],
121
- ];
122
- if (hasFlag(node, 'gradient')) {
123
- style.push(
124
- ['WebkitBackgroundClip', 'text, border-box'],
125
- ['backgroundClip', 'text, border-box'],
126
- ['WebkitTextFillColor', 'transparent'],
127
- ['color', 'transparent'],
128
- );
129
- }
130
- if (hasFlag(node, 'fade')) style.push(['borderImage', `${gradient} 1`]);
131
- return style;
132
- }
133
-
134
- function attrObject(node: DirectiveNode) {
135
- return {
136
- from: attr(node, 'from'),
137
- to: attr(node, 'to'),
138
- dir: attr(node, 'dir'),
139
- image: attr(node, 'image'),
140
- };
141
- }
@@ -1,44 +0,0 @@
1
- import {
2
- estreeString,
3
- jsxExpression,
4
- walkContainers,
5
- type MdastNode,
6
- } from './directive-utils.ts';
7
-
8
- export function remarkGameExport() {
9
- return (tree: MdastNode) => {
10
- walkContainers(tree, (directive) => {
11
- if (
12
- directive.name !== 'item' &&
13
- directive.name !== 'multiblock' &&
14
- directive.name !== 'image'
15
- )
16
- return;
17
-
18
- const attrs = Object.entries(directive.attributes ?? {}).map(
19
- ([name, value]) => ({
20
- type: 'mdxJsxAttribute' as const,
21
- name,
22
- value:
23
- typeof value === 'string'
24
- ? value
25
- : jsxExpression(estreeString(String(value ?? ''))),
26
- }),
27
- );
28
-
29
- const component =
30
- directive.name === 'item'
31
- ? 'ItemIcon'
32
- : directive.name === 'multiblock'
33
- ? 'Multiblock'
34
- : 'DocsImage';
35
-
36
- Object.assign(directive, {
37
- type: 'mdxJsxFlowElement',
38
- name: component,
39
- attributes: attrs,
40
- children: directive.children ?? [],
41
- });
42
- });
43
- };
44
- }
@@ -1,135 +0,0 @@
1
- import type { MdastNode } from './directive-utils.ts';
2
-
3
- type NodeWithValue = MdastNode & { value: string };
4
- type NodeWithChildren = MdastNode & { children: MdastNode[] };
5
-
6
- function objectToCss(body: string): string | undefined {
7
- const entries: string[] = [];
8
- const re = /(\w+)\s*:\s*['"]([^'"]*)['"]/g;
9
- let m: RegExpExecArray | null;
10
- while ((m = re.exec(body)) !== null) entries.push(`${m[1]}: ${m[2]}`);
11
- return entries.length ? entries.join('; ') : undefined;
12
- }
13
-
14
- const JSX_ATTR = /(\w[\w-]*)=\{\{([\s\S]*?)\}\}/g;
15
-
16
- function rewriteAttrs(html: string): string {
17
- return html.replace(JSX_ATTR, (all, attr: string, body: string) => {
18
- if (attr !== 'style') return all;
19
- const css = objectToCss(body);
20
- return css ? `style="${css}"` : all;
21
- });
22
- }
23
-
24
- const OPEN_TAG = /<([a-zA-Z][\w-]*)((?:[^>"']|"[^"]*"|'[^']*'|\{\{[\s\S]*?\}\})*?)>/;
25
-
26
- function stylesFromTag(tag: string): string | undefined {
27
- const m = /style=\{\{([\s\S]*?)\}\}/.exec(tag);
28
- return m ? objectToCss(m[1]!) : undefined;
29
- }
30
-
31
- function elementNode(
32
- tag: string,
33
- hName: string,
34
- children: MdastNode[],
35
- ): MdastNode {
36
- const style = stylesFromTag(tag);
37
- return {
38
- type: 'emphasis',
39
- children,
40
- data: {
41
- hName,
42
- ...(style ? { hProperties: { style } } : {}),
43
- },
44
- } as MdastNode;
45
- }
46
-
47
- function textNode(value: string): MdastNode {
48
- return { type: 'text', value } as MdastNode;
49
- }
50
-
51
- export function remarkHtmlStyle() {
52
- return (tree: MdastNode) => {
53
- const visitChildren = (parent: NodeWithChildren) => {
54
- const kids = parent.children;
55
- for (let i = 0; i < kids.length; i++) {
56
- const child = kids[i]!;
57
- if (child.type === 'html' && typeof child.value === 'string') {
58
- (child as NodeWithValue).value = rewriteAttrs(child.value);
59
- } else if (
60
- child.type === 'text' &&
61
- typeof child.value === 'string' &&
62
- child.value.includes('={{')
63
- ) {
64
- if (fuseJsxStyleTags(kids, i)) i--;
65
- } else if (child.children) {
66
- visitChildren(child as NodeWithChildren);
67
- }
68
- }
69
- };
70
- visitChildren(tree as NodeWithChildren);
71
- };
72
- }
73
-
74
- function fuseJsxStyleTags(kids: MdastNode[], i: number): boolean {
75
- const text = kids[i] as NodeWithValue;
76
- let open: RegExpExecArray | null;
77
- const re = new RegExp(OPEN_TAG.source, 'g');
78
- do {
79
- open = re.exec(text.value);
80
- } while (open && !open[2]?.includes('={{'));
81
- if (!open) return false;
82
-
83
- const [openTag, tagName] = open;
84
- const prefix = text.value.slice(0, open.index);
85
- const rest = text.value.slice(open.index + openTag.length);
86
-
87
- const closeSelf = new RegExp(`</${tagName}\\s*>`).exec(rest);
88
- if (closeSelf) {
89
- const end = closeSelf.index + closeSelf[0].length;
90
- kids.splice(
91
- i,
92
- 1,
93
- ...compact([
94
- prefix ? textNode(prefix) : undefined,
95
- elementNode(openTag, tagName, [textNode(rest.slice(0, closeSelf.index))]),
96
- rest.slice(end) ? textNode(rest.slice(end)) : undefined,
97
- ]),
98
- );
99
- return true;
100
- }
101
-
102
- for (let j = i + 1; j < kids.length; j++) {
103
- const sib = kids[j]!;
104
- if (
105
- sib.type === 'html' &&
106
- typeof sib.value === 'string' &&
107
- new RegExp(`^</${tagName}\\s*>$`).test(sib.value.trim())
108
- ) {
109
- const inner = rest ? [textNode(rest)] : [];
110
- kids.splice(
111
- i,
112
- j - i + 1,
113
- ...compact([
114
- prefix ? textNode(prefix) : undefined,
115
- elementNode(openTag, tagName, [...inner, ...kids.slice(i + 1, j)]),
116
- ]),
117
- );
118
- return true;
119
- }
120
- }
121
-
122
- kids.splice(
123
- i,
124
- 1,
125
- ...compact([
126
- prefix ? textNode(prefix) : undefined,
127
- elementNode(openTag, tagName, rest ? [textNode(rest)] : []),
128
- ]),
129
- );
130
- return true;
131
- }
132
-
133
- function compact(nodes: (MdastNode | undefined)[]): MdastNode[] {
134
- return nodes.filter((n): n is MdastNode => n !== undefined);
135
- }
@@ -1,252 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import { remarkDirectives } from './directives.ts';
3
- import {
4
- remarkCodeTab,
5
- remarkHeading,
6
- remarkImage,
7
- remarkMdxFiles,
8
- remarkMdxMermaid,
9
- remarkNpm,
10
- remarkSteps,
11
- } from 'fumadocs-core/mdx-plugins';
12
- import remarkDirective from 'remark-directive';
13
- import remarkGfm from 'remark-gfm';
14
- import remarkMath from 'remark-math';
15
- import remarkParse from 'remark-parse';
16
- import { unified, type Plugin } from 'unified';
17
- import {
18
- contentChildren,
19
- extractDirectiveLabel,
20
- jsxAttr,
21
- type DirectiveNode,
22
- type JsxElement,
23
- type MdastNode,
24
- } from './directive-utils.ts';
25
-
26
- const require = createRequire(import.meta.url);
27
-
28
- type VFileLike = {
29
- path?: string;
30
- cwd?: string;
31
- value?: unknown;
32
- };
33
-
34
- type Positioned = {
35
- position?: {
36
- start?: { offset?: number };
37
- end?: { offset?: number };
38
- };
39
- };
40
-
41
- const previewSeq = new WeakMap<object, number>();
42
-
43
- function nextPreviewId(file: VFileLike): number {
44
- const key = file as object;
45
- const n = (previewSeq.get(key) ?? 0) + 1;
46
- previewSeq.set(key, n);
47
- return n;
48
- }
49
-
50
- function slugify(text: string): string {
51
- return (
52
- text
53
- .toLowerCase()
54
- .replace(/[^\p{L}\p{N}]+/gu, '-')
55
- .replace(/^-+|-+$/g, '') || 'section'
56
- );
57
- }
58
-
59
- function walk(node: MdastNode, visit: (n: MdastNode) => void): void {
60
- visit(node);
61
- for (const child of node.children ?? []) walk(child, visit);
62
- }
63
-
64
- function markHeadingNoToc(heading: MdastNode): void {
65
- const kids = heading.children ?? [];
66
- const last = kids[kids.length - 1];
67
- if (last?.type === 'text' && typeof last.value === 'string') {
68
- if (!last.value.includes('[!toc]')) {
69
- last.value = `${last.value.replace(/\s+$/, '')} [!toc]`;
70
- }
71
- return;
72
- }
73
- kids.push({ type: 'text', value: ' [!toc]' });
74
- heading.children = kids;
75
- }
76
-
77
- function uniquifyHeadingIds(tree: MdastNode): void {
78
- const used = new Set<string>();
79
- walk(tree, (node) => {
80
- if (node.type !== 'heading') return;
81
- const props = (node.data?.hProperties ?? {}) as { id?: string };
82
- node.data ??= {};
83
- node.data.hProperties = props;
84
- if (typeof props.id !== 'string' || props.id.length === 0) return;
85
-
86
- let id = props.id;
87
- if (!used.has(id)) {
88
- used.add(id);
89
- return;
90
- }
91
- let n = 1;
92
- while (used.has(`${id}-${n}`)) n += 1;
93
- props.id = `${id}-${n}`;
94
- used.add(props.id);
95
- });
96
- }
97
-
98
- export function remarkPreview() {
99
- return async (tree: MdastNode, file: VFileLike) => {
100
- previewSeq.set(file as object, 0);
101
- await expandPreviews(tree, file);
102
- uniquifyHeadingIds(tree);
103
- };
104
- }
105
-
106
- async function expandPreviews(parent: MdastNode, file: VFileLike): Promise<void> {
107
- const children = parent.children;
108
- if (!children) return;
109
-
110
- for (let i = 0; i < children.length; i++) {
111
- const child = children[i]!;
112
- if (child.type === 'containerDirective' && child.name === 'preview') {
113
- const expanded = await expandPreview(child as DirectiveNode, file);
114
- children.splice(i, 1, ...expanded);
115
- i += expanded.length - 1;
116
- continue;
117
- }
118
- await expandPreviews(child, file);
119
- }
120
- }
121
-
122
- function isMdEscapeFence(node: MdastNode): boolean {
123
- if (node.type !== 'code' || typeof node.value !== 'string') return false;
124
- const lang = typeof node.lang === 'string' ? node.lang.toLowerCase() : '';
125
- return lang === 'md' || lang === 'markdown';
126
- }
127
-
128
- function fileText(file: VFileLike): string {
129
- if (typeof file.value === 'string') return file.value;
130
- if (file.value == null) return '';
131
- if (Buffer.isBuffer(file.value)) return file.value.toString('utf8');
132
- return String(file.value);
133
- }
134
-
135
- function offsetOf(node: MdastNode, side: 'start' | 'end'): number | undefined {
136
- const pos = (node as Positioned).position?.[side]?.offset;
137
- return typeof pos === 'number' ? pos : undefined;
138
- }
139
-
140
- function sourceFromChildren(
141
- kids: MdastNode[],
142
- file: VFileLike,
143
- ): string | undefined {
144
- if (kids.length === 0) return undefined;
145
-
146
- if (kids.length === 1 && isMdEscapeFence(kids[0]!)) {
147
- return kids[0]!.value as string;
148
- }
149
-
150
- const raw = fileText(file);
151
- const start = offsetOf(kids[0]!, 'start');
152
- const end = offsetOf(kids[kids.length - 1]!, 'end');
153
- if (raw && start != null && end != null && end >= start) {
154
- return raw.slice(start, end).replace(/\r\n/g, '\n').trimEnd();
155
- }
156
-
157
- return undefined;
158
- }
159
-
160
- async function expandPreview(
161
- node: DirectiveNode,
162
- file: VFileLike,
163
- ): Promise<MdastNode[]> {
164
- const kids = contentChildren(node);
165
- const source = sourceFromChildren(kids, file);
166
- if (source === undefined) return kids;
167
-
168
- const label = extractDirectiveLabel(node);
169
- const previewNo = nextPreviewId(file);
170
-
171
- const input: JsxElement = {
172
- type: 'mdxJsxFlowElement',
173
- name: 'Input',
174
- attributes: label ? [jsxAttr('label', label)] : [],
175
- children: [
176
- {
177
- type: 'code',
178
- lang: 'md',
179
- meta: 'title="example.md" lineNumbers',
180
- value: source,
181
- },
182
- ],
183
- };
184
-
185
- const parsed = await processOutputBody(source, file, previewNo);
186
-
187
- const output: JsxElement = {
188
- type: 'mdxJsxFlowElement',
189
- name: 'Output',
190
- attributes: [],
191
- children: parsed.children ?? [],
192
- };
193
-
194
- const preview: JsxElement = {
195
- type: 'mdxJsxFlowElement',
196
- name: 'Preview',
197
- attributes: [],
198
- children: [input as unknown as MdastNode, output as unknown as MdastNode],
199
- };
200
-
201
- return [preview as unknown as MdastNode];
202
- }
203
-
204
- async function processOutputBody(
205
- source: string,
206
- file: VFileLike,
207
- previewNo: number,
208
- ): Promise<MdastNode> {
209
-
210
- const { remarkInclude } = require('fumadocs-mdx/config') as {
211
- remarkInclude: Plugin;
212
- };
213
-
214
- const used = new Map<string, number>();
215
-
216
- const processor = unified()
217
- .use(remarkParse)
218
- .use(remarkGfm)
219
- .use(remarkMath)
220
- .use(remarkDirective)
221
- .use(remarkDirectives)
222
- .use(remarkInclude)
223
- .use(remarkImage, { publicDir: 'public', useImport: false })
224
- .use(remarkNpm)
225
- .use(remarkCodeTab)
226
- .use(remarkMdxFiles)
227
- .use(remarkMdxMermaid)
228
- .use(remarkSteps)
229
- .use(remarkHeading, {
230
- generateToc: false,
231
- customId: false,
232
- slug: (_root, _heading, text: string) => {
233
- const base = slugify(text);
234
- const n = used.get(base) ?? 0;
235
- used.set(base, n + 1);
236
- const local = n === 0 ? base : `${base}-${n}`;
237
- return `preview-${previewNo}-${local}`;
238
- },
239
- });
240
-
241
- const tree = processor.parse({ value: source, path: file.path });
242
- const result = (await processor.run(
243
- tree,
244
- file as never,
245
- )) as unknown as MdastNode;
246
-
247
- walk(result, (node) => {
248
- if (node.type === 'heading') markHeadingNoToc(node);
249
- });
250
-
251
- return result;
252
- }