@barodoc/theme-docs 5.0.0 → 6.1.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/package.json +6 -2
- package/src/components/Contributors.astro +71 -0
- package/src/components/KeyboardShortcuts.astro +108 -0
- package/src/components/VersionSwitcher.tsx +79 -0
- package/src/components/index.ts +6 -0
- package/src/components/mdx/ApiEndpoint.tsx +35 -0
- package/src/components/mdx/ApiPlayground.tsx +789 -0
- package/src/components/mdx/ImageZoom.tsx +35 -0
- package/src/components/mdx/Video.tsx +71 -0
- package/src/index.ts +27 -1
- package/src/layouts/BaseLayout.astro +3 -0
- package/src/layouts/BlogLayout.astro +93 -0
- package/src/layouts/DocsLayout.astro +33 -1
- package/src/pages/blog/[...slug].astro +39 -0
- package/src/pages/blog/index.astro +92 -0
- package/src/pages/changelog/index.astro +72 -0
- package/src/pages/docs/[...slug].astro +4 -0
- package/src/styles/global.css +1436 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barodoc/theme-docs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Documentation theme for Barodoc",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -28,9 +28,13 @@
|
|
|
28
28
|
"class-variance-authority": "^0.7.1",
|
|
29
29
|
"clsx": "^2.1.1",
|
|
30
30
|
"lucide-react": "^0.563.0",
|
|
31
|
+
"medium-zoom": "^1.1.0",
|
|
31
32
|
"mermaid": "^11.12.2",
|
|
33
|
+
"reading-time": "^1.5.0",
|
|
34
|
+
"rehype-katex": "^7.0.1",
|
|
35
|
+
"remark-math": "^6.0.0",
|
|
32
36
|
"tailwind-merge": "^3.4.0",
|
|
33
|
-
"@barodoc/core": "
|
|
37
|
+
"@barodoc/core": "6.0.0"
|
|
34
38
|
},
|
|
35
39
|
"peerDependencies": {
|
|
36
40
|
"astro": "^5.0.0",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
filePath?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Contributor {
|
|
9
|
+
name: string;
|
|
10
|
+
email: string;
|
|
11
|
+
avatarUrl: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { filePath } = Astro.props;
|
|
15
|
+
|
|
16
|
+
let contributors: Contributor[] = [];
|
|
17
|
+
|
|
18
|
+
if (filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = execSync(
|
|
21
|
+
`git log --format='%aN|%aE' -- "${filePath}"`,
|
|
22
|
+
{ encoding: "utf-8", timeout: 5000 }
|
|
23
|
+
).trim();
|
|
24
|
+
|
|
25
|
+
if (raw) {
|
|
26
|
+
const seen = new Map<string, Contributor>();
|
|
27
|
+
for (const line of raw.split("\n")) {
|
|
28
|
+
const [name, email] = line.split("|");
|
|
29
|
+
if (!name || seen.has(email)) continue;
|
|
30
|
+
const hash = email.trim().toLowerCase();
|
|
31
|
+
seen.set(email, {
|
|
32
|
+
name: name.trim(),
|
|
33
|
+
email: email.trim(),
|
|
34
|
+
avatarUrl: `https://gravatar.com/avatar/${await computeHash(hash)}?s=64&d=mp`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
contributors = Array.from(seen.values());
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// git not available or file not tracked
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function computeHash(input: string): Promise<string> {
|
|
45
|
+
const encoder = new TextEncoder();
|
|
46
|
+
const data = encoder.encode(input);
|
|
47
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
48
|
+
return Array.from(new Uint8Array(hashBuffer))
|
|
49
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
50
|
+
.join("");
|
|
51
|
+
}
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
{contributors.length > 0 && (
|
|
55
|
+
<div class="bd-contributors">
|
|
56
|
+
<span class="bd-contributors-label">Contributors</span>
|
|
57
|
+
<div class="bd-contributors-avatars">
|
|
58
|
+
{contributors.map((c) => (
|
|
59
|
+
<img
|
|
60
|
+
src={c.avatarUrl}
|
|
61
|
+
alt={c.name}
|
|
62
|
+
title={c.name}
|
|
63
|
+
class="bd-contributor-avatar"
|
|
64
|
+
loading="lazy"
|
|
65
|
+
width="28"
|
|
66
|
+
height="28"
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Keyboard shortcuts: Cmd/Ctrl+K → search, ←/→ → prev/next, ? → help modal
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
function initKeyboardShortcuts() {
|
|
7
|
+
if (window.__bdShortcutsInit) return;
|
|
8
|
+
window.__bdShortcutsInit = true;
|
|
9
|
+
|
|
10
|
+
const isMac = navigator.platform.toUpperCase().includes('MAC');
|
|
11
|
+
const modLabel = isMac ? '⌘' : 'Ctrl';
|
|
12
|
+
|
|
13
|
+
function isInputFocused(): boolean {
|
|
14
|
+
const el = document.activeElement;
|
|
15
|
+
if (!el) return false;
|
|
16
|
+
const tag = el.tagName;
|
|
17
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (el as HTMLElement).isContentEditable;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function showShortcutsModal() {
|
|
21
|
+
if (document.querySelector('.bd-shortcuts-overlay')) return;
|
|
22
|
+
|
|
23
|
+
const overlay = document.createElement('div');
|
|
24
|
+
overlay.className = 'bd-shortcuts-overlay';
|
|
25
|
+
overlay.addEventListener('click', (e) => {
|
|
26
|
+
if (e.target === overlay) overlay.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
overlay.innerHTML = `
|
|
30
|
+
<div class="bd-shortcuts-modal">
|
|
31
|
+
<h3>Keyboard Shortcuts</h3>
|
|
32
|
+
<div class="bd-shortcut-row">
|
|
33
|
+
<span class="bd-shortcut-desc">Search</span>
|
|
34
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">${modLabel}</kbd><kbd class="bd-kbd">K</kbd></span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="bd-shortcut-row">
|
|
37
|
+
<span class="bd-shortcut-desc">Previous page</span>
|
|
38
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">←</kbd></span>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="bd-shortcut-row">
|
|
41
|
+
<span class="bd-shortcut-desc">Next page</span>
|
|
42
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">→</kbd></span>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="bd-shortcut-row">
|
|
45
|
+
<span class="bd-shortcut-desc">Show shortcuts</span>
|
|
46
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">?</kbd></span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
document.body.appendChild(overlay);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
55
|
+
// Close modal on Escape
|
|
56
|
+
if (e.key === 'Escape') {
|
|
57
|
+
const modal = document.querySelector('.bd-shortcuts-overlay');
|
|
58
|
+
if (modal) { modal.remove(); return; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cmd/Ctrl+K → open search
|
|
62
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
const searchBtn = document.querySelector('[data-search-trigger]') as HTMLButtonElement;
|
|
65
|
+
if (searchBtn) searchBtn.click();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isInputFocused()) return;
|
|
70
|
+
|
|
71
|
+
// ? → show shortcuts
|
|
72
|
+
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
showShortcutsModal();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ← → prev page
|
|
79
|
+
if (e.key === 'ArrowLeft' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
80
|
+
const prev = document.querySelector('nav[aria-label="Page navigation"] a:first-of-type') as HTMLAnchorElement;
|
|
81
|
+
if (prev) { prev.click(); return; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// → → next page
|
|
85
|
+
if (e.key === 'ArrowRight' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
86
|
+
const links = document.querySelectorAll('nav[aria-label="Page navigation"] a');
|
|
87
|
+
const next = links[links.length - 1] as HTMLAnchorElement;
|
|
88
|
+
if (next && links.length > 1) { next.click(); return; }
|
|
89
|
+
if (next && links.length === 1) {
|
|
90
|
+
const parent = next.closest('.col-span-1');
|
|
91
|
+
if (parent && parent === parent.parentElement?.lastElementChild) { next.click(); }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
initKeyboardShortcuts();
|
|
98
|
+
document.addEventListener('astro:page-load', () => {
|
|
99
|
+
window.__bdShortcutsInit = false;
|
|
100
|
+
initKeyboardShortcuts();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
declare global {
|
|
104
|
+
interface Window {
|
|
105
|
+
__bdShortcutsInit?: boolean;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
3
|
+
|
|
4
|
+
interface VersionConfig {
|
|
5
|
+
label: string;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface VersionSwitcherProps {
|
|
10
|
+
versions: VersionConfig[];
|
|
11
|
+
currentPath: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function VersionSwitcher({ versions, currentPath }: VersionSwitcherProps) {
|
|
15
|
+
if (!versions || versions.length <= 1) return null;
|
|
16
|
+
|
|
17
|
+
const current = versions.find((v) => currentPath.includes(`/docs/${v.path}/`)) || versions[0];
|
|
18
|
+
|
|
19
|
+
function switchVersion(target: VersionConfig) {
|
|
20
|
+
const regex = /\/docs\/([^/]+)\//;
|
|
21
|
+
const match = currentPath.match(regex);
|
|
22
|
+
if (match) {
|
|
23
|
+
const newPath = currentPath.replace(`/docs/${match[1]}/`, `/docs/${target.path}/`);
|
|
24
|
+
window.location.href = newPath;
|
|
25
|
+
} else {
|
|
26
|
+
window.location.href = `/docs/${target.path}/`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<DropdownMenu.Root>
|
|
32
|
+
<DropdownMenu.Trigger asChild>
|
|
33
|
+
<button className="bd-version-trigger">
|
|
34
|
+
{current.label}
|
|
35
|
+
<svg
|
|
36
|
+
width="12"
|
|
37
|
+
height="12"
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
fill="none"
|
|
40
|
+
stroke="currentColor"
|
|
41
|
+
strokeWidth="2"
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
>
|
|
45
|
+
<path d="m6 9 6 6 6-6" />
|
|
46
|
+
</svg>
|
|
47
|
+
</button>
|
|
48
|
+
</DropdownMenu.Trigger>
|
|
49
|
+
|
|
50
|
+
<DropdownMenu.Portal>
|
|
51
|
+
<DropdownMenu.Content className="bd-version-menu" sideOffset={4} align="start">
|
|
52
|
+
{versions.map((v) => (
|
|
53
|
+
<DropdownMenu.Item
|
|
54
|
+
key={v.path}
|
|
55
|
+
className={`bd-version-item ${v.path === current.path ? "bd-version-active" : ""}`}
|
|
56
|
+
onSelect={() => switchVersion(v)}
|
|
57
|
+
>
|
|
58
|
+
{v.label}
|
|
59
|
+
{v.path === current.path && (
|
|
60
|
+
<svg
|
|
61
|
+
width="14"
|
|
62
|
+
height="14"
|
|
63
|
+
viewBox="0 0 24 24"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
strokeWidth="2.5"
|
|
67
|
+
strokeLinecap="round"
|
|
68
|
+
strokeLinejoin="round"
|
|
69
|
+
>
|
|
70
|
+
<polyline points="20 6 9 17 4 12" />
|
|
71
|
+
</svg>
|
|
72
|
+
)}
|
|
73
|
+
</DropdownMenu.Item>
|
|
74
|
+
))}
|
|
75
|
+
</DropdownMenu.Content>
|
|
76
|
+
</DropdownMenu.Portal>
|
|
77
|
+
</DropdownMenu.Root>
|
|
78
|
+
);
|
|
79
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -24,6 +24,12 @@ export { Expandable, ExpandableList, ExpandableItem } from "./mdx/Expandable.tsx
|
|
|
24
24
|
export { Icon, CheckIcon, XIcon, InfoIcon, WarningIcon } from "./mdx/Icon.tsx";
|
|
25
25
|
export { Steps, Step } from "./mdx/Steps.tsx";
|
|
26
26
|
export { Mermaid } from "./mdx/Mermaid.tsx";
|
|
27
|
+
export { ImageZoom } from "./mdx/ImageZoom.tsx";
|
|
28
|
+
export { Video } from "./mdx/Video.tsx";
|
|
29
|
+
export { ApiPlayground } from "./mdx/ApiPlayground.tsx";
|
|
30
|
+
export { ApiEndpoint } from "./mdx/ApiEndpoint.tsx";
|
|
31
|
+
|
|
32
|
+
export { VersionSwitcher } from "./VersionSwitcher.tsx";
|
|
27
33
|
|
|
28
34
|
// Legacy exports for backwards compatibility
|
|
29
35
|
export { Tabs, Tab } from "./mdx/Tabs.tsx";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
|
|
4
|
+
interface ApiEndpointProps {
|
|
5
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
+
path: string;
|
|
7
|
+
summary?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const METHOD_COLORS: Record<string, string> = {
|
|
12
|
+
GET: "bd-method-get",
|
|
13
|
+
POST: "bd-method-post",
|
|
14
|
+
PUT: "bd-method-put",
|
|
15
|
+
PATCH: "bd-method-patch",
|
|
16
|
+
DELETE: "bd-method-delete",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function ApiEndpoint({ method, path, summary, id }: ApiEndpointProps) {
|
|
20
|
+
const slug = id || `${method.toLowerCase()}-${path.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}`;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="bd-api-endpoint" id={slug}>
|
|
24
|
+
<a href={`#${slug}`} className="bd-api-endpoint-anchor" aria-label={`${method} ${path}`}>
|
|
25
|
+
<div className="bd-api-endpoint-header">
|
|
26
|
+
<span className={cn("bd-api-endpoint-method", METHOD_COLORS[method])}>
|
|
27
|
+
{method}
|
|
28
|
+
</span>
|
|
29
|
+
<code className="bd-api-endpoint-path">{path}</code>
|
|
30
|
+
</div>
|
|
31
|
+
{summary && <p className="bd-api-endpoint-summary">{summary}</p>}
|
|
32
|
+
</a>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|