@terrafirmagreg-team/mdx 1.2.1 → 1.2.3
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.
- package/package.json +5 -5
- package/src/components/docs-image.tsx +62 -62
- package/src/components/mdx-components.tsx +158 -158
- package/src/components/story-fields.tsx +318 -318
- package/src/components/story-panel.tsx +181 -181
- package/src/lib/md-plugins.ts +1 -0
- package/src/lib/mdx/directive-utils.ts +118 -118
- package/src/lib/pack-assets.ts +46 -46
- package/src/lib/story-remark.ts +468 -468
- package/src/lib/story.ts +11 -11
package/src/lib/story-remark.ts
CHANGED
|
@@ -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
|
+
|