@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barodoc/theme-docs",
3
- "version": "5.0.0",
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": "5.0.0"
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
+ }
@@ -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
+ }