@terrafirmagreg-team/mdx 1.2.1 → 1.2.2

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.
@@ -1,468 +1,468 @@
1
- import type { TypeNode } from '@fumadocs/story/type-tree';
2
- import { md } from './md';
3
-
4
- export type DirectiveMeta = {
5
- label?: string;
6
- colon?: 1 | 2 | 3;
7
- name?: string;
8
- nameArg?: string;
9
- fence?: string;
10
- body?: string;
11
- hide?: string[];
12
- multiline?: string[];
13
- props?: string[];
14
- solo?: boolean;
15
- descriptions?: Record<string, string>;
16
- raw?: string;
17
- variants?: Record<string, Partial<DirectiveMeta>>;
18
- };
19
-
20
- const ENTRY_PROPS = [
21
- 'id',
22
- 'dataset',
23
- 'name',
24
- 'size',
25
- 'variant',
26
- 'labeled',
27
- 'label',
28
- 'copy',
29
- 'missing',
30
- 'tooltip',
31
- 'className',
32
- ];
33
-
34
- const DIRECTIVE_META: Record<string, DirectiveMeta> = {
35
- item: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
36
- block: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
37
- fluid: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
38
- entity: {
39
- label: 'id',
40
- props: [...ENTRY_PROPS, 'view'],
41
- descriptions: {
42
- view: 'auto: texture→egg fallback · texture: registry texture · egg: spawn egg · model: 3D model',
43
- },
44
- },
45
- tag: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
46
- quest: {
47
- label: 'id',
48
- props: [...ENTRY_PROPS, 'filename', 'href'],
49
- descriptions: {
50
- id: 'Quest id (hex), chapter filename or localized title',
51
- },
52
- },
53
- guide: {
54
- label: 'id',
55
- props: [...ENTRY_PROPS, 'href', 'modId', 'bookPath', 'entry', 'hash'],
56
- },
57
- recipe: { label: 'id', props: ['id', 'dataset', 'maxH', 'className'] },
58
- multiblock: {
59
- label: 'id',
60
- multiline: ['spec'],
61
- props: ['id', 'name', 'spec', 'label', 'title', 'dataset', 'className'],
62
- descriptions: {
63
- id: 'Dump model id (ns:name or hash), resolved via multiblock index',
64
- name: 'Localized guide name, resolved via guide entries index',
65
- spec: 'Inline GuideMultiblock JSON (:::multiblock + ```json fence)',
66
- },
67
- },
68
- card: {
69
- label: 'title',
70
- props: [
71
- 'title',
72
- 'icon',
73
- 'description',
74
- 'href',
75
- 'external',
76
- 'children',
77
- 'className',
78
- ],
79
- },
80
- 'type-table': { colon: 3, solo: true, hide: ['type'], props: ['type'] },
81
- icon: {
82
- label: 'name',
83
- colon: 1,
84
- props: ['name', 'size', 'color', 'strokeWidth', 'className'],
85
- descriptions: {
86
- name: 'Lucide or Simple Icons name, kebab-case ok (book-open, github)',
87
- size: 'Icon size, number (px) or CSS length; defaults to 1em',
88
- color: 'Any CSS color: keyword (tomato), hex, rgb(), var(--x)',
89
- strokeWidth: 'Lucide stroke width, default 2',
90
- },
91
- },
92
- 'gradient-text': {
93
- name: 'gradient',
94
- label: 'children',
95
- colon: 1,
96
- props: ['from', 'to', 'image', 'children', 'className'],
97
- descriptions: {
98
- from: 'Gradient start color',
99
- to: 'Gradient end color',
100
- image: 'CSS image overriding the gradient',
101
- children: 'Text content, the directive label',
102
- },
103
- },
104
- 'glow-header': {
105
- name: 'glow',
106
- label: 'children',
107
- colon: 1,
108
- props: ['from', 'to', 'image', 'gradient', 'fade', 'children', 'className'],
109
- descriptions: {
110
- from: 'Gradient start color',
111
- to: 'Gradient end color',
112
- image: 'CSS image overriding the gradient',
113
- gradient: 'Also tint the background box with the gradient',
114
- fade: 'Gradient border along the bottom edge',
115
- children: 'Header text, the directive label',
116
- },
117
- },
118
- callout: {
119
- nameArg: 'type',
120
- label: 'title',
121
- colon: 3,
122
- multiline: ['children'],
123
- props: ['type', 'title', 'children', 'className'],
124
- descriptions: {
125
- type: 'Callout kind, becomes the directive name (:::info …)',
126
- title: 'Overrides the default localized title',
127
- children: 'Body markdown',
128
- },
129
- },
130
- banner: {
131
- colon: 3,
132
- multiline: ['children'],
133
- props: ['id', 'height', 'variant', 'rainbowColors', 'changeLayout', 'children', 'className'],
134
- },
135
- tabs: {
136
- colon: 3,
137
- props: [
138
- 'items',
139
- 'defaultIndex',
140
- 'label',
141
- 'persist',
142
- 'groupId',
143
- 'updateAnchor',
144
- 'children',
145
- 'className',
146
- ],
147
- },
148
- accordions: {
149
- colon: 3,
150
- props: ['multiple', 'defaultValue', 'children', 'className'],
151
- },
152
- image: {
153
- label: 'alt',
154
- colon: 3,
155
- solo: true,
156
- props: ['src', 'pack', 'mod', 'path', 'alt', 'width', 'height', 'caption', 'className', 'defaultPack'],
157
- },
158
- steps: {
159
- body: md`
160
- :::steps
161
- ### 1. …
162
- ### 2. …
163
- :::
164
- `,
165
- props: ['children'],
166
- },
167
- files: {
168
- fence: 'files',
169
- body: md`
170
- TerraFirmaGreg
171
- ├── mods
172
- └── config
173
- `,
174
- props: ['children'],
175
- },
176
- code: {
177
- fence: 'ts',
178
- body: 'const x = 1;',
179
- props: ['title', 'allowCopy', 'children', 'className'],
180
- variants: {
181
- Titled: {
182
- fence: 'ts title="hello.ts"',
183
- body: md`
184
- export function hello() {
185
- return "hi";
186
- }
187
- `,
188
- },
189
- 'Line numbers': {
190
- fence: 'ts title="lines.ts" lineNumbers',
191
- body: md`
192
- const a = 1;
193
- const b = 2;
194
- const c = 3;
195
- `,
196
- },
197
- Highlight: {
198
- fence: 'ts {1,3}',
199
- body: md`
200
- const a = 1;
201
- const b = 2;
202
- const c = 3;
203
- `,
204
- },
205
- Diff: {
206
- fence: 'ts',
207
- body: md`
208
- const a = 1; // [!code --]
209
- const b = 2; // [!code ++]
210
- const c = 3;
211
- `,
212
- },
213
- Focus: {
214
- fence: 'ts',
215
- body: md`
216
- const a = 1;
217
- const b = 2; // [!code focus]
218
- const c = 3;
219
- `,
220
- },
221
- Word: {
222
- fence: 'ts',
223
- body: md`
224
- const amount = 42; // [!code word:amount]
225
- `,
226
- },
227
- 'No copy': {
228
- fence: 'ts noCopy',
229
- body: 'const x = 1;',
230
- },
231
- },
232
- },
233
- twoslash: {
234
- fence: 'ts twoslash',
235
- body: md`
236
- const amount = 42;
237
- // ^?
238
- `,
239
- props: [],
240
- variants: {
241
- Hover: {
242
- body: md`
243
- const amount = 42;
244
- amount.toFixed(2);
245
- `,
246
- },
247
- Errors: {
248
- body: md`
249
- const a: number = "oops";
250
- // @errors: 2322
251
- `,
252
- },
253
- Tags: {
254
- body: md`
255
- const amount = 42;
256
- // @log: computed once at build time
257
- // @warn: deprecated in favour of amount2
258
- // @annotate: see quest docs for usage
259
- `,
260
- },
261
- 'Completions ^|': {
262
- body: md`
263
- const m = Math;
264
- m.a
265
- // ^|
266
- `,
267
- },
268
- 'Highlight ^^^': {
269
- body: md`
270
- const amount = 42;
271
- // ^^^^^
272
- `,
273
- },
274
- Cut: {
275
- body: md`
276
- const secret = "hidden prelude";
277
- // ---cut---
278
- const amount = 42;
279
- `,
280
- },
281
- },
282
- },
283
- 'code-tabs': {
284
- raw: md`
285
- \`\`\`ts tab="pnpm"
286
- pnpm add fumadocs-core
287
- \`\`\`
288
-
289
- \`\`\`ts tab="npm"
290
- npm install fumadocs-core
291
- \`\`\`
292
- `,
293
- props: [],
294
- variants: {
295
- 'Package manager': {
296
- raw: md`
297
- \`\`\`npm
298
- npm install fumadocs-core
299
- \`\`\`
300
- `,
301
- },
302
- Synced: {
303
- raw: md`
304
- \`\`\`ts tab="pnpm" tab-group="pm"
305
- pnpm add fumadocs-core
306
- \`\`\`
307
-
308
- \`\`\`ts tab="npm" tab-group="pm"
309
- npm install fumadocs-core
310
- \`\`\`
311
- `,
312
- },
313
- },
314
- },
315
- mermaid: {
316
- fence: 'mermaid',
317
- body: '',
318
- multiline: ['chart'],
319
- props: ['chart'],
320
- descriptions: {
321
- chart: 'Mermaid diagram source, plain markdown inside the fence',
322
- },
323
- },
324
- math: {
325
- body: ':math[e = mc^2]',
326
- props: ['children', 'block'],
327
- multiline: ['children'],
328
- descriptions: {
329
- children: 'KaTeX source, the `:math[…]` label',
330
- block: 'Display block (`::math`/`:::math`) instead of inline `:math`',
331
- },
332
- },
333
- };
334
-
335
- export function directiveMeta(key: string): DirectiveMeta | undefined {
336
- return DIRECTIVE_META[key];
337
- }
338
-
339
- function attr(v: unknown): string | undefined {
340
- if (v === true) return undefined;
341
- if (v === false) return 'false';
342
- if (typeof v === 'object') return JSON.stringify(v);
343
- return String(v);
344
- }
345
-
346
- export function tweakControls(
347
- key: string,
348
- node: TypeNode,
349
- defaults?: Record<string, unknown>,
350
- ): TypeNode {
351
- if (node.type !== 'object') return node;
352
- const meta = DIRECTIVE_META[key];
353
- const whitelist = meta?.props ? new Set(meta.props) : undefined;
354
- const multiline = new Set(meta?.multiline ?? []);
355
- const properties = node.properties
356
- .filter(
357
- (p) =>
358
- (!whitelist || whitelist.has(p.name)) &&
359
- (p.name !== 'children' ||
360
- defaults?.children === undefined ||
361
- typeof defaults.children === 'string'),
362
- )
363
- .map((p) =>
364
- multiline.has(p.name)
365
- ? { ...p, type: { ...p.type, multiline: true } as unknown as TypeNode }
366
- : p,
367
- );
368
- return { ...node, properties };
369
- }
370
-
371
- export function remarkFor(
372
- key: string,
373
- args: Record<string, unknown>,
374
- controls?: TypeNode,
375
- variant?: string,
376
- ): string {
377
- const base = DIRECTIVE_META[key] ?? {};
378
- const meta: DirectiveMeta = {
379
- ...base,
380
- ...(variant ? base.variants?.[variant] : undefined),
381
- };
382
- if (meta.raw) return meta.raw;
383
- if (key === 'tabs') {
384
- const items = Array.isArray(args.items)
385
- ? (args.items as unknown[]).map(String)
386
- : [];
387
- const attrs: string[] = [];
388
- const di = typeof args.defaultIndex === 'number' ? args.defaultIndex : 0;
389
- if (di > 0 && items[di]) attrs.push(`default="${items[di]}"`);
390
- if (typeof args.label === 'string' && args.label)
391
- attrs.push(`label="${args.label}"`);
392
- if (args.persist) attrs.push('persist');
393
- if (typeof args.groupId === 'string' && args.groupId)
394
- attrs.push(`groupId="${args.groupId}"`);
395
- if (args.updateAnchor) attrs.push('updateAnchor');
396
- const body = items.length
397
- ? items.map((item) => `::tab[${item}]\n…`).join('\n\n')
398
- : '::tab[…]\n…';
399
- return `:::tabs${attrs.length ? `{${attrs.join(' ')}}` : ''}\n${body}\n:::`;
400
- }
401
- if (key === 'accordions') {
402
- return md`
403
- :::accordions${args.multiple ? '{multiple}' : ''}
404
- ::accordion[Title one]
405
- …
406
-
407
- ::accordion[Title two]
408
- …
409
- :::
410
- `;
411
- }
412
- if (key === 'multiblock' && typeof args.spec === 'string' && args.spec) {
413
- return `:::multiblock\n\`\`\`json\n${args.spec}\n\`\`\`\n:::`;
414
- }
415
- if (key === 'math') {
416
- const tex = typeof args.children === 'string' ? args.children : 'e = mc^2';
417
- if (tex.includes(']'))
418
- return args.block ? `$$\n${tex}\n$$` : `$${tex}$`;
419
- if (args.block)
420
- return tex.includes('\n')
421
- ? `:::math\n${tex}\n:::`
422
- : `::math[${tex}]`;
423
- return `:math[${tex}]`;
424
- }
425
- if (meta.fence) {
426
- const body = key === 'mermaid' ? String(args.chart ?? '…') : meta.body;
427
- return '```' + meta.fence + '\n' + body + '\n```';
428
- }
429
- if (meta.body) return meta.body;
430
- const name = meta.nameArg
431
- ? String(args[meta.nameArg] ?? meta.name ?? key)
432
- : (meta.name ?? key);
433
- const skip = new Set(
434
- [
435
- 'children',
436
- meta.label,
437
- meta.nameArg,
438
- ...(meta.hide ?? []),
439
- ].filter(Boolean),
440
- );
441
- const editable =
442
- controls?.type === 'object'
443
- ? new Set(controls.properties.map((p) => p.name))
444
- : undefined;
445
- const attrs = Object.entries(args)
446
- .filter(
447
- ([k, v]) =>
448
- !skip.has(k) &&
449
- (!editable || editable.has(k)) &&
450
- v !== undefined &&
451
- v !== '',
452
- )
453
- .map(([k, v]) => {
454
- const s = attr(v);
455
- return s === undefined ? k : `${k}="${s}"`;
456
- })
457
- .join(' ');
458
- const label =
459
- meta.label && args[meta.label] != null
460
- ? `[${String(args[meta.label])}]`
461
- : '';
462
- const head = `${':'.repeat(meta.colon ?? 2)}${name}${label}${attrs ? `{${attrs}}` : ''}`;
463
- if (meta.colon !== 3 || meta.solo) return head;
464
- const inner =
465
- typeof args.children === 'string' && args.children ? args.children : '…';
466
- return `${head}\n${inner}\n:::`;
467
- }
468
-
1
+ import type { TypeNode } from '@fumadocs/story/type-tree';
2
+ import { md } from './md';
3
+
4
+ export type DirectiveMeta = {
5
+ label?: string;
6
+ colon?: 1 | 2 | 3;
7
+ name?: string;
8
+ nameArg?: string;
9
+ fence?: string;
10
+ body?: string;
11
+ hide?: string[];
12
+ multiline?: string[];
13
+ props?: string[];
14
+ solo?: boolean;
15
+ descriptions?: Record<string, string>;
16
+ raw?: string;
17
+ variants?: Record<string, Partial<DirectiveMeta>>;
18
+ };
19
+
20
+ const ENTRY_PROPS = [
21
+ 'id',
22
+ 'dataset',
23
+ 'name',
24
+ 'size',
25
+ 'variant',
26
+ 'labeled',
27
+ 'label',
28
+ 'copy',
29
+ 'missing',
30
+ 'tooltip',
31
+ 'className',
32
+ ];
33
+
34
+ const DIRECTIVE_META: Record<string, DirectiveMeta> = {
35
+ item: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
36
+ block: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
37
+ fluid: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
38
+ entity: {
39
+ label: 'id',
40
+ props: [...ENTRY_PROPS, 'view'],
41
+ descriptions: {
42
+ view: 'auto: texture→egg fallback · texture: registry texture · egg: spawn egg · model: 3D model',
43
+ },
44
+ },
45
+ tag: { label: 'id', props: [...ENTRY_PROPS, 'href', 'tab', 'search'] },
46
+ quest: {
47
+ label: 'id',
48
+ props: [...ENTRY_PROPS, 'filename', 'href'],
49
+ descriptions: {
50
+ id: 'Quest id (hex), chapter filename or localized title',
51
+ },
52
+ },
53
+ guide: {
54
+ label: 'id',
55
+ props: [...ENTRY_PROPS, 'href', 'modId', 'bookPath', 'entry', 'hash'],
56
+ },
57
+ recipe: { label: 'id', props: ['id', 'dataset', 'maxH', 'className'] },
58
+ multiblock: {
59
+ label: 'id',
60
+ multiline: ['spec'],
61
+ props: ['id', 'name', 'spec', 'label', 'title', 'dataset', 'className'],
62
+ descriptions: {
63
+ id: 'Dump model id (ns:name or hash), resolved via multiblock index',
64
+ name: 'Localized guide name, resolved via guide entries index',
65
+ spec: 'Inline GuideMultiblock JSON (:::multiblock + ```json fence)',
66
+ },
67
+ },
68
+ card: {
69
+ label: 'title',
70
+ props: [
71
+ 'title',
72
+ 'icon',
73
+ 'description',
74
+ 'href',
75
+ 'external',
76
+ 'children',
77
+ 'className',
78
+ ],
79
+ },
80
+ 'type-table': { colon: 3, solo: true, hide: ['type'], props: ['type'] },
81
+ icon: {
82
+ label: 'name',
83
+ colon: 1,
84
+ props: ['name', 'size', 'color', 'strokeWidth', 'className'],
85
+ descriptions: {
86
+ name: 'Lucide or Simple Icons name, kebab-case ok (book-open, github)',
87
+ size: 'Icon size, number (px) or CSS length; defaults to 1em',
88
+ color: 'Any CSS color: keyword (tomato), hex, rgb(), var(--x)',
89
+ strokeWidth: 'Lucide stroke width, default 2',
90
+ },
91
+ },
92
+ 'gradient-text': {
93
+ name: 'gradient',
94
+ label: 'children',
95
+ colon: 1,
96
+ props: ['from', 'to', 'image', 'children', 'className'],
97
+ descriptions: {
98
+ from: 'Gradient start color',
99
+ to: 'Gradient end color',
100
+ image: 'CSS image overriding the gradient',
101
+ children: 'Text content, the directive label',
102
+ },
103
+ },
104
+ 'glow-header': {
105
+ name: 'glow',
106
+ label: 'children',
107
+ colon: 1,
108
+ props: ['from', 'to', 'image', 'gradient', 'fade', 'children', 'className'],
109
+ descriptions: {
110
+ from: 'Gradient start color',
111
+ to: 'Gradient end color',
112
+ image: 'CSS image overriding the gradient',
113
+ gradient: 'Also tint the background box with the gradient',
114
+ fade: 'Gradient border along the bottom edge',
115
+ children: 'Header text, the directive label',
116
+ },
117
+ },
118
+ callout: {
119
+ nameArg: 'type',
120
+ label: 'title',
121
+ colon: 3,
122
+ multiline: ['children'],
123
+ props: ['type', 'title', 'children', 'className'],
124
+ descriptions: {
125
+ type: 'Callout kind, becomes the directive name (:::info …)',
126
+ title: 'Overrides the default localized title',
127
+ children: 'Body markdown',
128
+ },
129
+ },
130
+ banner: {
131
+ colon: 3,
132
+ multiline: ['children'],
133
+ props: ['id', 'height', 'variant', 'rainbowColors', 'changeLayout', 'children', 'className'],
134
+ },
135
+ tabs: {
136
+ colon: 3,
137
+ props: [
138
+ 'items',
139
+ 'defaultIndex',
140
+ 'label',
141
+ 'persist',
142
+ 'groupId',
143
+ 'updateAnchor',
144
+ 'children',
145
+ 'className',
146
+ ],
147
+ },
148
+ accordions: {
149
+ colon: 3,
150
+ props: ['multiple', 'defaultValue', 'children', 'className'],
151
+ },
152
+ image: {
153
+ label: 'alt',
154
+ colon: 3,
155
+ solo: true,
156
+ props: ['src', 'pack', 'mod', 'path', 'alt', 'width', 'height', 'caption', 'className', 'defaultPack'],
157
+ },
158
+ steps: {
159
+ body: md`
160
+ :::steps
161
+ ### 1. …
162
+ ### 2. …
163
+ :::
164
+ `,
165
+ props: ['children'],
166
+ },
167
+ files: {
168
+ fence: 'files',
169
+ body: md`
170
+ TerraFirmaGreg
171
+ ├── mods
172
+ └── config
173
+ `,
174
+ props: ['children'],
175
+ },
176
+ code: {
177
+ fence: 'ts',
178
+ body: 'const x = 1;',
179
+ props: ['title', 'allowCopy', 'children', 'className'],
180
+ variants: {
181
+ Titled: {
182
+ fence: 'ts title="hello.ts"',
183
+ body: md`
184
+ export function hello() {
185
+ return "hi";
186
+ }
187
+ `,
188
+ },
189
+ 'Line numbers': {
190
+ fence: 'ts title="lines.ts" lineNumbers',
191
+ body: md`
192
+ const a = 1;
193
+ const b = 2;
194
+ const c = 3;
195
+ `,
196
+ },
197
+ Highlight: {
198
+ fence: 'ts {1,3}',
199
+ body: md`
200
+ const a = 1;
201
+ const b = 2;
202
+ const c = 3;
203
+ `,
204
+ },
205
+ Diff: {
206
+ fence: 'ts',
207
+ body: md`
208
+ const a = 1; // [!code --]
209
+ const b = 2; // [!code ++]
210
+ const c = 3;
211
+ `,
212
+ },
213
+ Focus: {
214
+ fence: 'ts',
215
+ body: md`
216
+ const a = 1;
217
+ const b = 2; // [!code focus]
218
+ const c = 3;
219
+ `,
220
+ },
221
+ Word: {
222
+ fence: 'ts',
223
+ body: md`
224
+ const amount = 42; // [!code word:amount]
225
+ `,
226
+ },
227
+ 'No copy': {
228
+ fence: 'ts noCopy',
229
+ body: 'const x = 1;',
230
+ },
231
+ },
232
+ },
233
+ twoslash: {
234
+ fence: 'ts twoslash',
235
+ body: md`
236
+ const amount = 42;
237
+ // ^?
238
+ `,
239
+ props: [],
240
+ variants: {
241
+ Hover: {
242
+ body: md`
243
+ const amount = 42;
244
+ amount.toFixed(2);
245
+ `,
246
+ },
247
+ Errors: {
248
+ body: md`
249
+ const a: number = "oops";
250
+ // @errors: 2322
251
+ `,
252
+ },
253
+ Tags: {
254
+ body: md`
255
+ const amount = 42;
256
+ // @log: computed once at build time
257
+ // @warn: deprecated in favour of amount2
258
+ // @annotate: see quest docs for usage
259
+ `,
260
+ },
261
+ 'Completions ^|': {
262
+ body: md`
263
+ const m = Math;
264
+ m.a
265
+ // ^|
266
+ `,
267
+ },
268
+ 'Highlight ^^^': {
269
+ body: md`
270
+ const amount = 42;
271
+ // ^^^^^
272
+ `,
273
+ },
274
+ Cut: {
275
+ body: md`
276
+ const secret = "hidden prelude";
277
+ // ---cut---
278
+ const amount = 42;
279
+ `,
280
+ },
281
+ },
282
+ },
283
+ 'code-tabs': {
284
+ raw: md`
285
+ \`\`\`ts tab="pnpm"
286
+ pnpm add fumadocs-core
287
+ \`\`\`
288
+
289
+ \`\`\`ts tab="npm"
290
+ npm install fumadocs-core
291
+ \`\`\`
292
+ `,
293
+ props: [],
294
+ variants: {
295
+ 'Package manager': {
296
+ raw: md`
297
+ \`\`\`npm
298
+ npm install fumadocs-core
299
+ \`\`\`
300
+ `,
301
+ },
302
+ Synced: {
303
+ raw: md`
304
+ \`\`\`ts tab="pnpm" tab-group="pm"
305
+ pnpm add fumadocs-core
306
+ \`\`\`
307
+
308
+ \`\`\`ts tab="npm" tab-group="pm"
309
+ npm install fumadocs-core
310
+ \`\`\`
311
+ `,
312
+ },
313
+ },
314
+ },
315
+ mermaid: {
316
+ fence: 'mermaid',
317
+ body: '',
318
+ multiline: ['chart'],
319
+ props: ['chart'],
320
+ descriptions: {
321
+ chart: 'Mermaid diagram source, plain markdown inside the fence',
322
+ },
323
+ },
324
+ math: {
325
+ body: ':math[e = mc^2]',
326
+ props: ['children', 'block'],
327
+ multiline: ['children'],
328
+ descriptions: {
329
+ children: 'KaTeX source, the `:math[…]` label',
330
+ block: 'Display block (`::math`/`:::math`) instead of inline `:math`',
331
+ },
332
+ },
333
+ };
334
+
335
+ export function directiveMeta(key: string): DirectiveMeta | undefined {
336
+ return DIRECTIVE_META[key];
337
+ }
338
+
339
+ function attr(v: unknown): string | undefined {
340
+ if (v === true) return undefined;
341
+ if (v === false) return 'false';
342
+ if (typeof v === 'object') return JSON.stringify(v);
343
+ return String(v);
344
+ }
345
+
346
+ export function tweakControls(
347
+ key: string,
348
+ node: TypeNode,
349
+ defaults?: Record<string, unknown>,
350
+ ): TypeNode {
351
+ if (node.type !== 'object') return node;
352
+ const meta = DIRECTIVE_META[key];
353
+ const whitelist = meta?.props ? new Set(meta.props) : undefined;
354
+ const multiline = new Set(meta?.multiline ?? []);
355
+ const properties = node.properties
356
+ .filter(
357
+ (p) =>
358
+ (!whitelist || whitelist.has(p.name)) &&
359
+ (p.name !== 'children' ||
360
+ defaults?.children === undefined ||
361
+ typeof defaults.children === 'string'),
362
+ )
363
+ .map((p) =>
364
+ multiline.has(p.name)
365
+ ? { ...p, type: { ...p.type, multiline: true } as unknown as TypeNode }
366
+ : p,
367
+ );
368
+ return { ...node, properties };
369
+ }
370
+
371
+ export function remarkFor(
372
+ key: string,
373
+ args: Record<string, unknown>,
374
+ controls?: TypeNode,
375
+ variant?: string,
376
+ ): string {
377
+ const base = DIRECTIVE_META[key] ?? {};
378
+ const meta: DirectiveMeta = {
379
+ ...base,
380
+ ...(variant ? base.variants?.[variant] : undefined),
381
+ };
382
+ if (meta.raw) return meta.raw;
383
+ if (key === 'tabs') {
384
+ const items = Array.isArray(args.items)
385
+ ? (args.items as unknown[]).map(String)
386
+ : [];
387
+ const attrs: string[] = [];
388
+ const di = typeof args.defaultIndex === 'number' ? args.defaultIndex : 0;
389
+ if (di > 0 && items[di]) attrs.push(`default="${items[di]}"`);
390
+ if (typeof args.label === 'string' && args.label)
391
+ attrs.push(`label="${args.label}"`);
392
+ if (args.persist) attrs.push('persist');
393
+ if (typeof args.groupId === 'string' && args.groupId)
394
+ attrs.push(`groupId="${args.groupId}"`);
395
+ if (args.updateAnchor) attrs.push('updateAnchor');
396
+ const body = items.length
397
+ ? items.map((item) => `::tab[${item}]\n…`).join('\n\n')
398
+ : '::tab[…]\n…';
399
+ return `:::tabs${attrs.length ? `{${attrs.join(' ')}}` : ''}\n${body}\n:::`;
400
+ }
401
+ if (key === 'accordions') {
402
+ return md`
403
+ :::accordions${args.multiple ? '{multiple}' : ''}
404
+ ::accordion[Title one]
405
+ …
406
+
407
+ ::accordion[Title two]
408
+ …
409
+ :::
410
+ `;
411
+ }
412
+ if (key === 'multiblock' && typeof args.spec === 'string' && args.spec) {
413
+ return `:::multiblock\n\`\`\`json\n${args.spec}\n\`\`\`\n:::`;
414
+ }
415
+ if (key === 'math') {
416
+ const tex = typeof args.children === 'string' ? args.children : 'e = mc^2';
417
+ if (tex.includes(']'))
418
+ return args.block ? `$$\n${tex}\n$$` : `$${tex}$`;
419
+ if (args.block)
420
+ return tex.includes('\n')
421
+ ? `:::math\n${tex}\n:::`
422
+ : `::math[${tex}]`;
423
+ return `:math[${tex}]`;
424
+ }
425
+ if (meta.fence) {
426
+ const body = key === 'mermaid' ? String(args.chart ?? '…') : meta.body;
427
+ return '```' + meta.fence + '\n' + body + '\n```';
428
+ }
429
+ if (meta.body) return meta.body;
430
+ const name = meta.nameArg
431
+ ? String(args[meta.nameArg] ?? meta.name ?? key)
432
+ : (meta.name ?? key);
433
+ const skip = new Set(
434
+ [
435
+ 'children',
436
+ meta.label,
437
+ meta.nameArg,
438
+ ...(meta.hide ?? []),
439
+ ].filter(Boolean),
440
+ );
441
+ const editable =
442
+ controls?.type === 'object'
443
+ ? new Set(controls.properties.map((p) => p.name))
444
+ : undefined;
445
+ const attrs = Object.entries(args)
446
+ .filter(
447
+ ([k, v]) =>
448
+ !skip.has(k) &&
449
+ (!editable || editable.has(k)) &&
450
+ v !== undefined &&
451
+ v !== '',
452
+ )
453
+ .map(([k, v]) => {
454
+ const s = attr(v);
455
+ return s === undefined ? k : `${k}="${s}"`;
456
+ })
457
+ .join(' ');
458
+ const label =
459
+ meta.label && args[meta.label] != null
460
+ ? `[${String(args[meta.label])}]`
461
+ : '';
462
+ const head = `${':'.repeat(meta.colon ?? 2)}${name}${label}${attrs ? `{${attrs}}` : ''}`;
463
+ if (meta.colon !== 3 || meta.solo) return head;
464
+ const inner =
465
+ typeof args.children === 'string' && args.children ? args.children : '…';
466
+ return `${head}\n${inner}\n:::`;
467
+ }
468
+