@karmaniverous/jeeves-server 3.0.0-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/.env.local +13 -0
- package/.env.local.template +13 -0
- package/.tsbuildinfo +1 -0
- package/CHANGELOG.md +450 -0
- package/about.md +82 -0
- package/client/README.md +73 -0
- package/client/eslint.config.js +23 -0
- package/client/index.html +14 -0
- package/client/package-lock.json +5181 -0
- package/client/package.json +60 -0
- package/client/public/vite.svg +1 -0
- package/client/src/App.tsx +22 -0
- package/client/src/components/AccountMenu.tsx +167 -0
- package/client/src/components/ActionDropdown.tsx +120 -0
- package/client/src/components/CodeEditor.tsx +143 -0
- package/client/src/components/CodeViewer.tsx +113 -0
- package/client/src/components/ConfirmDialog.tsx +32 -0
- package/client/src/components/DirectoryRow.tsx +62 -0
- package/client/src/components/DirectoryTable.tsx +42 -0
- package/client/src/components/DownloadDropdown.tsx +116 -0
- package/client/src/components/DriveList.tsx +54 -0
- package/client/src/components/EmbeddedDiagramPanzoom.ts +28 -0
- package/client/src/components/FileContentView.tsx +155 -0
- package/client/src/components/InlineSvgPanzoom.ts +60 -0
- package/client/src/components/LazyDiagram.ts +93 -0
- package/client/src/components/LinkDropdown.tsx +134 -0
- package/client/src/components/MarkdownView.tsx +115 -0
- package/client/src/components/MermaidViewer.tsx +21 -0
- package/client/src/components/PlantUmlViewer.tsx +21 -0
- package/client/src/components/SearchModal.tsx +424 -0
- package/client/src/components/SvgViewer.tsx +107 -0
- package/client/src/components/TabBar.tsx +96 -0
- package/client/src/components/layout/Header.tsx +270 -0
- package/client/src/components/panzoom.ts +203 -0
- package/client/src/components/renderableUtils.ts +15 -0
- package/client/src/components/runner/JobTable.tsx +153 -0
- package/client/src/components/runner/RunHistory.tsx +140 -0
- package/client/src/components/runner/StatsBar.tsx +43 -0
- package/client/src/components/runner/StatusPill.tsx +27 -0
- package/client/src/components/runner/jobTableUtils.ts +65 -0
- package/client/src/components/scrollUtils.ts +39 -0
- package/client/src/components/ui/alert-dialog.tsx +107 -0
- package/client/src/components/ui/button.tsx +40 -0
- package/client/src/components/ui/dropdown-menu.tsx +79 -0
- package/client/src/components/ui/input.tsx +26 -0
- package/client/src/components/useActionState.ts +43 -0
- package/client/src/hooks/useFileBrowser.ts +102 -0
- package/client/src/hooks/useFileData.ts +78 -0
- package/client/src/hooks/useScrollAnchor.ts +70 -0
- package/client/src/hooks/useShareSettings.ts +22 -0
- package/client/src/hooks/useTopBar.ts +27 -0
- package/client/src/index.css +281 -0
- package/client/src/lib/AuthContext.ts +27 -0
- package/client/src/lib/api.ts +239 -0
- package/client/src/lib/auth.tsx +50 -0
- package/client/src/lib/codeBlockCm6.ts +129 -0
- package/client/src/lib/codeBlockCopy.ts +43 -0
- package/client/src/lib/codemirror.ts +77 -0
- package/client/src/lib/runner-api.ts +172 -0
- package/client/src/lib/svg.ts +50 -0
- package/client/src/lib/theme.ts +34 -0
- package/client/src/lib/utils.ts +6 -0
- package/client/src/main.tsx +11 -0
- package/client/src/pages/FileBrowser.tsx +135 -0
- package/client/src/pages/Home.tsx +46 -0
- package/client/src/pages/Runner.tsx +151 -0
- package/client/src/pages/RunnerJob.tsx +170 -0
- package/client/tsconfig.app.json +32 -0
- package/client/tsconfig.json +7 -0
- package/client/tsconfig.node.json +26 -0
- package/client/vite.config.ts +35 -0
- package/content/privacy.md +61 -0
- package/content/terms.md +41 -0
- package/dist/client/assets/CodeEditor-0XHVI8Nu.js +1 -0
- package/dist/client/assets/CodeViewer-CykMVsfX.js +1 -0
- package/dist/client/assets/index--MBieNJA.js +1 -0
- package/dist/client/assets/index-BENeXQI_.js +1 -0
- package/dist/client/assets/index-BbBpoOxz.js +1 -0
- package/dist/client/assets/index-BdV9g5AM.js +6 -0
- package/dist/client/assets/index-BjAilRri.js +2 -0
- package/dist/client/assets/index-BqbhWo2I.js +3 -0
- package/dist/client/assets/index-CVbycZ0H.js +1 -0
- package/dist/client/assets/index-Cs5oz2oJ.js +5 -0
- package/dist/client/assets/index-D8KZVveX.js +1 -0
- package/dist/client/assets/index-DC4HMHxY.js +13 -0
- package/dist/client/assets/index-DbMebkkd.css +1 -0
- package/dist/client/assets/index-DcY2RXqX.js +1 -0
- package/dist/client/assets/index-Duy-tZYV.js +1 -0
- package/dist/client/assets/index-Dw7rDFmE.js +7 -0
- package/dist/client/assets/index-FlCUvrjv.js +2 -0
- package/dist/client/assets/index-K6OVmfhg.js +1 -0
- package/dist/client/assets/index-LjwgzZ7F.js +62 -0
- package/dist/client/assets/index-MLwyFRN0.js +1 -0
- package/dist/client/assets/index-OpqBpSjn.js +1 -0
- package/dist/client/assets/index-SsHei0HE.js +1 -0
- package/dist/client/assets/index-uQa2yckk.js +1 -0
- package/dist/client/assets/index-udkXoIER.js +1 -0
- package/dist/client/index.html +15 -0
- package/dist/client/vite.svg +1 -0
- package/dist/src/auth/google.js +57 -0
- package/dist/src/auth/keys.js +185 -0
- package/dist/src/auth/resolve.js +102 -0
- package/dist/src/auth/session.js +57 -0
- package/dist/src/cli/commands/config.js +100 -0
- package/dist/src/cli/commands/config.test.js +84 -0
- package/dist/src/cli/commands/service.js +93 -0
- package/dist/src/cli/commands/start.js +24 -0
- package/dist/src/cli/index.js +20 -0
- package/dist/src/config/index.js +90 -0
- package/dist/src/config/loadConfig.test.js +127 -0
- package/dist/src/config/resolve.js +134 -0
- package/dist/src/config/resolve.test.js +148 -0
- package/dist/src/config/schema.js +159 -0
- package/dist/src/config/substituteEnvVars.js +45 -0
- package/dist/src/config/substituteEnvVars.test.js +51 -0
- package/dist/src/config/types.js +5 -0
- package/dist/src/routes/api/auth-status.js +56 -0
- package/dist/src/routes/api/diagrams.js +35 -0
- package/dist/src/routes/api/directory.js +93 -0
- package/dist/src/routes/api/drives.js +15 -0
- package/dist/src/routes/api/export.js +218 -0
- package/dist/src/routes/api/fileContent.js +286 -0
- package/dist/src/routes/api/index.js +33 -0
- package/dist/src/routes/api/linkInfo.js +71 -0
- package/dist/src/routes/api/linkInfo.test.js +104 -0
- package/dist/src/routes/api/middleware.js +117 -0
- package/dist/src/routes/api/raw.js +38 -0
- package/dist/src/routes/api/runner.js +59 -0
- package/dist/src/routes/api/search.js +236 -0
- package/dist/src/routes/api/sharing.js +203 -0
- package/dist/src/routes/api/status.js +68 -0
- package/dist/src/routes/api/status.test.js +62 -0
- package/dist/src/routes/auth.js +99 -0
- package/dist/src/routes/event.js +77 -0
- package/dist/src/routes/event.test.js +206 -0
- package/dist/src/routes/health.js +10 -0
- package/dist/src/routes/keys.js +129 -0
- package/dist/src/routes/path/index.js +17 -0
- package/dist/src/routes/static.js +30 -0
- package/dist/src/server.js +90 -0
- package/dist/src/services/deepShareLinks.js +163 -0
- package/dist/src/services/diagramCache.js +104 -0
- package/dist/src/services/embeddedDiagrams.js +136 -0
- package/dist/src/services/eventLog.js +55 -0
- package/dist/src/services/eventLog.test.js +113 -0
- package/dist/src/services/eventQueue.js +154 -0
- package/dist/src/services/eventQueue.test.js +104 -0
- package/dist/src/services/export.js +220 -0
- package/dist/src/services/exportCache.js +196 -0
- package/dist/src/services/markdown.js +147 -0
- package/dist/src/services/mermaid.js +97 -0
- package/dist/src/services/plantuml.js +145 -0
- package/dist/src/services/puppeteer.js +156 -0
- package/dist/src/util/breadcrumbs.js +22 -0
- package/dist/src/util/crypto.js +56 -0
- package/dist/src/util/crypto.test.js +99 -0
- package/dist/src/util/fileDetection.js +66 -0
- package/dist/src/util/fileDetection.test.js +89 -0
- package/dist/src/util/formatters.js +43 -0
- package/dist/src/util/formatters.test.js +83 -0
- package/dist/src/util/packageVersion.js +25 -0
- package/dist/src/util/platform.js +148 -0
- package/dist/src/util/state.js +46 -0
- package/dist/vitest.config.js +12 -0
- package/favicon.svg +3 -0
- package/guides/access-decision-flow.mmd +24 -0
- package/guides/access-decision-flow.svg +1 -0
- package/guides/api-integration.md +236 -0
- package/guides/deployment.md +287 -0
- package/guides/event-gateway.md +204 -0
- package/guides/event-gateway.mmd +17 -0
- package/guides/event-gateway.svg +1 -0
- package/guides/exports.md +239 -0
- package/guides/setup.md +313 -0
- package/guides/sharing.md +204 -0
- package/jeeves-server.config.template.json +25 -0
- package/package.json +124 -0
- package/scripts/download-plantuml.js +70 -0
- package/src/auth/google.ts +93 -0
- package/src/auth/keys.ts +252 -0
- package/src/auth/resolve.ts +157 -0
- package/src/auth/session.ts +77 -0
- package/src/cli/commands/config.test.ts +107 -0
- package/src/cli/commands/config.ts +113 -0
- package/src/cli/commands/service.ts +129 -0
- package/src/cli/commands/start.ts +27 -0
- package/src/cli/index.ts +25 -0
- package/src/config/index.ts +113 -0
- package/src/config/loadConfig.test.ts +155 -0
- package/src/config/resolve.test.ts +192 -0
- package/src/config/resolve.ts +173 -0
- package/src/config/schema.ts +179 -0
- package/src/config/substituteEnvVars.test.ts +64 -0
- package/src/config/substituteEnvVars.ts +52 -0
- package/src/config/types.ts +129 -0
- package/src/routes/api/auth-status.ts +85 -0
- package/src/routes/api/diagrams.ts +53 -0
- package/src/routes/api/directory.ts +123 -0
- package/src/routes/api/drives.ts +23 -0
- package/src/routes/api/export.ts +314 -0
- package/src/routes/api/fileContent.ts +414 -0
- package/src/routes/api/index.ts +37 -0
- package/src/routes/api/linkInfo.test.ts +132 -0
- package/src/routes/api/linkInfo.ts +83 -0
- package/src/routes/api/middleware.ts +156 -0
- package/src/routes/api/raw.ts +54 -0
- package/src/routes/api/runner.ts +107 -0
- package/src/routes/api/search.ts +321 -0
- package/src/routes/api/sharing.ts +259 -0
- package/src/routes/api/status.test.ts +72 -0
- package/src/routes/api/status.ts +82 -0
- package/src/routes/auth.ts +143 -0
- package/src/routes/event.test.ts +248 -0
- package/src/routes/event.ts +109 -0
- package/src/routes/health.ts +13 -0
- package/src/routes/keys.ts +192 -0
- package/src/routes/path/index.ts +24 -0
- package/src/routes/static.ts +54 -0
- package/src/server.ts +104 -0
- package/src/services/deepShareLinks.ts +203 -0
- package/src/services/diagramCache.ts +128 -0
- package/src/services/embeddedDiagrams.ts +168 -0
- package/src/services/eventLog.test.ts +144 -0
- package/src/services/eventLog.ts +68 -0
- package/src/services/eventQueue.test.ts +127 -0
- package/src/services/eventQueue.ts +196 -0
- package/src/services/export.ts +267 -0
- package/src/services/exportCache.ts +216 -0
- package/src/services/markdown.ts +189 -0
- package/src/services/mermaid.ts +113 -0
- package/src/services/plantuml.ts +172 -0
- package/src/services/puppeteer.ts +188 -0
- package/src/types/fastify.d.ts +13 -0
- package/src/types/jsonmap.d.ts +10 -0
- package/src/types/plantuml-encoder.d.ts +4 -0
- package/src/util/breadcrumbs.ts +33 -0
- package/src/util/crypto.test.ts +132 -0
- package/src/util/crypto.ts +79 -0
- package/src/util/fileDetection.test.ts +115 -0
- package/src/util/fileDetection.ts +70 -0
- package/src/util/formatters.test.ts +105 -0
- package/src/util/formatters.ts +44 -0
- package/src/util/packageVersion.ts +30 -0
- package/src/util/platform.ts +178 -0
- package/src/util/state.ts +55 -0
- package/test-docs/diagram-retry-test.md +18 -0
- package/test-docs/embedded-diagrams.md +52 -0
- package/test-docs/lazy-diagrams-test.md +333 -0
- package/test-docs/page-a.md +7 -0
- package/test-docs/page-b.md +7 -0
- package/test-docs/page-c.md +7 -0
- package/test-docs/sub/page-d.md +7 -0
- package/test-docs/test-diagram.puml +13 -0
- package/test-docs/validate-deep-share.js +318 -0
- package/tsconfig.json +37 -0
- package/tsdoc.json +13 -0
- package/vendor/.plantuml-version +1 -0
- package/vendor/plantuml.jar +0 -0
- package/vitest.config.js +12 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Link as LinkIcon } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
import { DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
|
4
|
+
import { ActionDropdown, DropdownErrorBanner, type ActionState } from '@/components/ActionDropdown';
|
|
5
|
+
import { useActionState } from '@/components/useActionState';
|
|
6
|
+
import { getShareLink, type ShareSettings } from '@/lib/api';
|
|
7
|
+
|
|
8
|
+
interface LinkDropdownProps {
|
|
9
|
+
path: string;
|
|
10
|
+
shareSettings: ShareSettings;
|
|
11
|
+
onShareSettingsChange: (settings: ShareSettings) => void;
|
|
12
|
+
showEvent?: boolean;
|
|
13
|
+
showRaw?: boolean;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
isDirectory?: boolean;
|
|
16
|
+
variant?: 'header' | 'default' | 'menuItem';
|
|
17
|
+
onError?: (error: string) => void;
|
|
18
|
+
onStateChange?: (state: ActionState) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type LinkType = 'page' | 'raw' | 'event';
|
|
22
|
+
|
|
23
|
+
async function copyShareLink(path: string, settings: ShareSettings, type: LinkType, isDirectory?: boolean) {
|
|
24
|
+
let expiryParam: string | undefined;
|
|
25
|
+
if (settings.expiry) {
|
|
26
|
+
const match = settings.expiry.match(/^(\d+)([hdw])$/i);
|
|
27
|
+
if (match) {
|
|
28
|
+
const val = parseInt(match[1], 10);
|
|
29
|
+
const unit = match[2].toLowerCase();
|
|
30
|
+
const multiplier: Record<string, number> = { h: 3_600_000, d: 86_400_000, w: 604_800_000 };
|
|
31
|
+
expiryParam = String(Date.now() + val * multiplier[unit]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const depth = !isDirectory && settings.depth > 0 ? settings.depth : undefined;
|
|
36
|
+
const dirs = !isDirectory && settings.dirs ? true : undefined;
|
|
37
|
+
|
|
38
|
+
const data = await getShareLink(path, expiryParam, depth, dirs);
|
|
39
|
+
if (!data.url) throw new Error('No URL returned');
|
|
40
|
+
|
|
41
|
+
let fullUrl = window.location.origin + data.url;
|
|
42
|
+
if (type === 'raw') {
|
|
43
|
+
const shareUrl = new URL(fullUrl);
|
|
44
|
+
shareUrl.pathname = shareUrl.pathname.replace('/browse/', '/api/raw/');
|
|
45
|
+
fullUrl = shareUrl.toString();
|
|
46
|
+
} else if (type === 'event') {
|
|
47
|
+
fullUrl = window.location.origin + '/event?key=' + data.url.split('key=')[1];
|
|
48
|
+
}
|
|
49
|
+
await navigator.clipboard.writeText(fullUrl);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const EXPIRY_OPTIONS = [
|
|
53
|
+
{ label: 'Never', value: '' },
|
|
54
|
+
{ label: '1 hour', value: '1h' },
|
|
55
|
+
{ label: '24 hours', value: '24h' },
|
|
56
|
+
{ label: '7 days', value: '7d' },
|
|
57
|
+
{ label: '30 days', value: '30d' },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export function LinkDropdown({ path, shareSettings, onShareSettingsChange, showEvent, showRaw, compact, isDirectory, variant = 'default', onError, onStateChange }: LinkDropdownProps) {
|
|
61
|
+
const { state, errorMsg, handleAction, resetOnClose } = useActionState(onError, onStateChange);
|
|
62
|
+
|
|
63
|
+
const items: { label: string; type: LinkType }[] = [{ label: 'Page', type: 'page' }];
|
|
64
|
+
if (showRaw) items.push({ label: 'Raw', type: 'raw' });
|
|
65
|
+
if (showEvent) items.push({ label: 'Event', type: 'event' });
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<ActionDropdown
|
|
69
|
+
icon={LinkIcon}
|
|
70
|
+
label="Share"
|
|
71
|
+
title="Copy share link"
|
|
72
|
+
variant={variant}
|
|
73
|
+
compact={compact}
|
|
74
|
+
state={state}
|
|
75
|
+
contentClass="w-52"
|
|
76
|
+
onOpenChange={resetOnClose}
|
|
77
|
+
errorSlot={<DropdownErrorBanner message={errorMsg} />}
|
|
78
|
+
>
|
|
79
|
+
{items.map((item) => (
|
|
80
|
+
<DropdownMenuItem
|
|
81
|
+
key={item.type}
|
|
82
|
+
onSelect={() => void handleAction(() => copyShareLink(path, shareSettings, item.type, isDirectory))}
|
|
83
|
+
className="cursor-pointer"
|
|
84
|
+
>
|
|
85
|
+
Copy {item.label} Link
|
|
86
|
+
</DropdownMenuItem>
|
|
87
|
+
))}
|
|
88
|
+
|
|
89
|
+
<DropdownMenuSeparator />
|
|
90
|
+
|
|
91
|
+
<div className="px-2 py-1 flex items-center justify-between gap-2">
|
|
92
|
+
<span className="text-xs text-muted-foreground whitespace-nowrap">Expires</span>
|
|
93
|
+
<select
|
|
94
|
+
className="text-xs bg-popover text-popover-foreground border border-border rounded px-1 py-0.5 focus:outline-none focus:ring-1 focus:ring-ring min-w-0"
|
|
95
|
+
value={shareSettings.expiry}
|
|
96
|
+
onChange={(e) => onShareSettingsChange({ ...shareSettings, expiry: e.target.value })}
|
|
97
|
+
onClick={(e) => e.stopPropagation()}
|
|
98
|
+
>
|
|
99
|
+
{EXPIRY_OPTIONS.map((opt) => (
|
|
100
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
101
|
+
))}
|
|
102
|
+
</select>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{!isDirectory && (
|
|
106
|
+
<>
|
|
107
|
+
<div className="px-2 py-1 flex items-center justify-between gap-2">
|
|
108
|
+
<span className="text-xs text-muted-foreground whitespace-nowrap">Depth</span>
|
|
109
|
+
<input
|
|
110
|
+
type="number"
|
|
111
|
+
min={0}
|
|
112
|
+
max={10}
|
|
113
|
+
className="text-xs bg-popover text-popover-foreground border border-border rounded px-1 py-0.5 w-14 text-right focus:outline-none focus:ring-1 focus:ring-ring"
|
|
114
|
+
value={shareSettings.depth}
|
|
115
|
+
onChange={(e) => onShareSettingsChange({ ...shareSettings, depth: Math.max(0, parseInt(e.target.value, 10) || 0) })}
|
|
116
|
+
onClick={(e) => e.stopPropagation()}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="px-2 py-1 flex items-center justify-between gap-2">
|
|
121
|
+
<span className="text-xs text-muted-foreground whitespace-nowrap">Directories</span>
|
|
122
|
+
<input
|
|
123
|
+
type="checkbox"
|
|
124
|
+
className="rounded border-border"
|
|
125
|
+
checked={shareSettings.dirs}
|
|
126
|
+
onChange={(e) => onShareSettingsChange({ ...shareSettings, dirs: e.target.checked })}
|
|
127
|
+
onClick={(e) => e.stopPropagation()}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
</>
|
|
131
|
+
)}
|
|
132
|
+
</ActionDropdown>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown rendered view with TOC sidebar.
|
|
3
|
+
*/
|
|
4
|
+
import type { FileContent } from '@/lib/api';
|
|
5
|
+
import { initEmbeddedDiagramPanzoom } from '@/components/EmbeddedDiagramPanzoom';
|
|
6
|
+
import { initLazyDiagrams } from '@/components/LazyDiagram';
|
|
7
|
+
import { initInlineSvgPanzoom } from '@/components/InlineSvgPanzoom';
|
|
8
|
+
import { initCodeBlockCm6 } from '@/lib/codeBlockCm6';
|
|
9
|
+
import { injectCopyButtons } from '@/lib/codeBlockCopy';
|
|
10
|
+
import { useTheme } from '@/lib/theme';
|
|
11
|
+
import { scrollToIdInContainer } from './scrollUtils';
|
|
12
|
+
|
|
13
|
+
interface MarkdownViewProps {
|
|
14
|
+
fileRendered: FileContent;
|
|
15
|
+
proseWidth: 'narrow' | 'medium' | 'wide';
|
|
16
|
+
topBarHeight: number;
|
|
17
|
+
mainRef: React.RefObject<HTMLElement | null>;
|
|
18
|
+
mobileTocOpen: boolean;
|
|
19
|
+
setMobileTocOpen: (open: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MarkdownView({
|
|
23
|
+
fileRendered, proseWidth, topBarHeight, mainRef,
|
|
24
|
+
mobileTocOpen, setMobileTocOpen,
|
|
25
|
+
}: MarkdownViewProps) {
|
|
26
|
+
const [theme] = useTheme();
|
|
27
|
+
const plainCode = new URLSearchParams(window.location.search).has('plain_code');
|
|
28
|
+
const hasHeadings = fileRendered.headings && fileRendered.headings.length > 2;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
{/* Mobile TOC overlay */}
|
|
33
|
+
{mobileTocOpen && hasHeadings && (
|
|
34
|
+
<>
|
|
35
|
+
<div className="lg:hidden fixed inset-0 z-40" onClick={() => setMobileTocOpen(false)} />
|
|
36
|
+
<div
|
|
37
|
+
className="lg:hidden fixed left-2 right-2 z-50 bg-popover text-popover-foreground border border-border rounded-lg shadow-lg max-h-[60vh] overflow-y-auto px-4 py-3"
|
|
38
|
+
style={{ top: `${topBarHeight + 4}px` }}
|
|
39
|
+
>
|
|
40
|
+
<nav>
|
|
41
|
+
{fileRendered.headings!.map((h) => (
|
|
42
|
+
<button
|
|
43
|
+
key={h.slug}
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={() => { scrollToIdInContainer(mainRef.current, h.slug); setMobileTocOpen(false); }}
|
|
46
|
+
className="block text-left text-sm text-muted-foreground hover:text-foreground cursor-pointer py-1 transition-colors w-full"
|
|
47
|
+
style={{ paddingLeft: `${(h.level - 1) * 0.75}rem` }}
|
|
48
|
+
>
|
|
49
|
+
{h.text}
|
|
50
|
+
</button>
|
|
51
|
+
))}
|
|
52
|
+
</nav>
|
|
53
|
+
</div>
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<div className="flex gap-6">
|
|
58
|
+
{/* Desktop TOC sidebar */}
|
|
59
|
+
{hasHeadings && (
|
|
60
|
+
<aside
|
|
61
|
+
className="toc-sidebar hidden lg:block w-56 shrink-0"
|
|
62
|
+
style={{ maxHeight: `calc(100vh - ${topBarHeight + 32}px)` }}
|
|
63
|
+
>
|
|
64
|
+
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">Contents</div>
|
|
65
|
+
<nav className="border-l border-border pl-3">
|
|
66
|
+
{fileRendered.headings!.map((h) => (
|
|
67
|
+
<button
|
|
68
|
+
key={h.slug}
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={() => scrollToIdInContainer(mainRef.current, h.slug)}
|
|
71
|
+
className="block text-left text-sm text-muted-foreground hover:text-foreground cursor-pointer py-0.5 transition-colors"
|
|
72
|
+
style={{ paddingLeft: `${(h.level - 1) * 0.75}rem` }}
|
|
73
|
+
>
|
|
74
|
+
{h.text}
|
|
75
|
+
</button>
|
|
76
|
+
))}
|
|
77
|
+
</nav>
|
|
78
|
+
</aside>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{/* Markdown article */}
|
|
82
|
+
<article
|
|
83
|
+
ref={(el) => { if (el) { if (!plainCode) initCodeBlockCm6(el, theme); injectCopyButtons(el); initInlineSvgPanzoom(el); initEmbeddedDiagramPanzoom(el); initLazyDiagrams(el); } }}
|
|
84
|
+
className={`prose bg-background p-6 rounded-lg border border-border min-w-0 flex-1 ${
|
|
85
|
+
proseWidth === 'narrow' ? 'max-w-prose' : proseWidth === 'medium' ? 'max-w-5xl' : 'max-w-none'
|
|
86
|
+
}`}
|
|
87
|
+
style={{
|
|
88
|
+
'--tw-prose-body': 'var(--foreground)',
|
|
89
|
+
'--tw-prose-headings': 'var(--foreground)',
|
|
90
|
+
'--tw-prose-bold': 'var(--foreground)',
|
|
91
|
+
'--tw-prose-links': '#3b82f6',
|
|
92
|
+
'--tw-prose-code': 'var(--foreground)',
|
|
93
|
+
'--tw-prose-pre-bg': 'var(--muted)',
|
|
94
|
+
'--tw-prose-pre-code': 'var(--foreground)',
|
|
95
|
+
'--tw-prose-hr': 'var(--border)',
|
|
96
|
+
'--tw-prose-quotes': 'var(--muted-foreground)',
|
|
97
|
+
'--tw-prose-quote-borders': 'var(--border)',
|
|
98
|
+
'--tw-prose-th-borders': 'var(--border)',
|
|
99
|
+
'--tw-prose-td-borders': 'var(--border)',
|
|
100
|
+
} as React.CSSProperties}
|
|
101
|
+
dangerouslySetInnerHTML={{ __html: fileRendered.html! }}
|
|
102
|
+
onClick={(e) => {
|
|
103
|
+
const target = e.target as HTMLElement;
|
|
104
|
+
const anchor = target.closest('a');
|
|
105
|
+
const href = anchor?.getAttribute('href');
|
|
106
|
+
if (href?.startsWith('#')) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
scrollToIdInContainer(mainRef.current, href.slice(1));
|
|
109
|
+
}
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SvgViewer } from './SvgViewer';
|
|
2
|
+
|
|
3
|
+
interface MermaidViewerProps {
|
|
4
|
+
/** Server-rendered SVG string */
|
|
5
|
+
html: string | null;
|
|
6
|
+
/** Raw mermaid source (fallback) */
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MermaidViewer({ html, content }: MermaidViewerProps) {
|
|
11
|
+
if (!html) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-4 bg-red-950/20 border border-red-800 rounded-lg">
|
|
14
|
+
<div className="text-red-400 text-sm font-medium mb-2">Mermaid render failed</div>
|
|
15
|
+
<pre className="text-red-300 text-xs overflow-x-auto">{content}</pre>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <SvgViewer content={html} />;
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SvgViewer } from './SvgViewer';
|
|
2
|
+
|
|
3
|
+
interface PlantUmlViewerProps {
|
|
4
|
+
/** Server-rendered SVG string */
|
|
5
|
+
html: string | null;
|
|
6
|
+
/** Raw PlantUML source (fallback) */
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function PlantUmlViewer({ html, content }: PlantUmlViewerProps) {
|
|
11
|
+
if (!html) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-4 bg-red-950/20 border border-red-800 rounded-lg">
|
|
14
|
+
<div className="text-red-400 text-sm font-medium mb-2">PlantUML render failed</div>
|
|
15
|
+
<pre className="text-red-300 text-xs overflow-x-auto">{content}</pre>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <SvgViewer content={html} />;
|
|
21
|
+
}
|