@umami/shiso 1.11.0 → 1.13.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.
- package/bin/shiso.mjs +20 -1
- package/dist/chunks/App.js +412 -51
- package/dist/chunks/abnfDiagram-VCTEODGH.js +109 -0
- package/dist/chunks/arc.js +130 -0
- package/dist/chunks/architecture-7GRP2DOG.js +4 -0
- package/dist/chunks/architectureDiagram-5GKGNRK7.js +7466 -0
- package/dist/chunks/array.js +8 -0
- package/dist/chunks/blockDiagram-I7D4REHJ.js +2546 -0
- package/dist/chunks/c4Diagram-7LVT6UL2.js +3654 -0
- package/dist/chunks/channel.js +9 -0
- package/dist/chunks/chunk-2Q5K7J3B.js +21 -0
- package/dist/chunks/chunk-4HAMMTFA.js +6106 -0
- package/dist/chunks/chunk-5VM5RSS4.js +21 -0
- package/dist/chunks/chunk-75Z2AOVW.js +2185 -0
- package/dist/chunks/chunk-DU6HZSFF.js +8729 -0
- package/dist/chunks/chunk-F27PBJKO.js +95 -0
- package/dist/chunks/chunk-FOHPRMQF.js +27162 -0
- package/dist/chunks/chunk-GMAD6QVW.js +2369 -0
- package/dist/chunks/chunk-GVQU2GXP.js +58 -0
- package/dist/chunks/chunk-IMKFNOWR.js +2593 -0
- package/dist/chunks/chunk-JWPE2WC7.js +12 -0
- package/dist/chunks/chunk-L3NEJ4N5.js +420 -0
- package/dist/chunks/chunk-OSK3NFVY.js +1029 -0
- package/dist/chunks/chunk-P2QGCYS3.js +95 -0
- package/dist/chunks/chunk-POPQ4Y6H.js +33 -0
- package/dist/chunks/chunk-PWAF6VOD.js +467 -0
- package/dist/chunks/chunk-SHT3W25Y.js +4813 -0
- package/dist/chunks/chunk-SVP7TREG.js +686 -0
- package/dist/chunks/chunk-TICWLB2K.js +3800 -0
- package/dist/chunks/chunk-XXDRQBXY.js +12 -0
- package/dist/chunks/chunk-Y2CYZVJY.js +15 -0
- package/dist/chunks/classDiagram-ZZMXUADV.js +33 -0
- package/dist/chunks/classDiagram-v2-VYDZK3BY.js +33 -0
- package/dist/chunks/cose-bilkent-JH36ORCC.js +4268 -0
- package/dist/chunks/cynefin-OW5HDTMX.js +4 -0
- package/dist/chunks/cynefinDiagram-5FMLGOSQ.js +482 -0
- package/dist/chunks/cytoscape.esm.js +28282 -0
- package/dist/chunks/dagre-GXQ25YYZ.js +583 -0
- package/dist/chunks/dagre.js +4235 -0
- package/dist/chunks/defaultLocale.js +246 -0
- package/dist/chunks/diagram-S7CK7UJ4.js +402 -0
- package/dist/chunks/diagram-UQ7AKVKN.js +291 -0
- package/dist/chunks/diagram-VSXAHHWV.js +553 -0
- package/dist/chunks/diagram-VX7I27RA.js +798 -0
- package/dist/chunks/diagram-Z3DM3KII.js +186 -0
- package/dist/chunks/dist.js +92 -0
- package/dist/chunks/docs.js +989 -93
- package/dist/chunks/ebnfDiagram-PWID7BFC.js +123 -0
- package/dist/chunks/erDiagram-RLTQ6QDP.js +2295 -0
- package/dist/chunks/eventmodeling-NTZA5JFV.js +4 -0
- package/dist/chunks/flowDiagram-HODETNUW.js +17 -0
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +3928 -0
- package/dist/chunks/gitGraph-4MIJSDKK.js +4 -0
- package/dist/chunks/gitGraphDiagram-WWUBYQGX.js +1153 -0
- package/dist/chunks/graphlib.js +4194 -0
- package/dist/chunks/info-A6RAGUB7.js +4 -0
- package/dist/chunks/infoDiagram-27XIBGKW.js +25 -0
- package/dist/chunks/init.js +14 -0
- package/dist/chunks/ishikawaDiagram-5VMMS53U.js +971 -0
- package/dist/chunks/journeyDiagram-3NMN7TZE.js +1226 -0
- package/dist/chunks/kanban-definition-UXKFOSKX.js +1241 -0
- package/dist/chunks/katex.js +26463 -0
- package/dist/chunks/line.js +48 -0
- package/dist/chunks/linear.js +391 -0
- package/dist/chunks/mermaid-parser.core.js +754 -0
- package/dist/chunks/mermaid.core.js +4898 -0
- package/dist/chunks/mindmap-definition-YA3MSWOX.js +1309 -0
- package/dist/chunks/ordinal.js +84 -0
- package/dist/chunks/packet-AYTQ26CC.js +4 -0
- package/dist/chunks/path.js +106 -0
- package/dist/chunks/pegDiagram-XKGWAZYB.js +115 -0
- package/dist/chunks/pie-WAS4IAKB.js +4 -0
- package/dist/chunks/pieDiagram-E7YTZNPT.js +297 -0
- package/dist/chunks/quadrantDiagram-AXDQQJYC.js +2234 -0
- package/dist/chunks/radar-RG4KPBEZ.js +4 -0
- package/dist/chunks/railroad-74A4TZTK.js +4 -0
- package/dist/chunks/railroad-abnf-HS5TGJTU.js +4 -0
- package/dist/chunks/railroad-ebnf-LZEXJU2U.js +4 -0
- package/dist/chunks/railroad-peg-WCYAUIDC.js +4 -0
- package/dist/chunks/railroadDiagram-O6MQD6OU.js +89 -0
- package/dist/chunks/requirementDiagram-BXWQKSXE.js +2464 -0
- package/dist/chunks/rough.esm.js +1396 -0
- package/dist/chunks/sankeyDiagram-P5KCCOFB.js +1308 -0
- package/dist/chunks/sequenceDiagram-WJ2MYXX4.js +5445 -0
- package/dist/chunks/sizeCapture-INFHLROL.js +56 -0
- package/dist/chunks/src.js +2653 -0
- package/dist/chunks/stateDiagram-D77RDMKH.js +368 -0
- package/dist/chunks/stateDiagram-v2-MP3YSRHH.js +33 -0
- package/dist/chunks/swimlanes-42K2YHIH.js +6925 -0
- package/dist/chunks/swimlanesDiagram-VR7AAH4N.js +32 -0
- package/dist/chunks/timeline-definition-24CTP7MA.js +1507 -0
- package/dist/chunks/treeView-Q6P3EWNA.js +4 -0
- package/dist/chunks/treemap-WGGIJYW6.js +4 -0
- package/dist/chunks/vennDiagram-4TSXK5OY.js +2803 -0
- package/dist/chunks/wardley-WFR3VGLG.js +4 -0
- package/dist/chunks/wardleyDiagram-VM6X3IG4.js +877 -0
- package/dist/chunks/xychartDiagram-S5SC5T6Z.js +2698 -0
- package/dist/components.js +2 -2
- package/dist/entry-client.js +2 -2
- package/dist/entry-server.js +31 -3
- package/docs.schema.json +90 -0
- package/mdx.config.ts +13 -97
- package/package.json +6 -4
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-content.mjs +358 -0
- package/scripts/expand-navigation-globs.mjs +277 -0
- package/scripts/expand-openapi-navigation.mjs +94 -0
- package/scripts/generate-openapi.mjs +117 -0
- package/scripts/generate-search-index.mjs +31 -1
- package/scripts/lib/openapi.mjs +653 -0
- package/scripts/load-docs-config.mjs +52 -6
- package/scripts/load-shiso-config.mjs +45 -3
- package/scripts/prerender.mjs +83 -3
- package/scripts/vite-docs-config.mjs +27 -6
- package/src/App.tsx +0 -1
- package/src/components/CodeBlock.tsx +108 -8
- package/src/components/DocContent.tsx +18 -0
- package/src/components/Docs.tsx +45 -17
- package/src/components/OpenApiOperation.tsx +205 -0
- package/src/components/SideNav.tsx +13 -2
- package/src/components/docs/Changelog.tsx +163 -0
- package/src/components/docs/CodeGroup.tsx +6 -2
- package/src/components/docs/Mermaid.tsx +164 -0
- package/src/components/docs/Panel.tsx +23 -0
- package/src/components/docs/ParamField.tsx +17 -5
- package/src/components/docs/PropertiesTable.tsx +2 -2
- package/src/components/docs/ResponseField.tsx +17 -5
- package/src/components/docs/Tiles.tsx +59 -0
- package/src/components/docs/Tree.tsx +359 -0
- package/src/components/docs/Update.tsx +72 -0
- package/src/components/docs/index.ts +7 -0
- package/src/components/docs/panel-context.tsx +25 -0
- package/src/components/docs/styles.ts +49 -0
- package/src/components/icons/index.ts +4 -0
- package/src/entry-server.tsx +50 -0
- package/src/lib/code-blocks.ts +18 -0
- package/src/lib/code-meta.ts +87 -0
- package/src/lib/docs-config.ts +4 -1
- package/src/lib/openapi.generated.ts +4 -0
- package/src/lib/openapi.ts +63 -0
- package/src/lib/rehype-shiki.ts +213 -0
- package/src/lib/site-model.ts +2 -0
- package/src/lib/types.ts +116 -2
- package/src/styles/global.css +63 -73
- package/types/components.d.ts +79 -0
- package/types/config.d.ts +11 -4
- package/vite.config.ts +49 -3
- package/CHANGELOG.md +0 -171
package/src/entry-server.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getLocaleByPathname,
|
|
12
12
|
getRedirects,
|
|
13
13
|
getSeo,
|
|
14
|
+
siteConfig,
|
|
14
15
|
siteName,
|
|
15
16
|
standalonePages,
|
|
16
17
|
} from '@/lib/site-config';
|
|
@@ -27,6 +28,13 @@ export interface SitemapEntry {
|
|
|
27
28
|
lastmod?: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export interface LlmsPage {
|
|
32
|
+
route: string;
|
|
33
|
+
filePath: string;
|
|
34
|
+
title: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/** Base-relative routes for every scope. The prerenderer prepends the deploy base itself. */
|
|
31
39
|
export function getRoutes(): string[] {
|
|
32
40
|
return [...docsSite.pages.map(page => page.url), ...standalonePages.map(page => page.path)];
|
|
@@ -51,6 +59,46 @@ export function getMarkdownPages(): { route: string; filePath: string }[] {
|
|
|
51
59
|
];
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
/** Navigable Markdown pages used to generate llms.txt and llms-full.txt. */
|
|
63
|
+
export function getLlmsPages(): LlmsPage[] {
|
|
64
|
+
const { indexing } = getSeo();
|
|
65
|
+
const pages: LlmsPage[] = [];
|
|
66
|
+
|
|
67
|
+
for (const page of docsSite.pages) {
|
|
68
|
+
const doc = getDocModule(page.filePath);
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
doc?.frontmatter?.noindex === true ||
|
|
72
|
+
((page.hidden || getScopeForPage(docsSite, page).hidden) && indexing !== 'all')
|
|
73
|
+
) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pages.push({
|
|
78
|
+
route: page.url,
|
|
79
|
+
filePath: page.filePath,
|
|
80
|
+
title: doc?.frontmatter?.title || page.label,
|
|
81
|
+
description: doc?.frontmatter?.description,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const page of standalonePages) {
|
|
86
|
+
if (page.filePath.endsWith('.tsx')) continue;
|
|
87
|
+
|
|
88
|
+
const doc = getDocModule(page.filePath);
|
|
89
|
+
if (doc?.frontmatter?.noindex === true) continue;
|
|
90
|
+
|
|
91
|
+
pages.push({
|
|
92
|
+
route: page.path,
|
|
93
|
+
filePath: page.filePath,
|
|
94
|
+
title: doc?.frontmatter?.title || page.title || page.path,
|
|
95
|
+
description: doc?.frontmatter?.description,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return pages;
|
|
100
|
+
}
|
|
101
|
+
|
|
54
102
|
/**
|
|
55
103
|
* Absolute URLs for the sitemap, honoring `seo.indexing` and per-page
|
|
56
104
|
* noindex. Empty when the shiso.config `siteUrl` is not configured, since a sitemap
|
|
@@ -106,4 +154,6 @@ export function render(url: string): RenderResult {
|
|
|
106
154
|
return { html, head: renderHeadToString(buildHead(url)), htmlAttrs: getLocaleByPathname(url) };
|
|
107
155
|
}
|
|
108
156
|
|
|
157
|
+
export const siteDescription = siteConfig.description;
|
|
158
|
+
|
|
109
159
|
export { docsHomeUrl, siteName };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Relative import: this module is also loaded by vite.config.ts, which esbuild
|
|
2
|
+
// bundles without applying the '@/' resolve alias.
|
|
3
|
+
import type { ResolvedCodeBlockConfig, StylingConfig } from './types.ts';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CODE_THEME = { light: 'github-light', dark: 'github-dark' } as const;
|
|
6
|
+
|
|
7
|
+
/** Applies defaults to `styling.codeBlocks` from docs.json. */
|
|
8
|
+
export function resolveCodeBlockConfig(styling?: StylingConfig): ResolvedCodeBlockConfig {
|
|
9
|
+
const codeBlocks = styling?.codeBlocks;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
lineNumbers: codeBlocks?.lineNumbers === true,
|
|
13
|
+
theme: {
|
|
14
|
+
light: codeBlocks?.theme?.light || DEFAULT_CODE_THEME.light,
|
|
15
|
+
dark: codeBlocks?.theme?.dark || DEFAULT_CODE_THEME.dark,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses the fenced code meta string (the text after the language tag) into
|
|
3
|
+
* the directives Shiso understands:
|
|
4
|
+
*
|
|
5
|
+
* ```ts title="app.ts" {1,3-5} showLineNumbers=10
|
|
6
|
+
*
|
|
7
|
+
* Anything unrecognized becomes the title, so the bare label form used by
|
|
8
|
+
* `CodeGroup` (```bash npm) keeps working.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface CodeMeta {
|
|
12
|
+
title?: string;
|
|
13
|
+
highlightLines: number[];
|
|
14
|
+
showLineNumbers?: boolean;
|
|
15
|
+
startLine?: number;
|
|
16
|
+
/** Unrecognized tokens, in order. */
|
|
17
|
+
rest: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Splits on whitespace while keeping quoted segments (title="my file.ts") intact. */
|
|
21
|
+
function tokenize(meta: string): string[] {
|
|
22
|
+
return meta.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseLineList(list: string): number[] {
|
|
26
|
+
const lines = new Set<number>();
|
|
27
|
+
|
|
28
|
+
for (const part of list.split(',')) {
|
|
29
|
+
const range = part.trim();
|
|
30
|
+
if (!range) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [from, to = from] = range.split('-').map(Number);
|
|
35
|
+
if (!Number.isInteger(from) || from < 1 || !Number.isInteger(to)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (let line = from; line <= to; line += 1) {
|
|
40
|
+
lines.add(line);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return [...lines].sort((a, b) => a - b);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function unquote(value: string): string {
|
|
48
|
+
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseCodeMeta(meta: string | undefined): CodeMeta {
|
|
52
|
+
const result: CodeMeta = { highlightLines: [], rest: [] };
|
|
53
|
+
const lines = new Set<number>();
|
|
54
|
+
|
|
55
|
+
for (const token of tokenize((meta || '').trim())) {
|
|
56
|
+
const highlight = token.match(/^\{([\d,\s-]+)\}$/);
|
|
57
|
+
const title = token.match(/^title=(.+)$/);
|
|
58
|
+
const numbered = token.match(/^showLineNumbers(?:=(\d+))?$/);
|
|
59
|
+
|
|
60
|
+
if (highlight) {
|
|
61
|
+
for (const line of parseLineList(highlight[1])) {
|
|
62
|
+
lines.add(line);
|
|
63
|
+
}
|
|
64
|
+
} else if (title) {
|
|
65
|
+
result.title = unquote(title[1]);
|
|
66
|
+
} else if (numbered) {
|
|
67
|
+
result.showLineNumbers = true;
|
|
68
|
+
if (numbered[1]) {
|
|
69
|
+
result.startLine = Number(numbered[1]);
|
|
70
|
+
}
|
|
71
|
+
} else if (token === 'hideLineNumbers') {
|
|
72
|
+
result.showLineNumbers = false;
|
|
73
|
+
} else {
|
|
74
|
+
result.rest.push(unquote(token));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
result.highlightLines = [...lines].sort((a, b) => a - b);
|
|
79
|
+
|
|
80
|
+
// Back-compat: a bare label (```bash npm) is the title, which CodeGroup uses
|
|
81
|
+
// as the tab name.
|
|
82
|
+
if (!result.title && result.rest.length) {
|
|
83
|
+
result.title = result.rest.join(' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
package/src/lib/docs-config.ts
CHANGED
|
@@ -391,6 +391,7 @@ interface PendingPage {
|
|
|
391
391
|
hidden?: boolean;
|
|
392
392
|
icon?: string;
|
|
393
393
|
tag?: string;
|
|
394
|
+
method?: string;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
type PendingNode =
|
|
@@ -424,7 +425,7 @@ function addPage(
|
|
|
424
425
|
pageRef: string,
|
|
425
426
|
context: WalkContext,
|
|
426
427
|
state: WalkState,
|
|
427
|
-
extra: { label?: string; icon?: string; tag?: string; hidden?: boolean } = {},
|
|
428
|
+
extra: { label?: string; icon?: string; tag?: string; method?: string; hidden?: boolean } = {},
|
|
428
429
|
): number {
|
|
429
430
|
const { fileSlug, slug } = normalizePageReference(pageRef);
|
|
430
431
|
const order = state.order.value++;
|
|
@@ -440,6 +441,7 @@ function addPage(
|
|
|
440
441
|
hidden: extra.hidden || context.hidden || undefined,
|
|
441
442
|
icon: extra.icon,
|
|
442
443
|
tag: extra.tag,
|
|
444
|
+
method: extra.method,
|
|
443
445
|
});
|
|
444
446
|
|
|
445
447
|
return order;
|
|
@@ -497,6 +499,7 @@ function collectPages(items: PageItem[], context: WalkContext, state: WalkState)
|
|
|
497
499
|
label,
|
|
498
500
|
icon: typeof item.icon === 'string' ? item.icon : undefined,
|
|
499
501
|
tag: typeof item.tag === 'string' ? item.tag : undefined,
|
|
502
|
+
method: typeof item.method === 'string' ? item.method : undefined,
|
|
500
503
|
hidden: item.hidden === true,
|
|
501
504
|
}),
|
|
502
505
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime helpers for OpenAPI reference pages. Operation data is produced at
|
|
3
|
+
* build time by scripts/generate-openapi.mjs and reaches the client through
|
|
4
|
+
* the aliased openapi.generated module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BadgeColor } from '@/components/docs/Badge';
|
|
8
|
+
import { OPENAPI_OPERATIONS } from '@/lib/openapi.generated';
|
|
9
|
+
import { createSlugger } from '@/lib/slug';
|
|
10
|
+
import type { NormalizedOperation, TocEntry } from '@/lib/types';
|
|
11
|
+
|
|
12
|
+
export const METHOD_COLORS: Record<string, BadgeColor> = {
|
|
13
|
+
GET: 'green',
|
|
14
|
+
POST: 'blue',
|
|
15
|
+
PUT: 'orange',
|
|
16
|
+
PATCH: 'purple',
|
|
17
|
+
DELETE: 'red',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function methodColor(method?: string): BadgeColor {
|
|
21
|
+
return (method && METHOD_COLORS[method.toUpperCase()]) || 'gray';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function statusColor(status: string): BadgeColor {
|
|
25
|
+
if (status.startsWith('2')) return 'green';
|
|
26
|
+
if (status.startsWith('3')) return 'blue';
|
|
27
|
+
if (status.startsWith('4') || status.startsWith('5')) return 'red';
|
|
28
|
+
return 'gray';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Looks up the operation bound by an `openapi:` frontmatter value. */
|
|
32
|
+
export function getOperation(key?: unknown): NormalizedOperation | undefined {
|
|
33
|
+
if (typeof key !== 'string' || !key.trim()) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [method, ...rest] = key.trim().split(/\s+/);
|
|
38
|
+
return OPENAPI_OPERATIONS[`${method.toUpperCase()} ${rest.join(' ')}`];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hasParameters(operation: NormalizedOperation): boolean {
|
|
42
|
+
const { query, path, header, cookie } = operation.parameters;
|
|
43
|
+
return (
|
|
44
|
+
query.length + path.length + header.length + cookie.length > 0 || operation.security.length > 0
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Section headings for an operation, in render order. The single source of
|
|
50
|
+
* truth for section ids: the component, the table of contents, the content
|
|
51
|
+
* checker, and the search indexer all derive their anchors from these labels.
|
|
52
|
+
*/
|
|
53
|
+
export function operationSections(operation: NormalizedOperation): TocEntry[] {
|
|
54
|
+
const slugger = createSlugger();
|
|
55
|
+
const names = [
|
|
56
|
+
hasParameters(operation) ? 'Parameters' : undefined,
|
|
57
|
+
operation.requestBody ? 'Request body' : undefined,
|
|
58
|
+
operation.responses.length ? 'Responses' : undefined,
|
|
59
|
+
operation.samples.length ? 'Code samples' : undefined,
|
|
60
|
+
].filter((name): name is string => Boolean(name));
|
|
61
|
+
|
|
62
|
+
return names.map(name => ({ name, id: slugger.slug(name), size: 2 }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Highlights fenced code blocks with Shiki at build time.
|
|
3
|
+
*
|
|
4
|
+
* Output contract (consumed by `components/CodeBlock.tsx` and global.css):
|
|
5
|
+
* - `<pre>` carries `data-language`, and when present `data-title`,
|
|
6
|
+
* `data-line-numbers`, `data-line-start`, `data-line-count`, `data-diff-markers`.
|
|
7
|
+
* - `<code class="language-x">` keeps its class; its children are Shiki's
|
|
8
|
+
* `<span class="line">` elements, each optionally marked `data-highlighted`
|
|
9
|
+
* or `data-diff="add|remove"`.
|
|
10
|
+
* - Token spans carry only `--shiki-light` / `--shiki-dark` variables so the
|
|
11
|
+
* markup is identical in both color modes and CSS picks the theme.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { transformerNotationDiff, transformerNotationHighlight } from '@shikijs/transformers';
|
|
15
|
+
import {
|
|
16
|
+
bundledLanguages,
|
|
17
|
+
bundledThemes,
|
|
18
|
+
createHighlighter,
|
|
19
|
+
type Highlighter,
|
|
20
|
+
type ShikiTransformer,
|
|
21
|
+
} from 'shiki';
|
|
22
|
+
// Relative imports: this module is also loaded by vite.config.ts, which esbuild
|
|
23
|
+
// bundles without applying the '@/' resolve alias.
|
|
24
|
+
import { parseCodeMeta } from './code-meta.ts';
|
|
25
|
+
import { type MdNode, toText, walkTree } from './mdast.ts';
|
|
26
|
+
import type { ResolvedCodeBlockConfig } from './types.ts';
|
|
27
|
+
|
|
28
|
+
type Properties = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
const highlighters = new Map<string, Promise<Highlighter>>();
|
|
31
|
+
const languageLoads = new Map<string, Promise<void>>();
|
|
32
|
+
const warnedLanguages = new Set<string>();
|
|
33
|
+
|
|
34
|
+
function assertTheme(name: string, key: 'light' | 'dark') {
|
|
35
|
+
if (!(name in bundledThemes)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[shiso] Unknown code theme "${name}" in docs.json styling.codeBlocks.theme.${key}. ` +
|
|
38
|
+
'Use a bundled Shiki theme name (https://shiki.style/themes).',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getHighlighter(theme: ResolvedCodeBlockConfig['theme']): Promise<Highlighter> {
|
|
44
|
+
const key = `${theme.light}|${theme.dark}`;
|
|
45
|
+
let highlighter = highlighters.get(key);
|
|
46
|
+
|
|
47
|
+
if (!highlighter) {
|
|
48
|
+
assertTheme(theme.light, 'light');
|
|
49
|
+
assertTheme(theme.dark, 'dark');
|
|
50
|
+
highlighter = createHighlighter({ themes: [theme.light, theme.dark], langs: [] });
|
|
51
|
+
highlighters.set(key, highlighter);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return highlighter;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function ensureLanguage(highlighter: Highlighter, lang: string) {
|
|
58
|
+
if (highlighter.getLoadedLanguages().includes(lang)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let load = languageLoads.get(lang);
|
|
63
|
+
if (!load) {
|
|
64
|
+
load = highlighter.loadLanguage(lang as keyof typeof bundledLanguages);
|
|
65
|
+
languageLoads.set(lang, load);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await load;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveLanguage(requested: string | undefined): string {
|
|
72
|
+
if (!requested || requested === 'text' || requested === 'plaintext' || requested === 'txt') {
|
|
73
|
+
return 'text';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (requested in bundledLanguages) {
|
|
77
|
+
return requested;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!warnedLanguages.has(requested)) {
|
|
81
|
+
warnedLanguages.add(requested);
|
|
82
|
+
console.warn(`[shiso] No syntax grammar for "${requested}"; rendering it as plain text.`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return 'text';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Marks meta `{1,3-5}` lines and, for `diff` blocks, the added/removed lines. */
|
|
89
|
+
function shisoLineMarks(highlight: Set<number>, diffLanguage: boolean): ShikiTransformer {
|
|
90
|
+
return {
|
|
91
|
+
name: 'shiso:line-marks',
|
|
92
|
+
line(node, line) {
|
|
93
|
+
if (highlight.has(line)) {
|
|
94
|
+
node.properties['data-highlighted'] = '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (diffLanguage) {
|
|
98
|
+
const text = toText(node as unknown as MdNode);
|
|
99
|
+
if (/^\+(?!\+\+)/.test(text)) {
|
|
100
|
+
node.properties['data-diff'] = 'add';
|
|
101
|
+
} else if (/^-(?!--)/.test(text)) {
|
|
102
|
+
node.properties['data-diff'] = 'remove';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Converts transformer classes (`highlighted`, `diff add|remove`) into data attributes. */
|
|
110
|
+
function normalizeLine(line: MdNode) {
|
|
111
|
+
const properties = (line.properties || {}) as Properties;
|
|
112
|
+
// Shiki transformers write `class`; mdast-util-to-hast writes `className`.
|
|
113
|
+
const classes = [properties.class, properties.className]
|
|
114
|
+
.flatMap(value => (Array.isArray(value) ? value : String(value || '').split(' ')))
|
|
115
|
+
.map(String)
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
|
|
118
|
+
if (classes.includes('highlighted')) {
|
|
119
|
+
properties['data-highlighted'] = '';
|
|
120
|
+
}
|
|
121
|
+
if (classes.includes('diff')) {
|
|
122
|
+
properties['data-diff'] = classes.includes('remove') ? 'remove' : 'add';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
delete properties.class;
|
|
126
|
+
properties.className = ['line'];
|
|
127
|
+
line.properties = properties;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function highlightBlock(pre: MdNode, code: MdNode, config: ResolvedCodeBlockConfig) {
|
|
131
|
+
const codeProperties = (code.properties || {}) as Properties;
|
|
132
|
+
const classes = Array.isArray(codeProperties.className)
|
|
133
|
+
? codeProperties.className.map(String)
|
|
134
|
+
: [];
|
|
135
|
+
const requested = classes.map(value => value.match(/^language-(\S+)$/)?.[1]).find(Boolean);
|
|
136
|
+
const meta = parseCodeMeta(typeof code.data?.meta === 'string' ? code.data.meta : '');
|
|
137
|
+
|
|
138
|
+
// Mermaid diagrams render client-side via the <Mermaid> component; keep the
|
|
139
|
+
// raw definition so CodeBlock can hand it over untouched.
|
|
140
|
+
if (requested === 'mermaid') {
|
|
141
|
+
const metaRaw = typeof code.data?.meta === 'string' ? code.data.meta : '';
|
|
142
|
+
const placement = metaRaw.match(/placement=["'](top-left|top-right|bottom-left|bottom-right)["']/)?.[1];
|
|
143
|
+
const actions = metaRaw.match(/actions=\{(true|false)\}/)?.[1];
|
|
144
|
+
pre.properties = {
|
|
145
|
+
...(pre.properties as Properties),
|
|
146
|
+
'data-language': 'mermaid',
|
|
147
|
+
...(meta.title ? { 'data-title': meta.title } : {}),
|
|
148
|
+
...(placement ? { 'data-placement': placement } : {}),
|
|
149
|
+
...(actions ? { 'data-actions': actions } : {}),
|
|
150
|
+
};
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const lang = resolveLanguage(requested);
|
|
155
|
+
// mdast-util-to-hast appends a trailing newline to the code text.
|
|
156
|
+
const source = toText(code).replace(/\n$/, '');
|
|
157
|
+
|
|
158
|
+
const highlighter = await getHighlighter(config.theme);
|
|
159
|
+
if (lang !== 'text') {
|
|
160
|
+
await ensureLanguage(highlighter, lang);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const hast = highlighter.codeToHast(source, {
|
|
164
|
+
lang,
|
|
165
|
+
themes: config.theme,
|
|
166
|
+
defaultColor: false,
|
|
167
|
+
transformers: [
|
|
168
|
+
transformerNotationHighlight({ matchAlgorithm: 'v3' }),
|
|
169
|
+
transformerNotationDiff({ matchAlgorithm: 'v3' }),
|
|
170
|
+
shisoLineMarks(new Set(meta.highlightLines), lang === 'diff'),
|
|
171
|
+
],
|
|
172
|
+
}) as MdNode;
|
|
173
|
+
|
|
174
|
+
// root > pre > code
|
|
175
|
+
const shikiPre = hast.children?.[0] as MdNode;
|
|
176
|
+
const shikiCode = shikiPre?.children?.[0] as MdNode;
|
|
177
|
+
const lines = (shikiCode?.children || []).filter(node => node.tagName === 'span');
|
|
178
|
+
lines.forEach(normalizeLine);
|
|
179
|
+
|
|
180
|
+
const hasNotationDiff =
|
|
181
|
+
lang !== 'diff' && lines.some(line => (line.properties as Properties)['data-diff']);
|
|
182
|
+
const showLineNumbers = meta.showLineNumbers ?? config.lineNumbers;
|
|
183
|
+
|
|
184
|
+
code.children = shikiCode?.children || [];
|
|
185
|
+
pre.properties = {
|
|
186
|
+
...(pre.properties as Properties),
|
|
187
|
+
'data-language': lang,
|
|
188
|
+
...(meta.title ? { 'data-title': meta.title } : {}),
|
|
189
|
+
...(showLineNumbers ? { 'data-line-numbers': 'true' } : {}),
|
|
190
|
+
...(meta.startLine ? { 'data-line-start': String(meta.startLine) } : {}),
|
|
191
|
+
'data-line-count': String(lines.length),
|
|
192
|
+
...(hasNotationDiff ? { 'data-diff-markers': 'true' } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function rehypeShiki(config: ResolvedCodeBlockConfig) {
|
|
197
|
+
return async (tree: MdNode) => {
|
|
198
|
+
const tasks: Promise<void>[] = [];
|
|
199
|
+
|
|
200
|
+
walkTree(tree, pre => {
|
|
201
|
+
if (pre.tagName !== 'pre') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const code = pre.children?.find(child => child.tagName === 'code');
|
|
206
|
+
if (code) {
|
|
207
|
+
tasks.push(highlightBlock(pre, code, config));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await Promise.all(tasks);
|
|
212
|
+
};
|
|
213
|
+
}
|
package/src/lib/site-model.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveCodeBlockConfig } from '@/lib/code-blocks';
|
|
1
2
|
import { toAbsoluteUrl, toHref } from '@/lib/paths';
|
|
2
3
|
import { resolveSearchConfig } from '@/lib/search/config';
|
|
3
4
|
import type {
|
|
@@ -130,6 +131,7 @@ export function resolveSiteModel(
|
|
|
130
131
|
},
|
|
131
132
|
styling: {
|
|
132
133
|
eyebrows: config.styling?.eyebrows === 'breadcrumbs' ? 'breadcrumbs' : 'section',
|
|
134
|
+
codeBlocks: resolveCodeBlockConfig(config.styling),
|
|
133
135
|
},
|
|
134
136
|
search: resolveSearchConfig(config.search),
|
|
135
137
|
contextualOptions: config.contextual?.options || [],
|