@raystack/chronicle 0.10.2 → 0.10.4
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/dist/cli/index.js +32 -5
- package/package.json +1 -1
- package/src/cli/commands/dev.ts +2 -1
- package/src/components/mdx/paragraph.tsx +1 -1
- package/src/components/ui/PrefetchProvider.tsx +5 -3
- package/src/lib/mdx-utils.ts +4 -0
- package/src/lib/page-context.tsx +7 -0
- package/src/lib/preload.ts +6 -1
- package/src/lib/remark-resolve-images.ts +21 -2
- package/src/lib/source.ts +11 -1
- package/src/server/api/page.ts +2 -1
- package/src/server/api/ready.ts +13 -9
- package/src/server/entry-server.tsx +5 -1
- package/src/server/error.ts +11 -0
- package/src/server/vite-config.ts +2 -1
- package/src/themes/default/Layout.tsx +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -46,6 +46,15 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
|
|
49
|
+
// src/lib/mdx-utils.ts
|
|
50
|
+
var MdxNodeType;
|
|
51
|
+
var init_mdx_utils = __esm(() => {
|
|
52
|
+
MdxNodeType = {
|
|
53
|
+
JsxFlow: "mdxJsxFlowElement",
|
|
54
|
+
JsxText: "mdxJsxTextElement"
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
49
58
|
// src/lib/remark-resolve-images.ts
|
|
50
59
|
import path4 from "node:path";
|
|
51
60
|
import { visit } from "unist-util-visit";
|
|
@@ -72,16 +81,29 @@ var remarkResolveImages = () => {
|
|
|
72
81
|
return;
|
|
73
82
|
const relative = filePath.slice(contentIdx + "/content/".length);
|
|
74
83
|
const dir = path4.posix.dirname(relative);
|
|
84
|
+
const seen = new Set;
|
|
85
|
+
const images = [];
|
|
86
|
+
function collect(src) {
|
|
87
|
+
if (!src || seen.has(src) || /^data:/i.test(src))
|
|
88
|
+
return;
|
|
89
|
+
seen.add(src);
|
|
90
|
+
images.push(src);
|
|
91
|
+
}
|
|
75
92
|
visit(tree, "image", (node) => {
|
|
76
93
|
if (!node.url)
|
|
77
94
|
return;
|
|
78
95
|
node.url = resolveUrl(node.url, dir);
|
|
96
|
+
collect(node.url);
|
|
79
97
|
});
|
|
80
98
|
visit(tree, "html", (node) => {
|
|
81
|
-
node.value = node.value.replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi, (_, before, src, after) =>
|
|
99
|
+
node.value = node.value.replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi, (_, before, src, after) => {
|
|
100
|
+
const resolved = resolveUrl(src, dir);
|
|
101
|
+
collect(resolved);
|
|
102
|
+
return `${before}${resolved}${after}`;
|
|
103
|
+
});
|
|
82
104
|
});
|
|
83
105
|
visit(tree, (node) => {
|
|
84
|
-
if (node.type !==
|
|
106
|
+
if (node.type !== MdxNodeType.JsxFlow && node.type !== MdxNodeType.JsxText)
|
|
85
107
|
return;
|
|
86
108
|
const jsx = node;
|
|
87
109
|
if (jsx.name !== "img")
|
|
@@ -90,6 +112,7 @@ var remarkResolveImages = () => {
|
|
|
90
112
|
if (!srcAttr?.value || typeof srcAttr.value !== "string")
|
|
91
113
|
return;
|
|
92
114
|
srcAttr.value = resolveUrl(srcAttr.value, dir);
|
|
115
|
+
collect(srcAttr.value);
|
|
93
116
|
});
|
|
94
117
|
visit(tree, "element", (node) => {
|
|
95
118
|
if (node.tagName !== "img")
|
|
@@ -98,10 +121,13 @@ var remarkResolveImages = () => {
|
|
|
98
121
|
if (typeof src !== "string")
|
|
99
122
|
return;
|
|
100
123
|
node.properties.src = resolveUrl(src, dir);
|
|
124
|
+
collect(node.properties.src);
|
|
101
125
|
});
|
|
126
|
+
file.data.images = images;
|
|
102
127
|
};
|
|
103
128
|
}, remark_resolve_images_default;
|
|
104
129
|
var init_remark_resolve_images = __esm(() => {
|
|
130
|
+
init_mdx_utils();
|
|
105
131
|
remark_resolve_images_default = remarkResolveImages;
|
|
106
132
|
});
|
|
107
133
|
|
|
@@ -360,7 +386,7 @@ async function createViteConfig(options) {
|
|
|
360
386
|
default: defineFumadocsConfig({
|
|
361
387
|
mdxOptions: {
|
|
362
388
|
remarkImageOptions: false,
|
|
363
|
-
valueToExport: ["readingTime"],
|
|
389
|
+
valueToExport: ["readingTime", "images"],
|
|
364
390
|
remarkPlugins: [
|
|
365
391
|
remarkDirective,
|
|
366
392
|
[remarkDirectiveAdmonition, {
|
|
@@ -433,6 +459,7 @@ async function createViteConfig(options) {
|
|
|
433
459
|
},
|
|
434
460
|
nitro: {
|
|
435
461
|
logLevel: 2,
|
|
462
|
+
errorHandler: path6.resolve(packageRoot, "src/server/error.ts"),
|
|
436
463
|
publicAssets: [{ dir: path6.resolve(projectRoot, "public") }],
|
|
437
464
|
output: {
|
|
438
465
|
dir: resolveOutputDir(projectRoot, preset)
|
|
@@ -808,14 +835,14 @@ var buildCommand = new Command("build").description("Build for production").opti
|
|
|
808
835
|
// src/cli/commands/dev.ts
|
|
809
836
|
import chalk3 from "chalk";
|
|
810
837
|
import { Command as Command2 } from "commander";
|
|
811
|
-
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").action(async (options) => {
|
|
838
|
+
var devCommand = new Command2("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("--config <path>", "Path to chronicle.yaml").option("--host <host>", "Host address", "localhost").option("--preset <preset>", "Deploy preset (bun, node-server, etc.)").action(async (options) => {
|
|
812
839
|
const { config: config2, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
813
840
|
const port = parseInt(options.port, 10);
|
|
814
841
|
await linkContent(projectRoot, config2);
|
|
815
842
|
console.log(chalk3.cyan("Starting dev server..."));
|
|
816
843
|
const { createServer } = await import("vite");
|
|
817
844
|
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_vite_config(), exports_vite_config));
|
|
818
|
-
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
845
|
+
const viteConfig = await createViteConfig2({ packageRoot: PACKAGE_ROOT, projectRoot, configPath, preset: options.preset });
|
|
819
846
|
const server = await createServer({
|
|
820
847
|
...viteConfig,
|
|
821
848
|
server: { ...viteConfig.server, port, host: options.host }
|
package/package.json
CHANGED
package/src/cli/commands/dev.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const devCommand = new Command('dev')
|
|
|
9
9
|
.option('-p, --port <port>', 'Port number', '3000')
|
|
10
10
|
.option('--config <path>', 'Path to chronicle.yaml')
|
|
11
11
|
.option('--host <host>', 'Host address', 'localhost')
|
|
12
|
+
.option('--preset <preset>', 'Deploy preset (bun, node-server, etc.)')
|
|
12
13
|
.action(async options => {
|
|
13
14
|
const { config, projectRoot, configPath } = await loadCLIConfig(options.config);
|
|
14
15
|
const port = parseInt(options.port, 10);
|
|
@@ -20,7 +21,7 @@ export const devCommand = new Command('dev')
|
|
|
20
21
|
const { createServer } = await import('vite');
|
|
21
22
|
const { createViteConfig } = await import('@/server/vite-config');
|
|
22
23
|
|
|
23
|
-
const viteConfig = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot, configPath });
|
|
24
|
+
const viteConfig = await createViteConfig({ packageRoot: PACKAGE_ROOT, projectRoot, configPath, preset: options.preset });
|
|
24
25
|
const server = await createServer({
|
|
25
26
|
...viteConfig,
|
|
26
27
|
server: { ...viteConfig.server, port, host: options.host }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Children, isValidElement, type ComponentProps } from 'react'
|
|
2
2
|
import styles from './paragraph.module.css'
|
|
3
3
|
|
|
4
|
-
const BLOCK_ELEMENTS = new Set(['summary', 'details', 'div', 'table', 'ul', 'ol'])
|
|
4
|
+
const BLOCK_ELEMENTS = new Set(['summary', 'details', 'div', 'table', 'ul', 'ol', 'p'])
|
|
5
5
|
|
|
6
6
|
function hasBlockChild(children: React.ReactNode): boolean {
|
|
7
7
|
return Children.toArray(children).some(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { prefetchPageData } from '@/lib/preload';
|
|
3
3
|
|
|
4
|
+
const NO_PREFETCH_ATTR = 'data-no-prefetch';
|
|
5
|
+
|
|
4
6
|
function resolvePathname(href: string | null): string | null {
|
|
5
7
|
if (!href) return null;
|
|
6
8
|
try {
|
|
@@ -16,14 +18,14 @@ export function PrefetchProvider({ children }: { children: React.ReactNode }) {
|
|
|
16
18
|
useEffect(() => {
|
|
17
19
|
const handleMouseOver = (e: MouseEvent) => {
|
|
18
20
|
const anchor = (e.target as HTMLElement).closest?.('a[href]');
|
|
19
|
-
if (!anchor) return;
|
|
21
|
+
if (!anchor || anchor.hasAttribute(NO_PREFETCH_ATTR)) return;
|
|
20
22
|
const pathname = resolvePathname(anchor.getAttribute('href'));
|
|
21
23
|
if (pathname) prefetchPageData(pathname);
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const handleFocusIn = (e: FocusEvent) => {
|
|
25
27
|
const anchor = (e.target as HTMLElement).closest?.('a[href]');
|
|
26
|
-
if (!anchor) return;
|
|
28
|
+
if (!anchor || anchor.hasAttribute(NO_PREFETCH_ATTR)) return;
|
|
27
29
|
const pathname = resolvePathname(anchor.getAttribute('href'));
|
|
28
30
|
if (pathname) prefetchPageData(pathname);
|
|
29
31
|
};
|
|
@@ -45,7 +47,7 @@ export function PrefetchProvider({ children }: { children: React.ReactNode }) {
|
|
|
45
47
|
);
|
|
46
48
|
|
|
47
49
|
const observeLinks = () => {
|
|
48
|
-
document.querySelectorAll(
|
|
50
|
+
document.querySelectorAll(`a[href]:not([data-prefetch-observed]):not([${NO_PREFETCH_ATTR}])`).forEach((link) => {
|
|
49
51
|
const pathname = resolvePathname(link.getAttribute('href'));
|
|
50
52
|
if (pathname) {
|
|
51
53
|
link.setAttribute('data-prefetch-observed', '');
|
package/src/lib/page-context.tsx
CHANGED
|
@@ -110,6 +110,7 @@ export function PageProvider({
|
|
|
110
110
|
frontmatter: Frontmatter;
|
|
111
111
|
relativePath: string;
|
|
112
112
|
originalPath?: string;
|
|
113
|
+
images?: string[];
|
|
113
114
|
prev?: PageNavLink | null;
|
|
114
115
|
next?: PageNavLink | null;
|
|
115
116
|
}
|
|
@@ -132,6 +133,12 @@ export function PageProvider({
|
|
|
132
133
|
try {
|
|
133
134
|
const data = await fetchPageData(slug);
|
|
134
135
|
if (cancelled.current) return;
|
|
136
|
+
if (data.images?.length) {
|
|
137
|
+
for (const src of data.images) {
|
|
138
|
+
const img = new Image();
|
|
139
|
+
img.src = src;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
135
142
|
const { content, toc } = await loadMdx(data.originalPath || data.relativePath);
|
|
136
143
|
if (cancelled.current) return;
|
|
137
144
|
setErrorStatus(null);
|
package/src/lib/preload.ts
CHANGED
|
@@ -28,8 +28,13 @@ function isApisRoute(pathname: string): boolean {
|
|
|
28
28
|
return pathname === '/apis' || pathname.startsWith('/apis/');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function hasFileExtension(pathname: string): boolean {
|
|
32
|
+
const lastSegment = pathname.split('/').pop() ?? '';
|
|
33
|
+
return lastSegment.includes('.');
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
export function prefetchPageData(pathname: string) {
|
|
32
|
-
if (isApisRoute(pathname)) return;
|
|
37
|
+
if (isApisRoute(pathname) || hasFileExtension(pathname)) return;
|
|
33
38
|
queryClient.prefetchQuery({
|
|
34
39
|
queryKey: pageDataQueryKey(pathname),
|
|
35
40
|
queryFn: () => fetchPageDataByPathname(pathname),
|
|
@@ -4,6 +4,7 @@ import type { Plugin } from 'unified'
|
|
|
4
4
|
import type { Image, Html } from 'mdast'
|
|
5
5
|
import type { Element } from 'hast'
|
|
6
6
|
import type { MdxJsxFlowElement, MdxJsxTextElement, MdxJsxAttribute } from 'mdast-util-mdx-jsx'
|
|
7
|
+
import { MdxNodeType } from './mdx-utils'
|
|
7
8
|
|
|
8
9
|
function resolveUrl(src: string, dir: string): string {
|
|
9
10
|
if (/^[a-z][a-z0-9+\-.]*:/i.test(src)) return src
|
|
@@ -26,25 +27,40 @@ const remarkResolveImages: Plugin = () => {
|
|
|
26
27
|
const relative = filePath.slice(contentIdx + '/content/'.length)
|
|
27
28
|
const dir = path.posix.dirname(relative)
|
|
28
29
|
|
|
30
|
+
const seen = new Set<string>()
|
|
31
|
+
const images: string[] = []
|
|
32
|
+
|
|
33
|
+
function collect(src: string) {
|
|
34
|
+
if (!src || seen.has(src) || /^data:/i.test(src)) return
|
|
35
|
+
seen.add(src)
|
|
36
|
+
images.push(src)
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
visit(tree, 'image', (node: Image) => {
|
|
30
40
|
if (!node.url) return
|
|
31
41
|
node.url = resolveUrl(node.url, dir)
|
|
42
|
+
collect(node.url)
|
|
32
43
|
})
|
|
33
44
|
|
|
34
45
|
visit(tree, 'html', (node: Html) => {
|
|
35
46
|
node.value = node.value.replace(
|
|
36
47
|
/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi,
|
|
37
|
-
(_, before, src, after) =>
|
|
48
|
+
(_, before, src, after) => {
|
|
49
|
+
const resolved = resolveUrl(src, dir)
|
|
50
|
+
collect(resolved)
|
|
51
|
+
return `${before}${resolved}${after}`
|
|
52
|
+
}
|
|
38
53
|
)
|
|
39
54
|
})
|
|
40
55
|
|
|
41
56
|
visit(tree, (node) => {
|
|
42
|
-
if (node.type !==
|
|
57
|
+
if (node.type !== MdxNodeType.JsxFlow && node.type !== MdxNodeType.JsxText) return
|
|
43
58
|
const jsx = node as MdxJsxFlowElement | MdxJsxTextElement
|
|
44
59
|
if (jsx.name !== 'img') return
|
|
45
60
|
const srcAttr = jsx.attributes.find((a): a is MdxJsxAttribute => a.type === 'mdxJsxAttribute' && a.name === 'src')
|
|
46
61
|
if (!srcAttr?.value || typeof srcAttr.value !== 'string') return
|
|
47
62
|
srcAttr.value = resolveUrl(srcAttr.value, dir)
|
|
63
|
+
collect(srcAttr.value)
|
|
48
64
|
})
|
|
49
65
|
|
|
50
66
|
visit(tree, 'element', (node: Element) => {
|
|
@@ -52,7 +68,10 @@ const remarkResolveImages: Plugin = () => {
|
|
|
52
68
|
const src = node.properties?.src
|
|
53
69
|
if (typeof src !== 'string') return
|
|
54
70
|
node.properties.src = resolveUrl(src, dir)
|
|
71
|
+
collect(node.properties.src as string)
|
|
55
72
|
})
|
|
73
|
+
|
|
74
|
+
file.data.images = images
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
|
package/src/lib/source.ts
CHANGED
|
@@ -37,6 +37,11 @@ const readingTimeGlob: Record<string, { text: string; minutes: number; words: nu
|
|
|
37
37
|
{ eager: true, import: 'readingTime' }
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
+
const imagesGlob: Record<string, string[] | undefined> = import.meta.glob(
|
|
41
|
+
'../../.content/**/*.{mdx,md}',
|
|
42
|
+
{ eager: true, import: 'images' }
|
|
43
|
+
);
|
|
44
|
+
|
|
40
45
|
const metaGlob: Record<string, Record<string, unknown>> = import.meta.glob(
|
|
41
46
|
'../../.content/**/meta.json',
|
|
42
47
|
{ eager: true }
|
|
@@ -54,10 +59,11 @@ function buildFiles() {
|
|
|
54
59
|
const relativePath = originalPath.replace(/readme\.(mdx?)$/i, 'index.$1');
|
|
55
60
|
const rt = readingTimeGlob[key];
|
|
56
61
|
const _readingTime = rt?.minutes != null ? Math.max(1, Math.round(rt.minutes)) : undefined;
|
|
62
|
+
const _images = imagesGlob[key] ?? [];
|
|
57
63
|
files.push({
|
|
58
64
|
type: 'page',
|
|
59
65
|
path: relativePath,
|
|
60
|
-
data: { ...data, _readingTime, _relativePath: relativePath, _originalPath: originalPath }
|
|
66
|
+
data: { ...data, _readingTime, _images, _relativePath: relativePath, _originalPath: originalPath }
|
|
61
67
|
});
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -285,6 +291,10 @@ export function getOriginalPath(page: { data: unknown }): string {
|
|
|
285
291
|
return ((page.data as Record<string, unknown>)._originalPath as string) ?? '';
|
|
286
292
|
}
|
|
287
293
|
|
|
294
|
+
export function getPageImages(page: { data: unknown }): string[] {
|
|
295
|
+
return ((page.data as Record<string, unknown>)._images as string[]) ?? [];
|
|
296
|
+
}
|
|
297
|
+
|
|
288
298
|
export async function getPageSearchContent(page: { data: unknown }): Promise<{ headings: string; body: string }> {
|
|
289
299
|
const originalPath = getOriginalPath(page);
|
|
290
300
|
if (!originalPath) return { headings: '', body: '' };
|
package/src/server/api/page.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineHandler, HTTPError } from 'nitro';
|
|
2
|
-
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath, isDraft } from '@/lib/source';
|
|
2
|
+
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath, getPageImages, isDraft } from '@/lib/source';
|
|
3
3
|
|
|
4
4
|
export default defineHandler(async event => {
|
|
5
5
|
const slugParam = event.url.searchParams.get('slug') ?? '';
|
|
@@ -16,6 +16,7 @@ export default defineHandler(async event => {
|
|
|
16
16
|
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
|
|
17
17
|
relativePath: getRelativePath(page),
|
|
18
18
|
originalPath: getOriginalPath(page),
|
|
19
|
+
images: getPageImages(page),
|
|
19
20
|
prev: nav.prev,
|
|
20
21
|
next: nav.next,
|
|
21
22
|
});
|
package/src/server/api/ready.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { defineHandler } from 'nitro';
|
|
2
|
-
import { isSearchReady } from './search';
|
|
2
|
+
import { ensureIndex, isSearchReady } from './search';
|
|
3
|
+
import { LATEST_CONTEXT } from '@/lib/version-source';
|
|
3
4
|
|
|
4
|
-
export default defineHandler(() => {
|
|
5
|
-
|
|
5
|
+
export default defineHandler(async () => {
|
|
6
|
+
ensureIndex(LATEST_CONTEXT).catch(e => console.error('[search:index]', e));
|
|
6
7
|
|
|
7
|
-
if (!
|
|
8
|
-
return Response.
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
);
|
|
8
|
+
if (!isSearchReady()) {
|
|
9
|
+
return new Response(JSON.stringify({ status: 'not_ready', search: false }), {
|
|
10
|
+
status: 503,
|
|
11
|
+
headers: { 'Content-Type': 'application/json' },
|
|
12
|
+
});
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
return Response.
|
|
15
|
+
return new Response(JSON.stringify({ status: 'ready', search: true }), {
|
|
16
|
+
status: 200,
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
});
|
|
15
19
|
});
|
|
@@ -9,7 +9,7 @@ import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
|
9
9
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
10
10
|
import { PageProvider } from '@/lib/page-context';
|
|
11
11
|
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
12
|
-
import { getPageTree, getPage, getPageNav, loadPageModule, extractFrontmatter, getRelativePath, getOriginalPath, isDraft } from '@/lib/source';
|
|
12
|
+
import { getPageTree, getPage, getPageNav, loadPageModule, extractFrontmatter, getRelativePath, getOriginalPath, getPageImages, isDraft } from '@/lib/source';
|
|
13
13
|
import { getFirstApiUrl } from '@/lib/api-routes';
|
|
14
14
|
import { StatusCodes } from 'http-status-codes';
|
|
15
15
|
import { resolveDocsRedirect } from '@/lib/tree-utils';
|
|
@@ -79,6 +79,7 @@ export default {
|
|
|
79
79
|
const relativePath = page ? getRelativePath(page) : null;
|
|
80
80
|
const originalPath = page ? getOriginalPath(page) : null;
|
|
81
81
|
const mdxModule = (originalPath || relativePath) ? await loadPageModule(originalPath || relativePath!) : null;
|
|
82
|
+
const pageImages = page ? getPageImages(page) : [];
|
|
82
83
|
|
|
83
84
|
const pageData = page
|
|
84
85
|
? {
|
|
@@ -125,6 +126,9 @@ export default {
|
|
|
125
126
|
{assets.js.map((attr: { href: string }) => (
|
|
126
127
|
<link key={attr.href} rel="modulepreload" {...attr} />
|
|
127
128
|
))}
|
|
129
|
+
{pageImages.map((src: string) => (
|
|
130
|
+
<link key={src} rel="preload" as="image" href={src} />
|
|
131
|
+
))}
|
|
128
132
|
<script type="module" src={assets.entry} />
|
|
129
133
|
<script dangerouslySetInnerHTML={{ __html: `window.__PAGE_DATA__ = ${safeJson}` }} />
|
|
130
134
|
</head>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineErrorHandler, HTTPError } from 'nitro';
|
|
2
|
+
|
|
3
|
+
export default defineErrorHandler((error, _event) => {
|
|
4
|
+
const status = HTTPError.isError(error) ? error.status : 500;
|
|
5
|
+
const message = error.message || 'Internal Server Error';
|
|
6
|
+
|
|
7
|
+
return new Response(JSON.stringify({ error: true, status, message }), {
|
|
8
|
+
status,
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -72,7 +72,7 @@ export async function createViteConfig(
|
|
|
72
72
|
default: defineFumadocsConfig({
|
|
73
73
|
mdxOptions: {
|
|
74
74
|
remarkImageOptions: false,
|
|
75
|
-
valueToExport: ['readingTime'],
|
|
75
|
+
valueToExport: ['readingTime', 'images'],
|
|
76
76
|
remarkPlugins: [
|
|
77
77
|
remarkDirective,
|
|
78
78
|
[remarkDirectiveAdmonition, {
|
|
@@ -145,6 +145,7 @@ export async function createViteConfig(
|
|
|
145
145
|
},
|
|
146
146
|
nitro: {
|
|
147
147
|
logLevel: 2,
|
|
148
|
+
errorHandler: path.resolve(packageRoot, 'src/server/error.ts'),
|
|
148
149
|
publicAssets: [{ dir: path.resolve(projectRoot, 'public') }],
|
|
149
150
|
output: {
|
|
150
151
|
dir: resolveOutputDir(projectRoot, preset),
|
|
@@ -138,7 +138,7 @@ export function Layout({
|
|
|
138
138
|
<DocumentTextIcon width={16} height={16} />
|
|
139
139
|
)}
|
|
140
140
|
classNames={{ root: styles.topLinkItem, text: styles.topLinkText }}
|
|
141
|
-
render={<RouterLink to={entry.href} />}
|
|
141
|
+
render={<RouterLink to={entry.href} data-no-prefetch />}
|
|
142
142
|
>
|
|
143
143
|
{entry.label}
|
|
144
144
|
</Sidebar.Item>
|
|
@@ -154,7 +154,7 @@ export function Layout({
|
|
|
154
154
|
<CodeBracketSquareIcon width={16} height={16} />
|
|
155
155
|
)}
|
|
156
156
|
classNames={{ root: styles.topLinkItem, text: styles.topLinkText }}
|
|
157
|
-
render={<RouterLink to={api.basePath} />}
|
|
157
|
+
render={<RouterLink to={api.basePath} data-no-prefetch />}
|
|
158
158
|
>
|
|
159
159
|
{api.name} API
|
|
160
160
|
</Sidebar.Item>
|