@umami/shiso 1.11.0 → 1.12.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 +360 -26
- package/dist/chunks/docs.js +2 -2
- package/dist/entry-client.js +1 -1
- package/dist/entry-server.js +30 -2
- package/docs.schema.json +90 -0
- package/mdx.config.ts +13 -97
- package/package.json +5 -4
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-content.mjs +358 -0
- package/scripts/expand-navigation-globs.mjs +208 -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 +72 -8
- package/src/components/DocContent.tsx +18 -0
- package/src/components/Docs.tsx +6 -1
- package/src/components/OpenApiOperation.tsx +197 -0
- package/src/components/SideNav.tsx +8 -2
- package/src/components/docs/CodeGroup.tsx +6 -2
- 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 +196 -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/config.d.ts +11 -4
- package/vite.config.ts +49 -3
- package/CHANGELOG.md +0 -171
|
@@ -0,0 +1,196 @@
|
|
|
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 lang = resolveLanguage(requested);
|
|
137
|
+
const meta = parseCodeMeta(typeof code.data?.meta === 'string' ? code.data.meta : '');
|
|
138
|
+
// mdast-util-to-hast appends a trailing newline to the code text.
|
|
139
|
+
const source = toText(code).replace(/\n$/, '');
|
|
140
|
+
|
|
141
|
+
const highlighter = await getHighlighter(config.theme);
|
|
142
|
+
if (lang !== 'text') {
|
|
143
|
+
await ensureLanguage(highlighter, lang);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const hast = highlighter.codeToHast(source, {
|
|
147
|
+
lang,
|
|
148
|
+
themes: config.theme,
|
|
149
|
+
defaultColor: false,
|
|
150
|
+
transformers: [
|
|
151
|
+
transformerNotationHighlight({ matchAlgorithm: 'v3' }),
|
|
152
|
+
transformerNotationDiff({ matchAlgorithm: 'v3' }),
|
|
153
|
+
shisoLineMarks(new Set(meta.highlightLines), lang === 'diff'),
|
|
154
|
+
],
|
|
155
|
+
}) as MdNode;
|
|
156
|
+
|
|
157
|
+
// root > pre > code
|
|
158
|
+
const shikiPre = hast.children?.[0] as MdNode;
|
|
159
|
+
const shikiCode = shikiPre?.children?.[0] as MdNode;
|
|
160
|
+
const lines = (shikiCode?.children || []).filter(node => node.tagName === 'span');
|
|
161
|
+
lines.forEach(normalizeLine);
|
|
162
|
+
|
|
163
|
+
const hasNotationDiff =
|
|
164
|
+
lang !== 'diff' && lines.some(line => (line.properties as Properties)['data-diff']);
|
|
165
|
+
const showLineNumbers = meta.showLineNumbers ?? config.lineNumbers;
|
|
166
|
+
|
|
167
|
+
code.children = shikiCode?.children || [];
|
|
168
|
+
pre.properties = {
|
|
169
|
+
...(pre.properties as Properties),
|
|
170
|
+
'data-language': lang,
|
|
171
|
+
...(meta.title ? { 'data-title': meta.title } : {}),
|
|
172
|
+
...(showLineNumbers ? { 'data-line-numbers': 'true' } : {}),
|
|
173
|
+
...(meta.startLine ? { 'data-line-start': String(meta.startLine) } : {}),
|
|
174
|
+
'data-line-count': String(lines.length),
|
|
175
|
+
...(hasNotationDiff ? { 'data-diff-markers': 'true' } : {}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function rehypeShiki(config: ResolvedCodeBlockConfig) {
|
|
180
|
+
return async (tree: MdNode) => {
|
|
181
|
+
const tasks: Promise<void>[] = [];
|
|
182
|
+
|
|
183
|
+
walkTree(tree, pre => {
|
|
184
|
+
if (pre.tagName !== 'pre') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const code = pre.children?.find(child => child.tagName === 'code');
|
|
189
|
+
if (code) {
|
|
190
|
+
tasks.push(highlightBlock(pre, code, config));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await Promise.all(tasks);
|
|
195
|
+
};
|
|
196
|
+
}
|
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 || [],
|
package/src/lib/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PluggableList } from 'unified';
|
|
2
|
+
|
|
1
3
|
/* ---------------------------------------------------------------------------
|
|
2
4
|
* Raw docs.json shapes
|
|
3
5
|
* ------------------------------------------------------------------------- */
|
|
@@ -8,6 +10,8 @@ export interface PageObjectItem {
|
|
|
8
10
|
label?: string;
|
|
9
11
|
icon?: string;
|
|
10
12
|
tag?: string;
|
|
13
|
+
/** HTTP method badge shown in the sidebar, set by OpenAPI navigation entries. */
|
|
14
|
+
method?: string;
|
|
11
15
|
hidden?: boolean;
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -21,7 +25,12 @@ export interface LinkItem {
|
|
|
21
25
|
target?: LinkTarget;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
export
|
|
28
|
+
export interface GlobItem {
|
|
29
|
+
glob: string;
|
|
30
|
+
exclude?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type PageItem = string | GroupItem | PageObjectItem | LinkItem | GlobItem;
|
|
25
34
|
|
|
26
35
|
export interface GroupItem {
|
|
27
36
|
group: string;
|
|
@@ -113,6 +122,11 @@ export type LogoOption =
|
|
|
113
122
|
|
|
114
123
|
/** Project-level settings supplied by shiso.config.ts. Mirrors the public
|
|
115
124
|
* shape exported from "@umami/shiso/config". */
|
|
125
|
+
export interface MdxConfig {
|
|
126
|
+
remarkPlugins?: PluggableList;
|
|
127
|
+
rehypePlugins?: PluggableList;
|
|
128
|
+
}
|
|
129
|
+
|
|
116
130
|
export interface ShisoConfig {
|
|
117
131
|
/** Where docs pages are mounted within the site. Default "/docs"; "" for root. */
|
|
118
132
|
docsPrefix?: string;
|
|
@@ -122,6 +136,8 @@ export interface ShisoConfig {
|
|
|
122
136
|
siteUrl?: string;
|
|
123
137
|
/** Locale used for deterministic date formatting. */
|
|
124
138
|
locale?: string;
|
|
139
|
+
/** Build-time remark and rehype plugins. */
|
|
140
|
+
mdx?: MdxConfig;
|
|
125
141
|
}
|
|
126
142
|
|
|
127
143
|
/** ShisoConfig after defaults and normalization, as served by `virtual:shiso-config`. */
|
|
@@ -130,6 +146,8 @@ export interface ResolvedShisoConfig {
|
|
|
130
146
|
contentDir: string;
|
|
131
147
|
siteUrl?: string;
|
|
132
148
|
locale: string;
|
|
149
|
+
/** Build-only compiler hooks. Removed from the virtual browser module. */
|
|
150
|
+
mdx?: MdxConfig;
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
export type LinkTarget = '_self' | '_blank';
|
|
@@ -236,9 +254,26 @@ export interface AppearanceConfig {
|
|
|
236
254
|
strict?: boolean;
|
|
237
255
|
}
|
|
238
256
|
|
|
257
|
+
export interface CodeBlockConfig {
|
|
258
|
+
/** Show line numbers on every block. Override per block with `showLineNumbers` or `hideLineNumbers`. */
|
|
259
|
+
lineNumbers?: boolean;
|
|
260
|
+
/** Bundled Shiki theme names for each color mode. */
|
|
261
|
+
theme?: {
|
|
262
|
+
light?: string;
|
|
263
|
+
dark?: string;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
239
267
|
export interface StylingConfig {
|
|
240
268
|
/** Page eyebrow style: the section name (default) or the full breadcrumb path. */
|
|
241
269
|
eyebrows?: 'section' | 'breadcrumbs';
|
|
270
|
+
/** Fenced code block defaults. */
|
|
271
|
+
codeBlocks?: CodeBlockConfig;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface ResolvedCodeBlockConfig {
|
|
275
|
+
lineNumbers: boolean;
|
|
276
|
+
theme: { light: string; dark: string };
|
|
242
277
|
}
|
|
243
278
|
|
|
244
279
|
export interface FontSpec {
|
|
@@ -345,6 +380,15 @@ export interface DocsConfig {
|
|
|
345
380
|
search?: false | SearchConfig;
|
|
346
381
|
interaction?: InteractionConfig;
|
|
347
382
|
contextual?: ContextualConfig;
|
|
383
|
+
/** API reference settings: the OpenAPI spec powering `openapi:` frontmatter pages. */
|
|
384
|
+
api?: ApiConfig;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export interface ApiConfig {
|
|
388
|
+
/** Path to a local OpenAPI 3.x spec (JSON or YAML), relative to the project root. */
|
|
389
|
+
spec: string;
|
|
390
|
+
/** Folder inside the content directory for generated endpoint pages. Default "api-reference". */
|
|
391
|
+
directory?: string;
|
|
348
392
|
}
|
|
349
393
|
|
|
350
394
|
/* ---------------------------------------------------------------------------
|
|
@@ -387,6 +431,8 @@ export interface NormalizedDocsPage {
|
|
|
387
431
|
tag?: string;
|
|
388
432
|
/** Module key of the MDX file, e.g. "/content/docs/installation.mdx". */
|
|
389
433
|
filePath: string;
|
|
434
|
+
/** HTTP method badge for API reference pages. */
|
|
435
|
+
method?: string;
|
|
390
436
|
}
|
|
391
437
|
|
|
392
438
|
/** A routed docs page in the navigation tree. */
|
|
@@ -530,7 +576,10 @@ export interface SiteModel {
|
|
|
530
576
|
footer: NormalizedFooter | null;
|
|
531
577
|
banner: BannerConfig | null;
|
|
532
578
|
appearance: Required<AppearanceConfig>;
|
|
533
|
-
styling:
|
|
579
|
+
styling: {
|
|
580
|
+
eyebrows: 'section' | 'breadcrumbs';
|
|
581
|
+
codeBlocks: ResolvedCodeBlockConfig;
|
|
582
|
+
};
|
|
534
583
|
search: import('@/lib/search/config').ResolvedSearchConfig;
|
|
535
584
|
contextualOptions: ContextualOption[];
|
|
536
585
|
error404: Required<Pick<Error404Config, 'redirect'>> & Error404Config;
|
|
@@ -574,6 +623,8 @@ export interface DocFrontmatter {
|
|
|
574
623
|
timestamp?: boolean;
|
|
575
624
|
/** Related pages rendered above the prev/next pager. */
|
|
576
625
|
related?: RelatedEntry[];
|
|
626
|
+
/** Binds the page to an API operation, e.g. "GET /users/{id}". */
|
|
627
|
+
openapi?: string;
|
|
577
628
|
[key: string]: unknown;
|
|
578
629
|
}
|
|
579
630
|
|
|
@@ -582,3 +633,66 @@ export interface DocModule {
|
|
|
582
633
|
frontmatter?: DocFrontmatter;
|
|
583
634
|
toc?: TocEntry[];
|
|
584
635
|
}
|
|
636
|
+
|
|
637
|
+
/* ---------------------------------------------------------------------------
|
|
638
|
+
* OpenAPI reference shapes (produced by scripts/lib/openapi.mjs)
|
|
639
|
+
* ------------------------------------------------------------------------- */
|
|
640
|
+
|
|
641
|
+
export interface SchemaNode {
|
|
642
|
+
name?: string;
|
|
643
|
+
/** Display label such as "string", "User[]", "enum<string>", or "oneOf". */
|
|
644
|
+
type: string;
|
|
645
|
+
required?: boolean;
|
|
646
|
+
deprecated?: boolean;
|
|
647
|
+
description?: string;
|
|
648
|
+
default?: string;
|
|
649
|
+
enum?: string[];
|
|
650
|
+
children?: SchemaNode[];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export interface OpenApiSample {
|
|
654
|
+
language: string;
|
|
655
|
+
label: string;
|
|
656
|
+
source: string;
|
|
657
|
+
/** Shiki-highlighted markup generated at build time. */
|
|
658
|
+
html?: string;
|
|
659
|
+
lineCount?: number;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export interface OpenApiResponse {
|
|
663
|
+
status: string;
|
|
664
|
+
description?: string;
|
|
665
|
+
contentType?: string;
|
|
666
|
+
schema?: SchemaNode;
|
|
667
|
+
example?: string;
|
|
668
|
+
exampleHtml?: string;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export interface NormalizedOperation {
|
|
672
|
+
id: string;
|
|
673
|
+
/** Frontmatter lookup key, e.g. "GET /users/{id}". */
|
|
674
|
+
key: string;
|
|
675
|
+
method: string;
|
|
676
|
+
path: string;
|
|
677
|
+
summary?: string;
|
|
678
|
+
description?: string;
|
|
679
|
+
tags: string[];
|
|
680
|
+
deprecated?: boolean;
|
|
681
|
+
parameters: {
|
|
682
|
+
query: SchemaNode[];
|
|
683
|
+
path: SchemaNode[];
|
|
684
|
+
header: SchemaNode[];
|
|
685
|
+
cookie: SchemaNode[];
|
|
686
|
+
};
|
|
687
|
+
requestBody?: {
|
|
688
|
+
required?: boolean;
|
|
689
|
+
contentType: string;
|
|
690
|
+
schema: SchemaNode;
|
|
691
|
+
example?: string;
|
|
692
|
+
exampleHtml?: string;
|
|
693
|
+
};
|
|
694
|
+
responses: OpenApiResponse[];
|
|
695
|
+
security: string[];
|
|
696
|
+
serverUrl: string;
|
|
697
|
+
samples: OpenApiSample[];
|
|
698
|
+
}
|
package/src/styles/global.css
CHANGED
|
@@ -256,88 +256,78 @@
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/* GitHub Dark syntax colors, scoped so the default GitHub theme stays light. */
|
|
272
|
-
[data-theme="dark"] .hljs {
|
|
273
|
-
color: #c9d1d9;
|
|
274
|
-
}
|
|
259
|
+
@layer components {
|
|
260
|
+
/* Shiki output: one inline-block per line so highlight and diff bands span the
|
|
261
|
+
full scroll width. Line numbers and diff markers are pseudo-elements, which
|
|
262
|
+
keeps them out of the copy button, text selection, and the search index. */
|
|
263
|
+
.code-block .line {
|
|
264
|
+
position: relative;
|
|
265
|
+
display: inline-block;
|
|
266
|
+
box-sizing: border-box;
|
|
267
|
+
min-width: 100%;
|
|
268
|
+
min-height: 1lh;
|
|
269
|
+
padding-inline: 0.75rem 3rem;
|
|
270
|
+
}
|
|
275
271
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
.hljs-template-variable,
|
|
283
|
-
.hljs-type,
|
|
284
|
-
.hljs-variable.language_
|
|
285
|
-
) {
|
|
286
|
-
color: #ff7b72;
|
|
287
|
-
}
|
|
272
|
+
.code-block .line span {
|
|
273
|
+
color: var(--shiki-light);
|
|
274
|
+
font-style: var(--shiki-light-font-style, inherit);
|
|
275
|
+
font-weight: var(--shiki-light-font-weight, inherit);
|
|
276
|
+
text-decoration: var(--shiki-light-text-decoration, inherit);
|
|
277
|
+
}
|
|
288
278
|
|
|
289
|
-
[data-theme="dark"]
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
279
|
+
[data-theme="dark"] .code-block .line span {
|
|
280
|
+
color: var(--shiki-dark);
|
|
281
|
+
font-style: var(--shiki-dark-font-style, inherit);
|
|
282
|
+
font-weight: var(--shiki-dark-font-weight, inherit);
|
|
283
|
+
text-decoration: var(--shiki-dark-text-decoration, inherit);
|
|
284
|
+
}
|
|
293
285
|
|
|
294
|
-
[data-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.hljs-literal,
|
|
299
|
-
.hljs-meta,
|
|
300
|
-
.hljs-number,
|
|
301
|
-
.hljs-operator,
|
|
302
|
-
.hljs-variable,
|
|
303
|
-
.hljs-selector-attr,
|
|
304
|
-
.hljs-selector-class,
|
|
305
|
-
.hljs-selector-id,
|
|
306
|
-
.hljs-section
|
|
307
|
-
) {
|
|
308
|
-
color: #79c0ff;
|
|
309
|
-
}
|
|
286
|
+
.code-block .line[data-highlighted] {
|
|
287
|
+
background: color-mix(in srgb, var(--primary) 10%, transparent);
|
|
288
|
+
box-shadow: inset 2px 0 var(--primary);
|
|
289
|
+
}
|
|
310
290
|
|
|
311
|
-
[data-
|
|
312
|
-
|
|
313
|
-
}
|
|
291
|
+
.code-block .line[data-diff="add"] {
|
|
292
|
+
background: color-mix(in srgb, #3fb950 14%, transparent);
|
|
293
|
+
}
|
|
314
294
|
|
|
315
|
-
[data-
|
|
316
|
-
|
|
317
|
-
}
|
|
295
|
+
.code-block .line[data-diff="remove"] {
|
|
296
|
+
background: color-mix(in srgb, #f85149 14%, transparent);
|
|
297
|
+
}
|
|
318
298
|
|
|
319
|
-
[data-
|
|
320
|
-
|
|
321
|
-
}
|
|
299
|
+
.code-block[data-diff-markers] .line {
|
|
300
|
+
padding-inline-start: 1.5rem;
|
|
301
|
+
}
|
|
322
302
|
|
|
323
|
-
[data-
|
|
324
|
-
|
|
325
|
-
|
|
303
|
+
.code-block[data-diff-markers] .line[data-diff]::after {
|
|
304
|
+
position: absolute;
|
|
305
|
+
top: 0;
|
|
306
|
+
left: 0.5rem;
|
|
307
|
+
color: var(--muted-foreground);
|
|
308
|
+
user-select: none;
|
|
309
|
+
}
|
|
326
310
|
|
|
327
|
-
[data-
|
|
328
|
-
|
|
329
|
-
}
|
|
311
|
+
.code-block[data-diff-markers] .line[data-diff="add"]::after {
|
|
312
|
+
content: "+";
|
|
313
|
+
}
|
|
330
314
|
|
|
331
|
-
[data-
|
|
332
|
-
|
|
333
|
-
}
|
|
315
|
+
.code-block[data-diff-markers] .line[data-diff="remove"]::after {
|
|
316
|
+
content: "-";
|
|
317
|
+
}
|
|
334
318
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
319
|
+
/* `counter-reset` is set inline by CodeBlock so blocks can start at any line. */
|
|
320
|
+
.code-block[data-line-numbers] .line {
|
|
321
|
+
counter-increment: line;
|
|
322
|
+
}
|
|
339
323
|
|
|
340
|
-
[data-
|
|
341
|
-
|
|
342
|
-
|
|
324
|
+
.code-block[data-line-numbers] .line::before {
|
|
325
|
+
content: counter(line);
|
|
326
|
+
display: inline-block;
|
|
327
|
+
width: var(--code-gutter, 2ch);
|
|
328
|
+
margin-inline-end: 1rem;
|
|
329
|
+
color: var(--muted-foreground);
|
|
330
|
+
text-align: right;
|
|
331
|
+
user-select: none;
|
|
332
|
+
}
|
|
343
333
|
}
|
package/types/config.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { PluggableList } from 'unified';
|
|
2
|
+
|
|
3
|
+
/** Build-time Markdown and MDX compiler extensions. */
|
|
4
|
+
export interface MdxConfig {
|
|
5
|
+
/** Unified remark plugins, run before Shiso's code-title and table-of-contents transforms. */
|
|
6
|
+
remarkPlugins?: PluggableList;
|
|
7
|
+
/** Unified rehype plugins, run before Shiso's highlighting, image, and heading transforms. */
|
|
8
|
+
rehypePlugins?: PluggableList;
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
/** Project-level settings supplied by shiso.config.ts. All fields are optional. */
|
|
7
12
|
export interface ShisoConfig {
|
|
@@ -13,6 +18,8 @@ export interface ShisoConfig {
|
|
|
13
18
|
siteUrl?: string;
|
|
14
19
|
/** Locale used for deterministic date formatting. Default "en-US". */
|
|
15
20
|
locale?: string;
|
|
21
|
+
/** Build-time remark and rehype plugins for Markdown and MDX content. */
|
|
22
|
+
mdx?: MdxConfig;
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/** Identity helper that types a shiso.config.ts default export. */
|
package/vite.config.ts
CHANGED
|
@@ -7,8 +7,10 @@ import { defineConfig, type Plugin, searchForWorkspaceRoot } from 'vite';
|
|
|
7
7
|
import { shisoMdx } from './mdx.config.ts';
|
|
8
8
|
import { generateIconRegistry } from './scripts/generate-icon-registry.mjs';
|
|
9
9
|
import { shisoLastModified } from './scripts/generate-last-modified.mjs';
|
|
10
|
+
import { generateOpenApiModule } from './scripts/generate-openapi.mjs';
|
|
10
11
|
import { generateSearchIndex } from './scripts/generate-search-index.mjs';
|
|
11
12
|
import { createDocsConfigModule } from './scripts/vite-docs-config.mjs';
|
|
13
|
+
import { resolveCodeBlockConfig } from './src/lib/code-blocks.ts';
|
|
12
14
|
import type { DocsConfig, ResolvedShisoConfig } from './src/lib/types.ts';
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -37,6 +39,34 @@ function shisoIconRegistry(getDocsConfig: () => DocsConfig, root: string, output
|
|
|
37
39
|
* Keeps src/lib/search-index.generated.ts in sync with content, so the search
|
|
38
40
|
* dialog can query page text without a server.
|
|
39
41
|
*/
|
|
42
|
+
function shisoOpenApi(
|
|
43
|
+
getDocsConfig: () => DocsConfig,
|
|
44
|
+
getSpecPath: () => string | undefined,
|
|
45
|
+
root: string,
|
|
46
|
+
output: string,
|
|
47
|
+
): Plugin {
|
|
48
|
+
const generate = () =>
|
|
49
|
+
generateOpenApiModule({
|
|
50
|
+
root,
|
|
51
|
+
config: getDocsConfig(),
|
|
52
|
+
theme: resolveCodeBlockConfig(getDocsConfig().styling).theme,
|
|
53
|
+
output,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name: 'shiso-openapi',
|
|
58
|
+
async buildStart() {
|
|
59
|
+
await generate();
|
|
60
|
+
},
|
|
61
|
+
async handleHotUpdate({ file }) {
|
|
62
|
+
const specPath = getSpecPath();
|
|
63
|
+
if ((specPath && path.resolve(file) === specPath) || file.endsWith('docs.json')) {
|
|
64
|
+
await generate();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
function shisoSearchIndex(
|
|
41
71
|
getDocsConfig: () => DocsConfig,
|
|
42
72
|
getShisoConfig: () => ResolvedShisoConfig,
|
|
@@ -221,8 +251,11 @@ function buildThemeCss(config: DocsConfig): string {
|
|
|
221
251
|
}
|
|
222
252
|
|
|
223
253
|
return [
|
|
224
|
-
|
|
225
|
-
|
|
254
|
+
// App styles are loaded by the client entry after this head style in dev.
|
|
255
|
+
// Use a more specific selector than the default token declarations so
|
|
256
|
+
// configured theme values win regardless of stylesheet load order.
|
|
257
|
+
root.length ? `html:root{${root.join('')}}` : '',
|
|
258
|
+
dark.length ? `html[data-theme="dark"]{${dark.join('')}}` : '',
|
|
226
259
|
...extra,
|
|
227
260
|
]
|
|
228
261
|
.filter(Boolean)
|
|
@@ -459,9 +492,18 @@ export default defineConfig(async () => {
|
|
|
459
492
|
projectRoot,
|
|
460
493
|
path.join(generatedRoot, 'search-index.generated.ts'),
|
|
461
494
|
),
|
|
495
|
+
shisoOpenApi(
|
|
496
|
+
getDocsConfig,
|
|
497
|
+
() => configModule.getSpecPath?.(),
|
|
498
|
+
projectRoot,
|
|
499
|
+
path.join(generatedRoot, 'openapi.generated.ts'),
|
|
500
|
+
),
|
|
462
501
|
shisoHtml(getDocsConfig),
|
|
463
502
|
shisoMarkdownDev(getDocsConfig, getShisoConfig, projectRoot),
|
|
464
|
-
shisoMdx(
|
|
503
|
+
shisoMdx({
|
|
504
|
+
...getShisoConfig().mdx,
|
|
505
|
+
codeBlocks: resolveCodeBlockConfig(getDocsConfig().styling),
|
|
506
|
+
}),
|
|
465
507
|
react({ include: /\.(mdx|md|tsx|ts|jsx|js)$/ }),
|
|
466
508
|
],
|
|
467
509
|
resolve: {
|
|
@@ -476,6 +518,10 @@ export default defineConfig(async () => {
|
|
|
476
518
|
find: '@/lib/search-index.generated',
|
|
477
519
|
replacement: path.join(generatedRoot, 'search-index.generated.ts'),
|
|
478
520
|
},
|
|
521
|
+
{
|
|
522
|
+
find: '@/lib/openapi.generated',
|
|
523
|
+
replacement: path.join(generatedRoot, 'openapi.generated.ts'),
|
|
524
|
+
},
|
|
479
525
|
{
|
|
480
526
|
find: '@/generated/last-modified',
|
|
481
527
|
replacement: path.join(generatedRoot, 'last-modified.ts'),
|