boltdocs 1.4.0 → 1.5.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/dist/{SearchDialog-FBNGKRPK.mjs → SearchDialog-5ISK64QY.mjs} +1 -1
- package/dist/{SearchDialog-O3V36MXA.css → SearchDialog-CEVPEMT3.css} +54 -5
- package/dist/{cache-GQHF6BXI.mjs → cache-KNL5B4EE.mjs} +1 -1
- package/dist/{chunk-CYBWLFOG.mjs → chunk-FFBNU6IJ.mjs} +2 -1
- package/dist/{chunk-D7YBQG6H.mjs → chunk-FMQ4HRKZ.mjs} +311 -133
- package/dist/client/index.css +54 -5
- package/dist/client/index.d.mts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.js +624 -475
- package/dist/client/index.mjs +2 -4
- package/dist/client/ssr.css +54 -5
- package/dist/client/ssr.d.mts +1 -1
- package/dist/client/ssr.d.ts +1 -1
- package/dist/client/ssr.js +544 -395
- package/dist/client/ssr.mjs +1 -1
- package/dist/{config-BD5ZHz15.d.mts → config-DkZg5aCf.d.mts} +2 -0
- package/dist/{config-BD5ZHz15.d.ts → config-DkZg5aCf.d.ts} +2 -0
- package/dist/node/index.d.mts +2 -2
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +24 -17
- package/dist/node/index.mjs +25 -19
- package/dist/{types-CvrzTbEX.d.mts → types-DGIo1VKD.d.mts} +2 -0
- package/dist/{types-CvrzTbEX.d.ts → types-DGIo1VKD.d.ts} +2 -0
- package/package.json +1 -1
- package/src/client/app/index.tsx +2 -12
- package/src/client/app/preload.tsx +3 -1
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +0 -11
- package/src/client/theme/styles/markdown.css +1 -5
- package/src/client/theme/ui/Link/Link.tsx +156 -18
- package/src/client/theme/ui/Link/LinkPreview.tsx +64 -0
- package/src/client/theme/ui/Link/link-preview.css +64 -0
- package/src/client/types.ts +2 -0
- package/src/node/config.ts +15 -6
- package/src/node/mdx.ts +11 -4
- package/src/node/routes/parser.ts +24 -2
- package/src/node/ssg/index.ts +1 -10
- package/src/node/utils.ts +4 -1
- package/dist/CodeBlock-QYIKJMEB.mjs +0 -7
- package/dist/chunk-KS5B3O6W.mjs +0 -43
package/dist/client/ssr.mjs
CHANGED
|
@@ -54,6 +54,8 @@ interface BoltdocsThemeConfig {
|
|
|
54
54
|
githubRepo?: string;
|
|
55
55
|
/** Whether to show the 'Powered by LiteDocs' badge in the sidebar (default: true) */
|
|
56
56
|
poweredBy?: boolean;
|
|
57
|
+
/** Whether to show a preview tooltip on internal links hover (default: true) */
|
|
58
|
+
linkPreview?: boolean;
|
|
57
59
|
/** Granular layout customization props */
|
|
58
60
|
layoutProps?: {
|
|
59
61
|
navbar?: any;
|
|
@@ -54,6 +54,8 @@ interface BoltdocsThemeConfig {
|
|
|
54
54
|
githubRepo?: string;
|
|
55
55
|
/** Whether to show the 'Powered by LiteDocs' badge in the sidebar (default: true) */
|
|
56
56
|
poweredBy?: boolean;
|
|
57
|
+
/** Whether to show a preview tooltip on internal links hover (default: true) */
|
|
58
|
+
linkPreview?: boolean;
|
|
57
59
|
/** Granular layout customization props */
|
|
58
60
|
layoutProps?: {
|
|
59
61
|
navbar?: any;
|
package/dist/node/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { B as BoltdocsConfig } from '../config-
|
|
3
|
-
export { a as BoltdocsThemeConfig } from '../config-
|
|
2
|
+
import { B as BoltdocsConfig } from '../config-DkZg5aCf.mjs';
|
|
3
|
+
export { a as BoltdocsThemeConfig } from '../config-DkZg5aCf.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options specifically for the Boltdocs Vite plugin.
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { B as BoltdocsConfig } from '../config-
|
|
3
|
-
export { a as BoltdocsThemeConfig } from '../config-
|
|
2
|
+
import { B as BoltdocsConfig } from '../config-DkZg5aCf.js';
|
|
3
|
+
export { a as BoltdocsThemeConfig } from '../config-DkZg5aCf.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options specifically for the Boltdocs Vite plugin.
|
package/dist/node/index.js
CHANGED
|
@@ -64,7 +64,8 @@ function escapeXml(str) {
|
|
|
64
64
|
}
|
|
65
65
|
function fileToRoutePath(relativePath) {
|
|
66
66
|
let cleanedPath = relativePath.split("/").map(stripNumberPrefix).join("/");
|
|
67
|
-
let routePath = cleanedPath.replace(
|
|
67
|
+
let routePath = cleanedPath.replace(/\/$/, "");
|
|
68
|
+
routePath = routePath.replace(/\.mdx?$/, "");
|
|
68
69
|
if (routePath === "index" || routePath.endsWith("/index")) {
|
|
69
70
|
routePath = routePath.replace(/index$/, "");
|
|
70
71
|
}
|
|
@@ -478,7 +479,12 @@ function parseDocFile(file, docsDir, basePath, config) {
|
|
|
478
479
|
}
|
|
479
480
|
}
|
|
480
481
|
const cleanRelativePath = parts.join("/");
|
|
481
|
-
|
|
482
|
+
let cleanRoutePath;
|
|
483
|
+
if (data.permalink) {
|
|
484
|
+
cleanRoutePath = data.permalink.startsWith("/") ? data.permalink : `/${data.permalink}`;
|
|
485
|
+
} else {
|
|
486
|
+
cleanRoutePath = fileToRoutePath(cleanRelativePath || "index.md");
|
|
487
|
+
}
|
|
482
488
|
let finalPath = basePath;
|
|
483
489
|
if (version) {
|
|
484
490
|
finalPath += "/" + version;
|
|
@@ -508,7 +514,11 @@ function parseDocFile(file, docsDir, basePath, config) {
|
|
|
508
514
|
headings.push({ level, text: escapeHtml(text), id });
|
|
509
515
|
}
|
|
510
516
|
const sanitizedTitle = data.title ? escapeHtml(data.title) : inferredTitle;
|
|
511
|
-
|
|
517
|
+
let sanitizedDescription = data.description ? escapeHtml(data.description) : "";
|
|
518
|
+
if (!sanitizedDescription && content) {
|
|
519
|
+
const summary = content.replace(/^#+.*$/gm, "").replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1").replace(/[_*`]/g, "").replace(/\n+/g, " ").trim().slice(0, 160);
|
|
520
|
+
sanitizedDescription = escapeHtml(summary);
|
|
521
|
+
}
|
|
512
522
|
const sanitizedBadge = data.badge ? escapeHtml(data.badge) : void 0;
|
|
513
523
|
return {
|
|
514
524
|
route: {
|
|
@@ -677,8 +687,8 @@ var CONFIG_FILES = [
|
|
|
677
687
|
"boltdocs.config.mjs",
|
|
678
688
|
"boltdocs.config.ts"
|
|
679
689
|
];
|
|
680
|
-
async function resolveConfig(docsDir) {
|
|
681
|
-
const projectRoot =
|
|
690
|
+
async function resolveConfig(docsDir, root = process.cwd()) {
|
|
691
|
+
const projectRoot = root;
|
|
682
692
|
const defaults = {
|
|
683
693
|
docsDir: import_path3.default.resolve(docsDir),
|
|
684
694
|
themeConfig: {
|
|
@@ -694,7 +704,8 @@ async function resolveConfig(docsDir) {
|
|
|
694
704
|
const configPath = import_path3.default.resolve(projectRoot, filename);
|
|
695
705
|
if (import_fs3.default.existsSync(configPath)) {
|
|
696
706
|
try {
|
|
697
|
-
const
|
|
707
|
+
const isTest = process.env.NODE_ENV === "test" || global.__vitest_worker__;
|
|
708
|
+
const fileUrl = (0, import_url.pathToFileURL)(configPath).href + (isTest ? "" : "?t=" + Date.now());
|
|
698
709
|
const mod = await import(fileUrl);
|
|
699
710
|
const userConfig = mod.default || mod;
|
|
700
711
|
const userThemeConfig = userConfig.themeConfig || userConfig;
|
|
@@ -806,12 +817,6 @@ async function generateStaticPages(options) {
|
|
|
806
817
|
return;
|
|
807
818
|
}
|
|
808
819
|
const template = import_fs4.default.readFileSync(templatePath, "utf-8");
|
|
809
|
-
let homePageComp;
|
|
810
|
-
if (config?._homePagePath) {
|
|
811
|
-
try {
|
|
812
|
-
} catch (e) {
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
820
|
await Promise.all(
|
|
816
821
|
routes.map(async (route) => {
|
|
817
822
|
const pageTitle = `${route.title} | ${siteTitle}`;
|
|
@@ -825,7 +830,8 @@ async function generateStaticPages(options) {
|
|
|
825
830
|
routes,
|
|
826
831
|
config: config || {},
|
|
827
832
|
modules: fakeModules,
|
|
828
|
-
homePage:
|
|
833
|
+
homePage: void 0
|
|
834
|
+
// No custom home page for now
|
|
829
835
|
});
|
|
830
836
|
const html = replaceMetaTags(template, {
|
|
831
837
|
title: escapeHtml(pageTitle),
|
|
@@ -1070,10 +1076,10 @@ var import_crypto2 = __toESM(require("crypto"));
|
|
|
1070
1076
|
init_cache();
|
|
1071
1077
|
var mdxCache = new TransformCache("mdx");
|
|
1072
1078
|
var mdxCacheLoaded = false;
|
|
1073
|
-
function boltdocsMdxPlugin(config) {
|
|
1079
|
+
function boltdocsMdxPlugin(config, compiler = import_rollup.default) {
|
|
1074
1080
|
const extraRemarkPlugins = config?.plugins?.flatMap((p) => p.remarkPlugins || []) || [];
|
|
1075
1081
|
const extraRehypePlugins = config?.plugins?.flatMap((p) => p.rehypePlugins || []) || [];
|
|
1076
|
-
const baseMdxPlugin = (
|
|
1082
|
+
const baseMdxPlugin = compiler({
|
|
1077
1083
|
remarkPlugins: [import_remark_gfm.default, import_remark_frontmatter.default, ...extraRemarkPlugins],
|
|
1078
1084
|
rehypePlugins: [
|
|
1079
1085
|
import_rehype_slug.default,
|
|
@@ -1089,6 +1095,9 @@ function boltdocsMdxPlugin(config) {
|
|
|
1089
1095
|
jsxRuntime: "automatic",
|
|
1090
1096
|
providerImportSource: "@mdx-js/react"
|
|
1091
1097
|
});
|
|
1098
|
+
if (baseMdxPlugin.isMock) {
|
|
1099
|
+
console.log("MDX PLUGIN IS MOCKED");
|
|
1100
|
+
}
|
|
1092
1101
|
return {
|
|
1093
1102
|
...baseMdxPlugin,
|
|
1094
1103
|
name: "vite-plugin-boltdocs-mdx",
|
|
@@ -1123,8 +1132,6 @@ function boltdocsMdxPlugin(config) {
|
|
|
1123
1132
|
);
|
|
1124
1133
|
if (result && typeof result === "object" && result.code) {
|
|
1125
1134
|
mdxCache.set(cacheKey, result.code);
|
|
1126
|
-
} else if (typeof result === "string") {
|
|
1127
|
-
mdxCache.set(cacheKey, result);
|
|
1128
1135
|
}
|
|
1129
1136
|
return result;
|
|
1130
1137
|
},
|
package/dist/node/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
normalizePath,
|
|
11
11
|
parseFrontmatter,
|
|
12
12
|
stripNumberPrefix
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-FFBNU6IJ.mjs";
|
|
14
14
|
|
|
15
15
|
// src/node/plugin/index.ts
|
|
16
16
|
import { loadEnv } from "vite";
|
|
@@ -61,7 +61,12 @@ function parseDocFile(file, docsDir, basePath, config) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
const cleanRelativePath = parts.join("/");
|
|
64
|
-
|
|
64
|
+
let cleanRoutePath;
|
|
65
|
+
if (data.permalink) {
|
|
66
|
+
cleanRoutePath = data.permalink.startsWith("/") ? data.permalink : `/${data.permalink}`;
|
|
67
|
+
} else {
|
|
68
|
+
cleanRoutePath = fileToRoutePath(cleanRelativePath || "index.md");
|
|
69
|
+
}
|
|
65
70
|
let finalPath = basePath;
|
|
66
71
|
if (version) {
|
|
67
72
|
finalPath += "/" + version;
|
|
@@ -91,7 +96,11 @@ function parseDocFile(file, docsDir, basePath, config) {
|
|
|
91
96
|
headings.push({ level, text: escapeHtml(text), id });
|
|
92
97
|
}
|
|
93
98
|
const sanitizedTitle = data.title ? escapeHtml(data.title) : inferredTitle;
|
|
94
|
-
|
|
99
|
+
let sanitizedDescription = data.description ? escapeHtml(data.description) : "";
|
|
100
|
+
if (!sanitizedDescription && content) {
|
|
101
|
+
const summary = content.replace(/^#+.*$/gm, "").replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1").replace(/[_*`]/g, "").replace(/\n+/g, " ").trim().slice(0, 160);
|
|
102
|
+
sanitizedDescription = escapeHtml(summary);
|
|
103
|
+
}
|
|
95
104
|
const sanitizedBadge = data.badge ? escapeHtml(data.badge) : void 0;
|
|
96
105
|
return {
|
|
97
106
|
route: {
|
|
@@ -260,8 +269,8 @@ var CONFIG_FILES = [
|
|
|
260
269
|
"boltdocs.config.mjs",
|
|
261
270
|
"boltdocs.config.ts"
|
|
262
271
|
];
|
|
263
|
-
async function resolveConfig(docsDir) {
|
|
264
|
-
const projectRoot =
|
|
272
|
+
async function resolveConfig(docsDir, root = process.cwd()) {
|
|
273
|
+
const projectRoot = root;
|
|
265
274
|
const defaults = {
|
|
266
275
|
docsDir: path2.resolve(docsDir),
|
|
267
276
|
themeConfig: {
|
|
@@ -277,7 +286,8 @@ async function resolveConfig(docsDir) {
|
|
|
277
286
|
const configPath = path2.resolve(projectRoot, filename);
|
|
278
287
|
if (fs.existsSync(configPath)) {
|
|
279
288
|
try {
|
|
280
|
-
const
|
|
289
|
+
const isTest = process.env.NODE_ENV === "test" || global.__vitest_worker__;
|
|
290
|
+
const fileUrl = pathToFileURL(configPath).href + (isTest ? "" : "?t=" + Date.now());
|
|
281
291
|
const mod = await import(fileUrl);
|
|
282
292
|
const userConfig = mod.default || mod;
|
|
283
293
|
const userThemeConfig = userConfig.themeConfig || userConfig;
|
|
@@ -385,12 +395,6 @@ async function generateStaticPages(options) {
|
|
|
385
395
|
return;
|
|
386
396
|
}
|
|
387
397
|
const template = fs2.readFileSync(templatePath, "utf-8");
|
|
388
|
-
let homePageComp;
|
|
389
|
-
if (config?._homePagePath) {
|
|
390
|
-
try {
|
|
391
|
-
} catch (e) {
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
398
|
await Promise.all(
|
|
395
399
|
routes.map(async (route) => {
|
|
396
400
|
const pageTitle = `${route.title} | ${siteTitle}`;
|
|
@@ -404,7 +408,8 @@ async function generateStaticPages(options) {
|
|
|
404
408
|
routes,
|
|
405
409
|
config: config || {},
|
|
406
410
|
modules: fakeModules,
|
|
407
|
-
homePage:
|
|
411
|
+
homePage: void 0
|
|
412
|
+
// No custom home page for now
|
|
408
413
|
});
|
|
409
414
|
const html = replaceMetaTags(template, {
|
|
410
415
|
title: escapeHtml(pageTitle),
|
|
@@ -430,7 +435,7 @@ async function generateStaticPages(options) {
|
|
|
430
435
|
console.log(
|
|
431
436
|
`[boltdocs] Generated ${routes.length} static pages + sitemap.xml`
|
|
432
437
|
);
|
|
433
|
-
const { flushCache } = await import("../cache-
|
|
438
|
+
const { flushCache } = await import("../cache-KNL5B4EE.mjs");
|
|
434
439
|
await flushCache();
|
|
435
440
|
}
|
|
436
441
|
|
|
@@ -612,7 +617,7 @@ function boltdocsPlugin(options = {}, passedConfig) {
|
|
|
612
617
|
if (!isBuild) return;
|
|
613
618
|
const outDir = viteConfig?.build?.outDir ? path4.resolve(viteConfig.root, viteConfig.build.outDir) : path4.resolve(process.cwd(), "dist");
|
|
614
619
|
await generateStaticPages({ docsDir, outDir, config });
|
|
615
|
-
const { flushCache } = await import("../cache-
|
|
620
|
+
const { flushCache } = await import("../cache-KNL5B4EE.mjs");
|
|
616
621
|
await flushCache();
|
|
617
622
|
}
|
|
618
623
|
},
|
|
@@ -646,10 +651,10 @@ import rehypePrettyCode from "rehype-pretty-code";
|
|
|
646
651
|
import crypto from "crypto";
|
|
647
652
|
var mdxCache = new TransformCache("mdx");
|
|
648
653
|
var mdxCacheLoaded = false;
|
|
649
|
-
function boltdocsMdxPlugin(config) {
|
|
654
|
+
function boltdocsMdxPlugin(config, compiler = mdxPlugin) {
|
|
650
655
|
const extraRemarkPlugins = config?.plugins?.flatMap((p) => p.remarkPlugins || []) || [];
|
|
651
656
|
const extraRehypePlugins = config?.plugins?.flatMap((p) => p.rehypePlugins || []) || [];
|
|
652
|
-
const baseMdxPlugin =
|
|
657
|
+
const baseMdxPlugin = compiler({
|
|
653
658
|
remarkPlugins: [remarkGfm, remarkFrontmatter, ...extraRemarkPlugins],
|
|
654
659
|
rehypePlugins: [
|
|
655
660
|
rehypeSlug,
|
|
@@ -665,6 +670,9 @@ function boltdocsMdxPlugin(config) {
|
|
|
665
670
|
jsxRuntime: "automatic",
|
|
666
671
|
providerImportSource: "@mdx-js/react"
|
|
667
672
|
});
|
|
673
|
+
if (baseMdxPlugin.isMock) {
|
|
674
|
+
console.log("MDX PLUGIN IS MOCKED");
|
|
675
|
+
}
|
|
668
676
|
return {
|
|
669
677
|
...baseMdxPlugin,
|
|
670
678
|
name: "vite-plugin-boltdocs-mdx",
|
|
@@ -699,8 +707,6 @@ function boltdocsMdxPlugin(config) {
|
|
|
699
707
|
);
|
|
700
708
|
if (result && typeof result === "object" && result.code) {
|
|
701
709
|
mdxCache.set(cacheKey, result.code);
|
|
702
|
-
} else if (typeof result === "string") {
|
|
703
|
-
mdxCache.set(cacheKey, result);
|
|
704
710
|
}
|
|
705
711
|
return result;
|
|
706
712
|
},
|
|
@@ -27,6 +27,8 @@ interface ComponentRoute {
|
|
|
27
27
|
text: string;
|
|
28
28
|
id: string;
|
|
29
29
|
}[];
|
|
30
|
+
/** The page summary or description */
|
|
31
|
+
description?: string;
|
|
30
32
|
/** The locale this route belongs to, if i18n is configured */
|
|
31
33
|
locale?: string;
|
|
32
34
|
/** The version this route belongs to, if versioning is configured */
|
|
@@ -27,6 +27,8 @@ interface ComponentRoute {
|
|
|
27
27
|
text: string;
|
|
28
28
|
id: string;
|
|
29
29
|
}[];
|
|
30
|
+
/** The page summary or description */
|
|
31
|
+
description?: string;
|
|
30
32
|
/** The locale this route belongs to, if i18n is configured */
|
|
31
33
|
locale?: string;
|
|
32
34
|
/** The version this route belongs to, if versioning is configured */
|
package/package.json
CHANGED
package/src/client/app/index.tsx
CHANGED
|
@@ -27,11 +27,7 @@ export function useConfig() {
|
|
|
27
27
|
return useContext(ConfigContext);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
import("../theme/components/CodeBlock").then((m) => ({
|
|
32
|
-
default: m.CodeBlock,
|
|
33
|
-
})),
|
|
34
|
-
);
|
|
30
|
+
import { CodeBlock } from "../theme/components/CodeBlock";
|
|
35
31
|
const Video = lazy(() =>
|
|
36
32
|
import("../theme/components/Video").then((m) => ({ default: m.Video })),
|
|
37
33
|
);
|
|
@@ -77,13 +73,7 @@ const mdxComponents = {
|
|
|
77
73
|
h4: (props: any) => <Heading level={4} {...props} />,
|
|
78
74
|
h5: (props: any) => <Heading level={5} {...props} />,
|
|
79
75
|
h6: (props: any) => <Heading level={6} {...props} />,
|
|
80
|
-
pre: (props: any) => {
|
|
81
|
-
return (
|
|
82
|
-
<Suspense fallback={<div className="code-block-skeleton" />}>
|
|
83
|
-
<CodeBlock {...props}>{props.children}</CodeBlock>
|
|
84
|
-
</Suspense>
|
|
85
|
-
);
|
|
86
|
-
},
|
|
76
|
+
pre: (props: any) => <CodeBlock {...props}>{props.children}</CodeBlock>,
|
|
87
77
|
video: (props: any) => (
|
|
88
78
|
<Suspense fallback={<div className="video-skeleton" />}>
|
|
89
79
|
<Video {...props} />
|
|
@@ -3,10 +3,12 @@ import { ComponentRoute } from "../types";
|
|
|
3
3
|
|
|
4
4
|
interface PreloadContextType {
|
|
5
5
|
preload: (path: string) => void;
|
|
6
|
+
routes: ComponentRoute[];
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
const PreloadContext = createContext<PreloadContextType>({
|
|
9
10
|
preload: () => {},
|
|
11
|
+
routes: [],
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
export function usePreload() {
|
|
@@ -49,7 +51,7 @@ export function PreloadProvider({
|
|
|
49
51
|
);
|
|
50
52
|
|
|
51
53
|
return (
|
|
52
|
-
<PreloadContext.Provider value={{ preload }}>
|
|
54
|
+
<PreloadContext.Provider value={{ preload, routes }}>
|
|
53
55
|
{children}
|
|
54
56
|
</PreloadContext.Provider>
|
|
55
57
|
);
|
|
@@ -16,17 +16,6 @@ export function CodeBlock({ children, ...props }: CodeBlockProps) {
|
|
|
16
16
|
const [copied, setCopied] = useState(false);
|
|
17
17
|
const preRef = useRef<HTMLPreElement>(null);
|
|
18
18
|
|
|
19
|
-
// Extract language from the child <code> element's data-language or className
|
|
20
|
-
let language = "";
|
|
21
|
-
if (React.isValidElement(children)) {
|
|
22
|
-
const childProps = children.props as any;
|
|
23
|
-
language = childProps?.["data-language"] || "";
|
|
24
|
-
if (!language && childProps?.className) {
|
|
25
|
-
const match = childProps.className.match(/language-(\w+)/);
|
|
26
|
-
if (match) language = match[1];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
19
|
const handleCopy = useCallback(async () => {
|
|
31
20
|
const code = preRef.current?.textContent || "";
|
|
32
21
|
copyToClipboard(code);
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
right: 0.75rem;
|
|
219
219
|
z-index: 50;
|
|
220
220
|
padding: 0.4rem;
|
|
221
|
-
background-color:
|
|
221
|
+
background-color: var(--ld-surface);
|
|
222
222
|
backdrop-filter: blur(8px);
|
|
223
223
|
-webkit-backdrop-filter: blur(8px);
|
|
224
224
|
border: 1px solid var(--ld-border-subtle);
|
|
@@ -227,14 +227,10 @@
|
|
|
227
227
|
cursor: pointer;
|
|
228
228
|
transition: all 0.2s ease;
|
|
229
229
|
opacity: 0;
|
|
230
|
-
visibility: hidden;
|
|
231
|
-
pointer-events: none;
|
|
232
230
|
}
|
|
233
231
|
|
|
234
232
|
.code-block-wrapper:hover .code-block-copy {
|
|
235
233
|
opacity: 1;
|
|
236
|
-
visibility: visible;
|
|
237
|
-
pointer-events: auto;
|
|
238
234
|
}
|
|
239
235
|
|
|
240
236
|
.code-block-copy:hover {
|
|
@@ -97,25 +97,44 @@ function useLocalizedTo(to: RouterLinkProps["to"]) {
|
|
|
97
97
|
return finalPath === basePath ? basePath : finalPath;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
import { LinkPreview } from "./LinkPreview";
|
|
101
|
+
|
|
100
102
|
export interface LinkProps extends Omit<RouterLinkProps, "prefetch"> {
|
|
101
103
|
/** Should prefetch the page on hover? Options: 'hover' | 'none'. Default 'hover' */
|
|
102
104
|
boltdocsPrefetch?: "hover" | "none";
|
|
105
|
+
/** Should show a preview tooltip on hover? Default true */
|
|
106
|
+
boltdocsPreview?: boolean;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
106
110
|
(props, ref) => {
|
|
107
111
|
const {
|
|
108
112
|
boltdocsPrefetch = "hover",
|
|
113
|
+
boltdocsPreview = true,
|
|
109
114
|
onMouseEnter,
|
|
115
|
+
onMouseLeave,
|
|
110
116
|
onFocus,
|
|
117
|
+
onBlur,
|
|
111
118
|
onClick,
|
|
112
119
|
to,
|
|
113
120
|
...rest
|
|
114
121
|
} = props;
|
|
115
122
|
const localizedTo = useLocalizedTo(to);
|
|
116
|
-
const { preload } = usePreload();
|
|
123
|
+
const { preload, routes } = usePreload();
|
|
124
|
+
const config = useConfig();
|
|
117
125
|
const navigate = useNavigate();
|
|
118
126
|
|
|
127
|
+
const shouldShowPreview =
|
|
128
|
+
boltdocsPreview && config?.themeConfig?.linkPreview !== false;
|
|
129
|
+
|
|
130
|
+
const [preview, setPreview] = React.useState<{
|
|
131
|
+
visible: boolean;
|
|
132
|
+
x: number;
|
|
133
|
+
y: number;
|
|
134
|
+
title: string;
|
|
135
|
+
summary?: string;
|
|
136
|
+
}>({ visible: false, x: 0, y: 0, title: "" });
|
|
137
|
+
|
|
119
138
|
const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
120
139
|
onMouseEnter?.(e);
|
|
121
140
|
if (
|
|
@@ -125,6 +144,37 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
|
125
144
|
) {
|
|
126
145
|
preload(localizedTo);
|
|
127
146
|
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
shouldShowPreview &&
|
|
150
|
+
typeof localizedTo === "string" &&
|
|
151
|
+
localizedTo.startsWith("/")
|
|
152
|
+
) {
|
|
153
|
+
const cleanPath = localizedTo.split("#")[0].split("?")[0];
|
|
154
|
+
const route = routes.find(
|
|
155
|
+
(r) => r.path === cleanPath || (cleanPath === "/" && r.path === ""),
|
|
156
|
+
);
|
|
157
|
+
if (route) {
|
|
158
|
+
setPreview({
|
|
159
|
+
visible: true,
|
|
160
|
+
x: e.clientX,
|
|
161
|
+
y: e.clientY,
|
|
162
|
+
title: route.title,
|
|
163
|
+
summary: route.description,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
170
|
+
if (preview.visible) {
|
|
171
|
+
setPreview((prev) => ({ ...prev, x: e.clientX, y: e.clientY }));
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const handleMouseLeave = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
176
|
+
onMouseLeave?.(e);
|
|
177
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
128
178
|
};
|
|
129
179
|
|
|
130
180
|
const handleFocus = (e: React.FocusEvent<HTMLAnchorElement>) => {
|
|
@@ -138,9 +188,15 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
|
138
188
|
}
|
|
139
189
|
};
|
|
140
190
|
|
|
191
|
+
const handleBlur = (e: React.FocusEvent<HTMLAnchorElement>) => {
|
|
192
|
+
onBlur?.(e);
|
|
193
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
194
|
+
};
|
|
195
|
+
|
|
141
196
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
142
197
|
// Allow user onClick to handle defaults or custom logic
|
|
143
198
|
onClick?.(e);
|
|
199
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
144
200
|
|
|
145
201
|
// If default prevented or not a simple left click, don't handle
|
|
146
202
|
if (
|
|
@@ -164,14 +220,28 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
|
164
220
|
};
|
|
165
221
|
|
|
166
222
|
return (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
223
|
+
<>
|
|
224
|
+
<RouterLink
|
|
225
|
+
ref={ref}
|
|
226
|
+
to={localizedTo}
|
|
227
|
+
onMouseEnter={handleMouseEnter}
|
|
228
|
+
onMouseMove={handleMouseMove}
|
|
229
|
+
onMouseLeave={handleMouseLeave}
|
|
230
|
+
onFocus={handleFocus}
|
|
231
|
+
onBlur={handleBlur}
|
|
232
|
+
onClick={handleClick}
|
|
233
|
+
{...rest}
|
|
234
|
+
/>
|
|
235
|
+
{shouldShowPreview && (
|
|
236
|
+
<LinkPreview
|
|
237
|
+
isVisible={preview.visible}
|
|
238
|
+
title={preview.title}
|
|
239
|
+
summary={preview.summary}
|
|
240
|
+
x={preview.x}
|
|
241
|
+
y={preview.y}
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
244
|
+
</>
|
|
175
245
|
);
|
|
176
246
|
},
|
|
177
247
|
);
|
|
@@ -180,23 +250,40 @@ Link.displayName = "Link";
|
|
|
180
250
|
export interface NavLinkProps extends Omit<RouterNavLinkProps, "prefetch"> {
|
|
181
251
|
/** Should prefetch the page on hover? Options: 'hover' | 'none'. Default 'hover' */
|
|
182
252
|
boltdocsPrefetch?: "hover" | "none";
|
|
253
|
+
/** Should show a preview tooltip on hover? Default true */
|
|
254
|
+
boltdocsPreview?: boolean;
|
|
183
255
|
}
|
|
184
256
|
|
|
185
257
|
export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
186
258
|
(props, ref) => {
|
|
187
259
|
const {
|
|
188
260
|
boltdocsPrefetch = "hover",
|
|
261
|
+
boltdocsPreview = true,
|
|
189
262
|
onMouseEnter,
|
|
263
|
+
onMouseLeave,
|
|
190
264
|
onFocus,
|
|
265
|
+
onBlur,
|
|
191
266
|
onClick,
|
|
192
267
|
to,
|
|
193
268
|
...rest
|
|
194
269
|
} = props;
|
|
195
270
|
|
|
196
271
|
const localizedTo = useLocalizedTo(to);
|
|
197
|
-
const { preload } = usePreload();
|
|
272
|
+
const { preload, routes } = usePreload();
|
|
273
|
+
const config = useConfig();
|
|
198
274
|
const navigate = useNavigate();
|
|
199
275
|
|
|
276
|
+
const shouldShowPreview =
|
|
277
|
+
boltdocsPreview && config?.themeConfig?.linkPreview !== false;
|
|
278
|
+
|
|
279
|
+
const [preview, setPreview] = React.useState<{
|
|
280
|
+
visible: boolean;
|
|
281
|
+
x: number;
|
|
282
|
+
y: number;
|
|
283
|
+
title: string;
|
|
284
|
+
summary?: string;
|
|
285
|
+
}>({ visible: false, x: 0, y: 0, title: "" });
|
|
286
|
+
|
|
200
287
|
const handleMouseEnter = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
201
288
|
onMouseEnter?.(e);
|
|
202
289
|
if (
|
|
@@ -206,6 +293,37 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
|
206
293
|
) {
|
|
207
294
|
preload(localizedTo);
|
|
208
295
|
}
|
|
296
|
+
|
|
297
|
+
if (
|
|
298
|
+
shouldShowPreview &&
|
|
299
|
+
typeof localizedTo === "string" &&
|
|
300
|
+
localizedTo.startsWith("/")
|
|
301
|
+
) {
|
|
302
|
+
const cleanPath = localizedTo.split("#")[0].split("?")[0];
|
|
303
|
+
const route = routes.find(
|
|
304
|
+
(r) => r.path === cleanPath || (cleanPath === "/" && r.path === ""),
|
|
305
|
+
);
|
|
306
|
+
if (route) {
|
|
307
|
+
setPreview({
|
|
308
|
+
visible: true,
|
|
309
|
+
x: e.clientX,
|
|
310
|
+
y: e.clientY,
|
|
311
|
+
title: route.title,
|
|
312
|
+
summary: route.description,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
319
|
+
if (preview.visible) {
|
|
320
|
+
setPreview((prev) => ({ ...prev, x: e.clientX, y: e.clientY }));
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const handleMouseLeave = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
325
|
+
onMouseLeave?.(e);
|
|
326
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
209
327
|
};
|
|
210
328
|
|
|
211
329
|
const handleFocus = (e: React.FocusEvent<HTMLAnchorElement>) => {
|
|
@@ -219,8 +337,14 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
|
219
337
|
}
|
|
220
338
|
};
|
|
221
339
|
|
|
340
|
+
const handleBlur = (e: React.FocusEvent<HTMLAnchorElement>) => {
|
|
341
|
+
onBlur?.(e);
|
|
342
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
343
|
+
};
|
|
344
|
+
|
|
222
345
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
223
346
|
onClick?.(e);
|
|
347
|
+
setPreview((prev) => ({ ...prev, visible: false }));
|
|
224
348
|
if (
|
|
225
349
|
e.defaultPrevented ||
|
|
226
350
|
e.button !== 0 ||
|
|
@@ -240,14 +364,28 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
|
240
364
|
};
|
|
241
365
|
|
|
242
366
|
return (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
367
|
+
<>
|
|
368
|
+
<RouterNavLink
|
|
369
|
+
ref={ref}
|
|
370
|
+
to={localizedTo}
|
|
371
|
+
onMouseEnter={handleMouseEnter}
|
|
372
|
+
onMouseMove={handleMouseMove}
|
|
373
|
+
onMouseLeave={handleMouseLeave}
|
|
374
|
+
onFocus={handleFocus}
|
|
375
|
+
onBlur={handleBlur}
|
|
376
|
+
onClick={handleClick}
|
|
377
|
+
{...rest}
|
|
378
|
+
/>
|
|
379
|
+
{shouldShowPreview && (
|
|
380
|
+
<LinkPreview
|
|
381
|
+
isVisible={preview.visible}
|
|
382
|
+
title={preview.title}
|
|
383
|
+
summary={preview.summary}
|
|
384
|
+
x={preview.x}
|
|
385
|
+
y={preview.y}
|
|
386
|
+
/>
|
|
387
|
+
)}
|
|
388
|
+
</>
|
|
251
389
|
);
|
|
252
390
|
},
|
|
253
391
|
);
|