@karmaniverous/jeeves-server 3.5.1 → 3.6.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/.tsbuildinfo +1 -1
- package/CHANGELOG.md +32 -1
- package/client/package.json +0 -1
- package/client/src/components/DirectoryRow.tsx +5 -1
- package/client/src/components/FileContentView.tsx +7 -0
- package/client/src/components/MarkdownView.tsx +63 -22
- package/client/src/components/TocSection.tsx +74 -0
- package/client/src/components/renderableUtils.ts +2 -2
- package/client/src/components/tocUtils.ts +65 -0
- package/client/src/index.css +36 -0
- package/client/src/lib/api.ts +2 -1
- package/dist/client/assets/{CodeViewer-Cegj3cEn.js → CodeViewer-D5fJ1Z6_.js} +1 -1
- package/dist/client/assets/{index-DrBXupPz.js → index-BXy6kgl7.js} +2 -2
- package/dist/client/assets/index-Ch0vkF39.css +2 -0
- package/dist/client/index.html +2 -2
- package/dist/src/cli/index.js +4 -1
- package/dist/src/cli/start-server.js +12 -2
- package/dist/src/config/index.js +16 -2
- package/dist/src/config/loadConfig.test.js +66 -1
- package/dist/src/config/resolve.js +0 -4
- package/dist/src/config/resolve.test.js +0 -2
- package/dist/src/config/schema.js +7 -21
- package/dist/src/config/substituteEnvVars.js +2 -0
- package/dist/src/descriptor.js +9 -2
- package/dist/src/routes/api/auth-status.js +1 -1
- package/dist/src/routes/api/directory.js +46 -24
- package/dist/src/routes/api/directory.test.js +65 -0
- package/dist/src/routes/api/export.js +5 -1
- package/dist/src/routes/api/export.test.js +46 -0
- package/dist/src/routes/api/fileContent.js +26 -4
- package/dist/src/routes/api/runner.js +2 -3
- package/dist/src/routes/api/runner.test.js +29 -0
- package/dist/src/routes/api/search.js +4 -9
- package/dist/src/routes/api/search.test.js +28 -0
- package/dist/src/routes/config.test.js +0 -1
- package/dist/src/routes/status.js +4 -4
- package/dist/src/routes/status.test.js +10 -4
- package/dist/src/server.js +4 -2
- package/dist/src/services/csv.js +114 -0
- package/dist/src/services/csv.test.js +107 -0
- package/dist/src/services/markdown.js +21 -1
- package/dist/src/services/markdown.test.js +43 -0
- package/dist/src/util/packageVersion.js +3 -13
- package/guides/deployment.md +1 -1
- package/guides/setup.md +14 -10
- package/knip.json +2 -1
- package/package.json +5 -4
- package/src/cli/index.ts +8 -2
- package/src/cli/start-server.ts +17 -3
- package/src/config/index.ts +22 -2
- package/src/config/loadConfig.test.ts +77 -1
- package/src/config/resolve.test.ts +0 -2
- package/src/config/resolve.ts +0 -4
- package/src/config/schema.ts +8 -21
- package/src/config/substituteEnvVars.ts +2 -0
- package/src/config/types.ts +0 -4
- package/src/descriptor.ts +9 -1
- package/src/routes/api/auth-status.ts +1 -1
- package/src/routes/api/directory.test.ts +77 -0
- package/src/routes/api/directory.ts +59 -22
- package/src/routes/api/export.test.ts +56 -0
- package/src/routes/api/export.ts +5 -1
- package/src/routes/api/fileContent.ts +27 -3
- package/src/routes/api/runner.test.ts +39 -0
- package/src/routes/api/runner.ts +2 -5
- package/src/routes/api/search.test.ts +36 -0
- package/src/routes/api/search.ts +4 -9
- package/src/routes/config.test.ts +0 -1
- package/src/routes/status.test.ts +13 -4
- package/src/routes/status.ts +4 -4
- package/src/server.ts +4 -2
- package/src/services/csv.test.ts +127 -0
- package/src/services/csv.ts +115 -0
- package/src/services/markdown.test.ts +54 -0
- package/src/services/markdown.ts +21 -1
- package/src/util/packageVersion.ts +3 -18
- package/dist/client/assets/index-Dk_myGs4.css +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
|
|
4
4
|
|
|
5
|
-
#### [3.
|
|
5
|
+
#### [3.6.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.2...3.6.0)
|
|
6
|
+
|
|
7
|
+
- feat: v3.6.0 core v0.5.1 adoption + core service alignment + UX improvements [`#146`](https://github.com/karmaniverous/jeeves-server/pull/146)
|
|
8
|
+
- [360] feat: Phase 3 — UX improvements (CSV tables, directory item counts, collapsible frontmatter, collapsible TOC) [`#48`](https://github.com/karmaniverous/jeeves-server/issues/48)
|
|
9
|
+
- [360] feat: Phase 1 — core service adoption (getServiceUrl, getBindAddress, Node 22 fast-fail) [`#135`](https://github.com/karmaniverous/jeeves-server/issues/135)
|
|
10
|
+
- [360] test: add missing test coverage for v3.6.0 features [`c23f7a1`](https://github.com/karmaniverous/jeeves-server/commit/c23f7a1dfa03517d2ed8a28eb0ef06da05379e38)
|
|
11
|
+
- [360] refactor: address code review — async directory reads, CSV row normalization, extract mapDirectoryEntry [`3a1a389`](https://github.com/karmaniverous/jeeves-server/commit/3a1a389ee5f3a4ce1d8e28db204784ea9fefd1d5)
|
|
12
|
+
- npm audit fix [`0be936a`](https://github.com/karmaniverous/jeeves-server/commit/0be936a6dbbfb07b1f7d62e334d307a0fb0db3f8)
|
|
13
|
+
- [360] refactor: split TocSection.tsx — move utilities to tocUtils.ts (fixes react-refresh lint) [`d8e8d57`](https://github.com/karmaniverous/jeeves-server/commit/d8e8d57df8eefc419bdcafed08f0a60ccd4e481d)
|
|
14
|
+
- [360] chore: remove TASK.md build artifact, DRY up Node version check and fix minor issues [`3e569db`](https://github.com/karmaniverous/jeeves-server/commit/3e569dbca069ea659ee5b1ca9030caa43ae4fea5)
|
|
15
|
+
- [360] docs: Phase 4 — documentation updates for v3.6.0 [`8e0be64`](https://github.com/karmaniverous/jeeves-server/commit/8e0be6472e300c6117dc0c917b76686015931a85)
|
|
16
|
+
- [360] feat: Phase 2 — core v0.5.1 wiring (cleanup escalation, getPackageVersion, substituteEnvVars TODO) [`2b97c20`](https://github.com/karmaniverous/jeeves-server/commit/2b97c209c0caaf99d27f36a6dff397ddbb4e168d)
|
|
17
|
+
- chore: release @karmaniverous/jeeves-server-openclaw v0.6.2 [`f120979`](https://github.com/karmaniverous/jeeves-server/commit/f1209795471958090f3fb18b4eda4bb62415da36)
|
|
18
|
+
- [360] chore: Phase 5 — quality gates clean (knip fixes, remove package-directory dep) [`a436c46`](https://github.com/karmaniverous/jeeves-server/commit/a436c462311b07bf5975e6ba8b0d75c2e216e637)
|
|
19
|
+
- [360] ci: drop Node 20 from Linux compatibility matrix (engine floor is now 22) [`5b690e6`](https://github.com/karmaniverous/jeeves-server/commit/5b690e6644e1e4dd2c7f66e089cf4d190daeeb32)
|
|
20
|
+
- [360] docs: fix Node version in deployment guide (20 → 22) [`dde5cd2`](https://github.com/karmaniverous/jeeves-server/commit/dde5cd2f3d876a8227be823de78ccbda003e9f62)
|
|
21
|
+
|
|
22
|
+
#### [service/3.5.2](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.1...service/3.5.2)
|
|
23
|
+
|
|
24
|
+
> 31 March 2026
|
|
25
|
+
|
|
26
|
+
- [53] Fix pre-existing lint + knip errors (Commander type resolution) [`#140`](https://github.com/karmaniverous/jeeves-server/pull/140)
|
|
27
|
+
- [53] Integrate core v0.4.6: init() before descriptor.run() + knip fixes [`#139`](https://github.com/karmaniverous/jeeves-server/pull/139)
|
|
28
|
+
- [53] chore: bump core to v0.4.6 (init before run) + fix knip issues [`e5f466a`](https://github.com/karmaniverous/jeeves-server/commit/e5f466a0df574e1052601fc99b0110aaf2136666)
|
|
29
|
+
- [53] chore: bump core to v0.4.6 (init before run) + fix knip issues [`cc2adb6`](https://github.com/karmaniverous/jeeves-server/commit/cc2adb6c4d2a540a145bdcb04a712b2f6b77fa9e)
|
|
30
|
+
- chore: release @karmaniverous/jeeves-server-openclaw v0.6.1 [`b0a4bec`](https://github.com/karmaniverous/jeeves-server/commit/b0a4bec47f5141d13e59070683f7d459e82a9faa)
|
|
31
|
+
- chore: release @karmaniverous/jeeves-server v3.5.2 [`08f6a37`](https://github.com/karmaniverous/jeeves-server/commit/08f6a37a4bc2802b9c1e65a2ed38482fffa2678b)
|
|
32
|
+
|
|
33
|
+
#### [service/3.5.1](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.0...service/3.5.1)
|
|
34
|
+
|
|
35
|
+
> 31 March 2026
|
|
6
36
|
|
|
7
37
|
- [51] Integrate descriptor.run from core v0.4.5 [`#137`](https://github.com/karmaniverous/jeeves-server/pull/137)
|
|
8
38
|
- chore: release @karmaniverous/jeeves-server-openclaw v0.6.0 [`bfa7501`](https://github.com/karmaniverous/jeeves-server/commit/bfa7501f80fb1c2c8a29f7e548b4092f1bc44395)
|
|
9
39
|
- [51] feat: integrate descriptor.run from core v0.4.5 [`74bebef`](https://github.com/karmaniverous/jeeves-server/commit/74bebef9adfe433621c1f4dd1b4877634ee65f6f)
|
|
40
|
+
- chore: release @karmaniverous/jeeves-server v3.5.1 [`fd56acc`](https://github.com/karmaniverous/jeeves-server/commit/fd56accdacacc97bf47f18e489df6d87b8d6be35)
|
|
10
41
|
|
|
11
42
|
#### [service/3.5.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.4.2...service/3.5.0)
|
|
12
43
|
|
package/client/package.json
CHANGED
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"@codemirror/state": "^6.6.0",
|
|
29
29
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
30
30
|
"@codemirror/view": "^6.40.0",
|
|
31
|
-
"@karmaniverous/jeeves": "^0.4.5",
|
|
32
31
|
"@panzoom/panzoom": "^4.6.1",
|
|
33
32
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
34
33
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
@@ -55,7 +55,11 @@ export function DirectoryRow({ entry, basePath, isInsider, shareSettings, onShar
|
|
|
55
55
|
</div>
|
|
56
56
|
</td>
|
|
57
57
|
<td className="px-4 py-2.5 text-muted-foreground text-sm">{typeLabel}</td>
|
|
58
|
-
<td className="px-4 py-2.5 text-muted-foreground text-sm">
|
|
58
|
+
<td className="px-4 py-2.5 text-muted-foreground text-sm">
|
|
59
|
+
{isDir
|
|
60
|
+
? (entry.itemCount != null ? `${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'}` : '-')
|
|
61
|
+
: formatSize(entry.size)}
|
|
62
|
+
</td>
|
|
59
63
|
<td className="px-4 py-2.5 text-muted-foreground text-sm">{entry.mtime ?? '-'}</td>
|
|
60
64
|
</tr>
|
|
61
65
|
);
|
|
@@ -115,6 +115,13 @@ export function FileContentView({
|
|
|
115
115
|
/>
|
|
116
116
|
)}
|
|
117
117
|
|
|
118
|
+
{/* CSV */}
|
|
119
|
+
{fileRendered?.type === 'csv' && fileRendered.html && activeTab === 'rendered' && (
|
|
120
|
+
<div className={`prose prose-sm dark:prose-invert max-w-none ${proseWidth === 'narrow' ? 'max-w-prose mx-auto' : proseWidth === 'medium' ? 'max-w-4xl mx-auto' : ''}`}>
|
|
121
|
+
<div className="overflow-x-auto" dangerouslySetInnerHTML={{ __html: fileRendered.html }} />
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
|
|
118
125
|
{/* SVG */}
|
|
119
126
|
{fileRendered?.type === 'svg' && fileRendered.content && activeTab === 'rendered' && (
|
|
120
127
|
<SvgViewer content={fileRendered.content} />
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Markdown rendered view with TOC sidebar.
|
|
2
|
+
* Markdown rendered view with collapsible TOC sidebar.
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
5
|
+
|
|
5
6
|
import { initEmbeddedDiagramPanzoom } from '@/components/EmbeddedDiagramPanzoom';
|
|
6
7
|
import { initLazyDiagrams } from '@/components/LazyDiagram';
|
|
7
8
|
import { initInlineSvgPanzoom } from '@/components/InlineSvgPanzoom';
|
|
9
|
+
import { TocSection } from '@/components/TocSection';
|
|
10
|
+
import { buildTocTree, findAncestorSlugs } from '@/components/tocUtils';
|
|
11
|
+
import type { FileContent } from '@/lib/api';
|
|
8
12
|
import { initCodeBlockCm6 } from '@/lib/codeBlockCm6';
|
|
9
13
|
import { injectCopyButtons } from '@/lib/codeBlockCopy';
|
|
10
14
|
import { useTheme } from '@/lib/theme';
|
|
@@ -27,6 +31,47 @@ export function MarkdownView({
|
|
|
27
31
|
const plainCode = new URLSearchParams(window.location.search).has('plain_code');
|
|
28
32
|
const hasHeadings = fileRendered.headings && fileRendered.headings.length > 2;
|
|
29
33
|
|
|
34
|
+
// Build TOC tree and collapse state
|
|
35
|
+
const tocTree = useMemo(
|
|
36
|
+
() => (hasHeadings ? buildTocTree(fileRendered.headings!) : []),
|
|
37
|
+
[fileRendered.headings, hasHeadings],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
|
|
41
|
+
|
|
42
|
+
const toggleCollapse = useCallback((slug: string) => {
|
|
43
|
+
setCollapsed((prev) => {
|
|
44
|
+
const next = new Set(prev);
|
|
45
|
+
if (next.has(slug)) next.delete(slug);
|
|
46
|
+
else next.add(slug);
|
|
47
|
+
return next;
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const scrollToHeading = useCallback(
|
|
52
|
+
(slug: string) => {
|
|
53
|
+
// Auto-expand ancestors if collapsed
|
|
54
|
+
const ancestors = findAncestorSlugs(tocTree, slug);
|
|
55
|
+
if (ancestors.some((a) => collapsed.has(a))) {
|
|
56
|
+
setCollapsed((prev) => {
|
|
57
|
+
const next = new Set(prev);
|
|
58
|
+
for (const a of ancestors) next.delete(a);
|
|
59
|
+
return next;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
scrollToIdInContainer(mainRef.current, slug);
|
|
63
|
+
},
|
|
64
|
+
[tocTree, collapsed, mainRef],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const mobileScrollTo = useCallback(
|
|
68
|
+
(slug: string) => {
|
|
69
|
+
scrollToHeading(slug);
|
|
70
|
+
setMobileTocOpen(false);
|
|
71
|
+
},
|
|
72
|
+
[scrollToHeading, setMobileTocOpen],
|
|
73
|
+
);
|
|
74
|
+
|
|
30
75
|
return (
|
|
31
76
|
<>
|
|
32
77
|
{/* Mobile TOC overlay */}
|
|
@@ -38,16 +83,14 @@ export function MarkdownView({
|
|
|
38
83
|
style={{ top: `${topBarHeight + 4}px` }}
|
|
39
84
|
>
|
|
40
85
|
<nav>
|
|
41
|
-
{
|
|
42
|
-
<
|
|
43
|
-
key={
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{h.text}
|
|
50
|
-
</button>
|
|
86
|
+
{tocTree.map((node) => (
|
|
87
|
+
<TocSection
|
|
88
|
+
key={node.heading.slug}
|
|
89
|
+
node={node}
|
|
90
|
+
collapsed={collapsed}
|
|
91
|
+
toggleCollapse={toggleCollapse}
|
|
92
|
+
scrollTo={mobileScrollTo}
|
|
93
|
+
/>
|
|
51
94
|
))}
|
|
52
95
|
</nav>
|
|
53
96
|
</div>
|
|
@@ -63,16 +106,14 @@ export function MarkdownView({
|
|
|
63
106
|
>
|
|
64
107
|
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">Contents</div>
|
|
65
108
|
<nav className="border-l border-border pl-3">
|
|
66
|
-
{
|
|
67
|
-
<
|
|
68
|
-
key={
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{h.text}
|
|
75
|
-
</button>
|
|
109
|
+
{tocTree.map((node) => (
|
|
110
|
+
<TocSection
|
|
111
|
+
key={node.heading.slug}
|
|
112
|
+
node={node}
|
|
113
|
+
collapsed={collapsed}
|
|
114
|
+
toggleCollapse={toggleCollapse}
|
|
115
|
+
scrollTo={scrollToHeading}
|
|
116
|
+
/>
|
|
76
117
|
))}
|
|
77
118
|
</nav>
|
|
78
119
|
</aside>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursive collapsible TOC section component.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { ChevronRight } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
import type { TocNode } from './tocUtils.js';
|
|
9
|
+
|
|
10
|
+
interface TocSectionProps {
|
|
11
|
+
node: TocNode;
|
|
12
|
+
collapsed: Set<string>;
|
|
13
|
+
toggleCollapse: (slug: string) => void;
|
|
14
|
+
activeSlug?: string;
|
|
15
|
+
scrollTo: (slug: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function TocSection({
|
|
19
|
+
node,
|
|
20
|
+
collapsed,
|
|
21
|
+
toggleCollapse,
|
|
22
|
+
activeSlug,
|
|
23
|
+
scrollTo,
|
|
24
|
+
}: TocSectionProps) {
|
|
25
|
+
const hasChildren = node.children.length > 0;
|
|
26
|
+
const isCollapsed = collapsed.has(node.heading.slug);
|
|
27
|
+
const isActive = activeSlug === node.heading.slug;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<div className="flex items-center">
|
|
32
|
+
{hasChildren ? (
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={() => toggleCollapse(node.heading.slug)}
|
|
36
|
+
className="w-4 h-4 flex items-center justify-center text-muted-foreground shrink-0"
|
|
37
|
+
>
|
|
38
|
+
<ChevronRight
|
|
39
|
+
className={`h-3 w-3 transition-transform ${isCollapsed ? '' : 'rotate-90'}`}
|
|
40
|
+
/>
|
|
41
|
+
</button>
|
|
42
|
+
) : (
|
|
43
|
+
<span className="w-4 shrink-0" />
|
|
44
|
+
)}
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => scrollTo(node.heading.slug)}
|
|
48
|
+
className={`text-left text-sm py-0.5 transition-colors truncate ${
|
|
49
|
+
isActive
|
|
50
|
+
? 'text-foreground font-medium'
|
|
51
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
52
|
+
}`}
|
|
53
|
+
style={{ paddingLeft: `${(node.heading.level - 1) * 0.75}rem` }}
|
|
54
|
+
>
|
|
55
|
+
{node.heading.text}
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
{hasChildren && !isCollapsed && (
|
|
59
|
+
<div>
|
|
60
|
+
{node.children.map((child) => (
|
|
61
|
+
<TocSection
|
|
62
|
+
key={child.heading.slug}
|
|
63
|
+
node={child}
|
|
64
|
+
collapsed={collapsed}
|
|
65
|
+
toggleCollapse={toggleCollapse}
|
|
66
|
+
activeSlug={activeSlug}
|
|
67
|
+
scrollTo={scrollTo}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { FileContent } from '@/lib/api';
|
|
5
5
|
|
|
6
|
-
const RENDERABLE_EXTENSIONS = new Set(['.md', '.svg', '.mmd', '.puml', '.plantuml', '.pu']);
|
|
6
|
+
const RENDERABLE_EXTENSIONS = new Set(['.md', '.csv', '.svg', '.mmd', '.puml', '.plantuml', '.pu']);
|
|
7
7
|
|
|
8
8
|
export function isRenderable(file: FileContent): boolean {
|
|
9
|
-
return file.type === 'markdown' || file.type === 'svg' || file.type === 'mermaid' || file.type === 'plantuml' || !!file.html;
|
|
9
|
+
return file.type === 'markdown' || file.type === 'csv' || file.type === 'svg' || file.type === 'mermaid' || file.type === 'plantuml' || !!file.html;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function isRenderableExt(reqPath: string): boolean {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOC tree utilities for building and navigating heading hierarchies.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TocNode {
|
|
8
|
+
heading: { level: number; text: string; slug: string };
|
|
9
|
+
children: TocNode[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build a tree of TocNodes from a flat heading list.
|
|
14
|
+
*/
|
|
15
|
+
export function buildTocTree(
|
|
16
|
+
headings: { level: number; text: string; slug: string }[],
|
|
17
|
+
): TocNode[] {
|
|
18
|
+
const root: TocNode[] = [];
|
|
19
|
+
const stack: TocNode[] = [];
|
|
20
|
+
|
|
21
|
+
for (const heading of headings) {
|
|
22
|
+
const node: TocNode = { heading, children: [] };
|
|
23
|
+
|
|
24
|
+
// Pop stack until we find a parent with a lower level
|
|
25
|
+
while (
|
|
26
|
+
stack.length > 0 &&
|
|
27
|
+
stack[stack.length - 1]!.heading.level >= heading.level
|
|
28
|
+
) {
|
|
29
|
+
stack.pop();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (stack.length === 0) {
|
|
33
|
+
root.push(node);
|
|
34
|
+
} else {
|
|
35
|
+
stack[stack.length - 1]!.children.push(node);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
stack.push(node);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return root;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find all ancestor slugs for a given slug in the tree.
|
|
46
|
+
*/
|
|
47
|
+
export function findAncestorSlugs(
|
|
48
|
+
nodes: TocNode[],
|
|
49
|
+
targetSlug: string,
|
|
50
|
+
): string[] {
|
|
51
|
+
const path: string[] = [];
|
|
52
|
+
|
|
53
|
+
function walk(list: TocNode[]): boolean {
|
|
54
|
+
for (const node of list) {
|
|
55
|
+
if (node.heading.slug === targetSlug) return true;
|
|
56
|
+
path.push(node.heading.slug);
|
|
57
|
+
if (walk(node.children)) return true;
|
|
58
|
+
path.pop();
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
walk(nodes);
|
|
64
|
+
return path;
|
|
65
|
+
}
|
package/client/src/index.css
CHANGED
|
@@ -279,3 +279,39 @@ pre:hover .code-copy-btn {
|
|
|
279
279
|
.diagram-retry-btn:hover {
|
|
280
280
|
background: var(--color-accent);
|
|
281
281
|
}
|
|
282
|
+
|
|
283
|
+
/* CSV table styles */
|
|
284
|
+
.csv-table {
|
|
285
|
+
width: 100%;
|
|
286
|
+
border-collapse: collapse;
|
|
287
|
+
font-size: 0.875rem;
|
|
288
|
+
line-height: 1.5;
|
|
289
|
+
}
|
|
290
|
+
.csv-table th,
|
|
291
|
+
.csv-table td {
|
|
292
|
+
border: 1px solid var(--color-border);
|
|
293
|
+
padding: 0.5rem 0.75rem;
|
|
294
|
+
text-align: left;
|
|
295
|
+
}
|
|
296
|
+
.csv-table th {
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
background: var(--color-muted);
|
|
299
|
+
}
|
|
300
|
+
.csv-table tbody tr:nth-child(even) {
|
|
301
|
+
background: color-mix(in srgb, var(--color-muted) 40%, transparent);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Collapsible frontmatter */
|
|
305
|
+
.frontmatter-collapsible .frontmatter-full { display: none; }
|
|
306
|
+
.frontmatter-collapsible .frontmatter-preview { display: block; }
|
|
307
|
+
.frontmatter-collapsible.frontmatter-expanded .frontmatter-full { display: block; }
|
|
308
|
+
.frontmatter-collapsible.frontmatter-expanded .frontmatter-preview { display: none; }
|
|
309
|
+
.frontmatter-toggle {
|
|
310
|
+
font-size: 0.75rem;
|
|
311
|
+
color: var(--color-muted-foreground);
|
|
312
|
+
cursor: pointer;
|
|
313
|
+
padding: 0.25rem 0;
|
|
314
|
+
border: none;
|
|
315
|
+
background: none;
|
|
316
|
+
}
|
|
317
|
+
.frontmatter-toggle:hover { text-decoration: underline; }
|
package/client/src/lib/api.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface DirectoryEntry {
|
|
|
37
37
|
ext: string;
|
|
38
38
|
size: number | null;
|
|
39
39
|
mtime: string | null;
|
|
40
|
+
itemCount?: number | null;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export interface DirectoryListing {
|
|
@@ -59,7 +60,7 @@ export interface DriveEntry {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export interface FileContent {
|
|
62
|
-
type: 'markdown' | 'text' | 'svg' | 'mermaid' | 'plantuml' | 'image' | 'binary';
|
|
63
|
+
type: 'markdown' | 'text' | 'csv' | 'svg' | 'mermaid' | 'plantuml' | 'image' | 'binary';
|
|
63
64
|
content?: string;
|
|
64
65
|
html?: string;
|
|
65
66
|
headings?: { level: number; text: string; slug: string }[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e,l as t,n,o as r,r as i,t as a}from"./theme-CPpIxvB0.js";import{n as o,t as s}from"./index-
|
|
1
|
+
import{i as e,l as t,n,o as r,r as i,t as a}from"./theme-CPpIxvB0.js";import{n as o,t as s}from"./index-BXy6kgl7.js";var c=o(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),l=t(r(),1),u=e();function d({content:e,fileName:t}){let r=(0,l.useRef)(null),o=(0,l.useRef)(null),[d,f]=(0,l.useState)(!0),[p,m]=(0,l.useState)(!1),[h]=a(),g=async()=>{await navigator.clipboard.writeText(e),m(!0),setTimeout(()=>m(!1),1500)};return(0,l.useEffect)(()=>{if(!r.current)return;let a=!1;return(async()=>{let{EditorView:s,EditorState:c,basicSetup:l,oneDark:u}=await i();if(a)return;let d=await n(t.split(`.`).pop()??``);if(a)return;let p=[l,c.readOnly.of(!0),s.editable.of(!1),s.theme({"&":{fontSize:`14px`},".cm-scroller":{overflow:`auto`},".cm-content":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-gutters":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-cursor":{display:`none`}})];h===`dark`&&p.push(u),d&&p.push(d),o.current=new s({state:c.create({doc:e,extensions:p}),parent:r.current}),f(!1)})(),()=>{a=!0,o.current&&=(o.current.destroy(),null)}},[e,t,h]),(0,u.jsxs)(`div`,{className:`relative group rounded-lg border border-border overflow-hidden`,children:[(0,u.jsx)(`div`,{className:`absolute top-2 right-2 z-10`,children:(0,u.jsx)(`button`,{onClick:()=>void g(),className:`p-1.5 rounded bg-accent hover:bg-accent/80 text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-all`,title:`Copy to clipboard`,children:p?(0,u.jsx)(s,{className:`h-3.5 w-3.5 text-green-400`}):(0,u.jsx)(c,{className:`h-3.5 w-3.5`})})}),(0,u.jsx)(`div`,{ref:r,children:d&&(0,u.jsx)(`pre`,{className:`p-4 text-sm text-muted-foreground bg-muted`,children:(0,u.jsxs)(`code`,{children:[e.slice(0,200),`…`]})})})]})}export{d as CodeViewer};
|