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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import "./link-preview.css";
|
|
4
|
+
|
|
5
|
+
interface LinkPreviewProps {
|
|
6
|
+
isVisible: boolean;
|
|
7
|
+
title: string;
|
|
8
|
+
summary?: string;
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function LinkPreview({
|
|
14
|
+
isVisible,
|
|
15
|
+
title,
|
|
16
|
+
summary,
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
}: LinkPreviewProps) {
|
|
20
|
+
const [mounted, setMounted] = useState(false);
|
|
21
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
22
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setMounted(true);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isVisible && ref.current) {
|
|
30
|
+
const rect = ref.current.getBoundingClientRect();
|
|
31
|
+
const padding = 15;
|
|
32
|
+
|
|
33
|
+
let top = y + padding;
|
|
34
|
+
let left = x + padding;
|
|
35
|
+
|
|
36
|
+
// Keep within viewport
|
|
37
|
+
if (left + rect.width > window.innerWidth) {
|
|
38
|
+
left = x - rect.width - padding;
|
|
39
|
+
}
|
|
40
|
+
if (top + rect.height > window.innerHeight) {
|
|
41
|
+
top = y - rect.height - padding;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setPosition({ top, left });
|
|
45
|
+
}
|
|
46
|
+
}, [isVisible, x, y]);
|
|
47
|
+
|
|
48
|
+
if (!mounted) return null;
|
|
49
|
+
|
|
50
|
+
return createPortal(
|
|
51
|
+
<div
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={`boltdocs-link-preview ${isVisible ? "is-visible" : ""}`}
|
|
54
|
+
style={{
|
|
55
|
+
top: position.top,
|
|
56
|
+
left: position.left,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<span className="boltdocs-link-preview-title">{title}</span>
|
|
60
|
+
{summary && <p className="boltdocs-link-preview-summary">{summary}</p>}
|
|
61
|
+
</div>,
|
|
62
|
+
document.body,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.boltdocs-link-preview {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 1000;
|
|
4
|
+
width: 320px;
|
|
5
|
+
padding: 1rem;
|
|
6
|
+
background-color: var(--ld-navbar-bg);
|
|
7
|
+
backdrop-filter: blur(var(--ld-navbar-blur));
|
|
8
|
+
-webkit-backdrop-filter: blur(var(--ld-navbar-blur));
|
|
9
|
+
border: 1px solid var(--ld-border-subtle);
|
|
10
|
+
border-radius: var(--ld-radius-md);
|
|
11
|
+
box-shadow:
|
|
12
|
+
0 10px 25px -5px rgba(0, 0, 0, 0.1),
|
|
13
|
+
0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
14
|
+
pointer-events: none;
|
|
15
|
+
opacity: 0;
|
|
16
|
+
transform: translateY(10px) scale(0.95);
|
|
17
|
+
transition:
|
|
18
|
+
opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
19
|
+
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
20
|
+
font-family: var(--ld-font-sans);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.boltdocs-link-preview.is-visible {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
transform: translateY(0) scale(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.boltdocs-link-preview-title {
|
|
29
|
+
display: block;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
font-size: 0.95rem;
|
|
32
|
+
color: var(--ld-text-main);
|
|
33
|
+
margin-bottom: 0.5rem;
|
|
34
|
+
line-height: 1.4;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.boltdocs-link-preview-summary {
|
|
38
|
+
display: block;
|
|
39
|
+
font-size: 0.85rem;
|
|
40
|
+
color: var(--ld-text-muted);
|
|
41
|
+
line-height: 1.5;
|
|
42
|
+
display: -webkit-box;
|
|
43
|
+
-webkit-line-clamp: 4;
|
|
44
|
+
line-clamp: 4;
|
|
45
|
+
-webkit-box-orient: vertical;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Dark mode adjustments */
|
|
50
|
+
[data-theme="dark"] .boltdocs-link-preview {
|
|
51
|
+
background-color: var(--ld-navbar-bg);
|
|
52
|
+
border-color: var(--ld-border-subtle);
|
|
53
|
+
box-shadow:
|
|
54
|
+
0 20px 25px -5px rgba(0, 0, 0, 0.3),
|
|
55
|
+
0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-theme="dark"] .boltdocs-link-preview-title {
|
|
59
|
+
color: #f8fafc;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-theme="dark"] .boltdocs-link-preview-summary {
|
|
63
|
+
color: #94a3b8;
|
|
64
|
+
}
|
package/src/client/types.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ComponentRoute {
|
|
|
23
23
|
groupPosition?: number;
|
|
24
24
|
/** Extracted markdown headings for search indexing */
|
|
25
25
|
headings?: { level: number; text: string; id: string }[];
|
|
26
|
+
/** The page summary or description */
|
|
27
|
+
description?: string;
|
|
26
28
|
/** The locale this route belongs to, if i18n is configured */
|
|
27
29
|
locale?: string;
|
|
28
30
|
/** The version this route belongs to, if versioning is configured */
|
package/src/node/config.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface BoltdocsThemeConfig {
|
|
|
53
53
|
githubRepo?: string;
|
|
54
54
|
/** Whether to show the 'Powered by LiteDocs' badge in the sidebar (default: true) */
|
|
55
55
|
poweredBy?: boolean;
|
|
56
|
+
/** Whether to show a preview tooltip on internal links hover (default: true) */
|
|
57
|
+
linkPreview?: boolean;
|
|
56
58
|
/** Granular layout customization props */
|
|
57
59
|
layoutProps?: {
|
|
58
60
|
navbar?: any;
|
|
@@ -139,11 +141,15 @@ export const CONFIG_FILES = [
|
|
|
139
141
|
* Loads user's configuration file (e.g., `boltdocs.config.js` or `boltdocs.config.ts`) if it exists,
|
|
140
142
|
* merges it with the default configuration, and returns the final `BoltdocsConfig`.
|
|
141
143
|
*
|
|
142
|
-
* @param docsDir - The
|
|
143
|
-
* @
|
|
144
|
+
* @param docsDir - The directory containing the documentation files
|
|
145
|
+
* @param root - The project root directory (defaults to process.cwd())
|
|
146
|
+
* @returns The merged configuration object
|
|
144
147
|
*/
|
|
145
|
-
export async function resolveConfig(
|
|
146
|
-
|
|
148
|
+
export async function resolveConfig(
|
|
149
|
+
docsDir: string,
|
|
150
|
+
root: string = process.cwd(),
|
|
151
|
+
): Promise<BoltdocsConfig> {
|
|
152
|
+
const projectRoot = root;
|
|
147
153
|
|
|
148
154
|
const defaults: BoltdocsConfig = {
|
|
149
155
|
docsDir: path.resolve(docsDir),
|
|
@@ -162,8 +168,11 @@ export async function resolveConfig(docsDir: string): Promise<BoltdocsConfig> {
|
|
|
162
168
|
const configPath = path.resolve(projectRoot, filename);
|
|
163
169
|
if (fs.existsSync(configPath)) {
|
|
164
170
|
try {
|
|
165
|
-
// Add a timestamp query parameter to bust the ESM cache
|
|
166
|
-
const
|
|
171
|
+
// Add a timestamp query parameter to bust the ESM cache in dev
|
|
172
|
+
const isTest =
|
|
173
|
+
process.env.NODE_ENV === "test" || (global as any).__vitest_worker__;
|
|
174
|
+
const fileUrl =
|
|
175
|
+
pathToFileURL(configPath).href + (isTest ? "" : "?t=" + Date.now());
|
|
167
176
|
const mod = await import(fileUrl);
|
|
168
177
|
const userConfig = mod.default || mod;
|
|
169
178
|
|
package/src/node/mdx.ts
CHANGED
|
@@ -24,15 +24,19 @@ let mdxCacheLoaded = false;
|
|
|
24
24
|
* Also wraps the plugin with a persistent cache to avoid re-compiling unchanged MDX files.
|
|
25
25
|
*
|
|
26
26
|
* @param config - The Boltdocs configuration containing custom plugins
|
|
27
|
+
* @param compiler - The MDX compiler plugin (for testing)
|
|
27
28
|
* @returns A Vite plugin configured for MDX parsing with caching
|
|
28
29
|
*/
|
|
29
|
-
export function boltdocsMdxPlugin(
|
|
30
|
+
export function boltdocsMdxPlugin(
|
|
31
|
+
config?: BoltdocsConfig,
|
|
32
|
+
compiler = mdxPlugin,
|
|
33
|
+
): Plugin {
|
|
30
34
|
const extraRemarkPlugins =
|
|
31
35
|
config?.plugins?.flatMap((p) => p.remarkPlugins || []) || [];
|
|
32
36
|
const extraRehypePlugins =
|
|
33
37
|
config?.plugins?.flatMap((p) => p.rehypePlugins || []) || [];
|
|
34
38
|
|
|
35
|
-
const baseMdxPlugin =
|
|
39
|
+
const baseMdxPlugin = compiler({
|
|
36
40
|
remarkPlugins: [remarkGfm, remarkFrontmatter, ...extraRemarkPlugins],
|
|
37
41
|
rehypePlugins: [
|
|
38
42
|
rehypeSlug,
|
|
@@ -49,6 +53,11 @@ export function boltdocsMdxPlugin(config?: BoltdocsConfig): Plugin {
|
|
|
49
53
|
providerImportSource: "@mdx-js/react",
|
|
50
54
|
}) as Plugin;
|
|
51
55
|
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
if (baseMdxPlugin.isMock) {
|
|
58
|
+
console.log("MDX PLUGIN IS MOCKED");
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
return {
|
|
53
62
|
...baseMdxPlugin,
|
|
54
63
|
name: "vite-plugin-boltdocs-mdx",
|
|
@@ -90,8 +99,6 @@ export function boltdocsMdxPlugin(config?: BoltdocsConfig): Plugin {
|
|
|
90
99
|
|
|
91
100
|
if (result && typeof result === "object" && result.code) {
|
|
92
101
|
mdxCache.set(cacheKey, result.code);
|
|
93
|
-
} else if (typeof result === "string") {
|
|
94
|
-
mdxCache.set(cacheKey, result);
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
return result;
|
|
@@ -70,7 +70,16 @@ export function parseDocFile(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const cleanRelativePath = parts.join("/");
|
|
73
|
-
|
|
73
|
+
|
|
74
|
+
let cleanRoutePath: string;
|
|
75
|
+
if (data.permalink) {
|
|
76
|
+
// If a permalink is specified, ensure it starts with a slash
|
|
77
|
+
cleanRoutePath = data.permalink.startsWith("/")
|
|
78
|
+
? data.permalink
|
|
79
|
+
: `/${data.permalink}`;
|
|
80
|
+
} else {
|
|
81
|
+
cleanRoutePath = fileToRoutePath(cleanRelativePath || "index.md");
|
|
82
|
+
}
|
|
74
83
|
|
|
75
84
|
let finalPath = basePath;
|
|
76
85
|
if (version) {
|
|
@@ -113,9 +122,22 @@ export function parseDocFile(
|
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
const sanitizedTitle = data.title ? escapeHtml(data.title) : inferredTitle;
|
|
116
|
-
|
|
125
|
+
let sanitizedDescription = data.description
|
|
117
126
|
? escapeHtml(data.description)
|
|
118
127
|
: "";
|
|
128
|
+
|
|
129
|
+
// If no description is provided, extract a summary from the content
|
|
130
|
+
if (!sanitizedDescription && content) {
|
|
131
|
+
const summary = content
|
|
132
|
+
.replace(/^#+.*$/gm, "") // Remove headers
|
|
133
|
+
.replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1") // Simplify links
|
|
134
|
+
.replace(/[_*`]/g, "") // Remove formatting
|
|
135
|
+
.replace(/\n+/g, " ") // Normalize whitespace
|
|
136
|
+
.trim()
|
|
137
|
+
.slice(0, 160);
|
|
138
|
+
sanitizedDescription = escapeHtml(summary);
|
|
139
|
+
}
|
|
140
|
+
|
|
119
141
|
const sanitizedBadge = data.badge ? escapeHtml(data.badge) : undefined;
|
|
120
142
|
|
|
121
143
|
return {
|
package/src/node/ssg/index.ts
CHANGED
|
@@ -49,15 +49,6 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
|
49
49
|
}
|
|
50
50
|
const template = fs.readFileSync(templatePath, "utf-8");
|
|
51
51
|
|
|
52
|
-
// Load user's homePage if configured
|
|
53
|
-
let homePageComp;
|
|
54
|
-
if ((config as any)?._homePagePath) {
|
|
55
|
-
try {
|
|
56
|
-
// Simplistic: if there's a custom home page compiled, we'd need it available to SSR.
|
|
57
|
-
// In a full framework this is complex, but for Boltdocs we assume it's bundled if needed.
|
|
58
|
-
} catch (e) {}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
// Generate an HTML file for each route concurrently
|
|
62
53
|
await Promise.all(
|
|
63
54
|
routes.map(async (route) => {
|
|
@@ -74,7 +65,7 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
|
74
65
|
routes: routes,
|
|
75
66
|
config: config || {},
|
|
76
67
|
modules: fakeModules,
|
|
77
|
-
homePage:
|
|
68
|
+
homePage: undefined, // No custom home page for now
|
|
78
69
|
});
|
|
79
70
|
|
|
80
71
|
const html = replaceMetaTags(template, {
|
package/src/node/utils.ts
CHANGED
|
@@ -113,7 +113,10 @@ export function fileToRoutePath(relativePath: string): string {
|
|
|
113
113
|
// Strip number prefixes from every segment
|
|
114
114
|
let cleanedPath = relativePath.split("/").map(stripNumberPrefix).join("/");
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// Remove trailing slash if present
|
|
117
|
+
let routePath = cleanedPath.replace(/\/$/, "");
|
|
118
|
+
|
|
119
|
+
routePath = routePath.replace(/\.mdx?$/, "");
|
|
117
120
|
|
|
118
121
|
// Handle index files → directory root
|
|
119
122
|
if (routePath === "index" || routePath.endsWith("/index")) {
|
package/dist/chunk-KS5B3O6W.mjs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
copyToClipboard
|
|
3
|
-
} from "./chunk-FMTOYQLO.mjs";
|
|
4
|
-
|
|
5
|
-
// src/client/theme/components/CodeBlock/CodeBlock.tsx
|
|
6
|
-
import React, { useState, useRef, useCallback } from "react";
|
|
7
|
-
import { Copy, Check } from "lucide-react";
|
|
8
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
function CodeBlock({ children, ...props }) {
|
|
10
|
-
const [copied, setCopied] = useState(false);
|
|
11
|
-
const preRef = useRef(null);
|
|
12
|
-
let language = "";
|
|
13
|
-
if (React.isValidElement(children)) {
|
|
14
|
-
const childProps = children.props;
|
|
15
|
-
language = childProps?.["data-language"] || "";
|
|
16
|
-
if (!language && childProps?.className) {
|
|
17
|
-
const match = childProps.className.match(/language-(\w+)/);
|
|
18
|
-
if (match) language = match[1];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
const handleCopy = useCallback(async () => {
|
|
22
|
-
const code = preRef.current?.textContent || "";
|
|
23
|
-
copyToClipboard(code);
|
|
24
|
-
setCopied(true);
|
|
25
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
26
|
-
}, []);
|
|
27
|
-
return /* @__PURE__ */ jsxs("div", { className: "code-block-wrapper", children: [
|
|
28
|
-
/* @__PURE__ */ jsx(
|
|
29
|
-
"button",
|
|
30
|
-
{
|
|
31
|
-
className: `code-block-copy ${copied ? "copied" : ""}`,
|
|
32
|
-
onClick: handleCopy,
|
|
33
|
-
"aria-label": "Copy code",
|
|
34
|
-
children: copied ? /* @__PURE__ */ jsx(Check, { size: 16 }) : /* @__PURE__ */ jsx(Copy, { size: 16 })
|
|
35
|
-
}
|
|
36
|
-
),
|
|
37
|
-
/* @__PURE__ */ jsx("pre", { ref: preRef, ...props, children })
|
|
38
|
-
] });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
CodeBlock
|
|
43
|
-
};
|