wiki-viewer 1.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.
Files changed (119) hide show
  1. package/.next/standalone/.github/workflows/publish.yml +37 -0
  2. package/.next/standalone/DESIGN.md +521 -0
  3. package/.next/standalone/README.md +283 -0
  4. package/.next/standalone/bin/wiki-viewer.js +158 -0
  5. package/.next/standalone/next.config.ts +11 -0
  6. package/.next/standalone/package.json +120 -0
  7. package/.next/standalone/pnpm-lock.yaml +5654 -0
  8. package/.next/standalone/pnpm-workspace.yaml +2 -0
  9. package/.next/standalone/postcss.config.mjs +2 -0
  10. package/.next/standalone/public/logo.svg +11 -0
  11. package/.next/standalone/public/public/logo.svg +11 -0
  12. package/.next/standalone/server.js +44 -0
  13. package/.next/standalone/src/app/api/app-proxy/[...path]/route.ts +247 -0
  14. package/.next/standalone/src/app/api/assets/[...path]/route.ts +70 -0
  15. package/.next/standalone/src/app/api/system/browse/route.ts +78 -0
  16. package/.next/standalone/src/app/api/system/clear-root/route.ts +9 -0
  17. package/.next/standalone/src/app/api/system/config/route.ts +10 -0
  18. package/.next/standalone/src/app/api/system/pins/route.ts +31 -0
  19. package/.next/standalone/src/app/api/system/reveal/route.ts +28 -0
  20. package/.next/standalone/src/app/api/system/root-status/route.ts +9 -0
  21. package/.next/standalone/src/app/api/system/set-root/route.ts +23 -0
  22. package/.next/standalone/src/app/api/upload/[...path]/route.ts +80 -0
  23. package/.next/standalone/src/app/api/wiki/app/route.ts +41 -0
  24. package/.next/standalone/src/app/api/wiki/content/route.ts +60 -0
  25. package/.next/standalone/src/app/api/wiki/file/route.ts +52 -0
  26. package/.next/standalone/src/app/api/wiki/folder/route.ts +26 -0
  27. package/.next/standalone/src/app/api/wiki/move/route.ts +45 -0
  28. package/.next/standalone/src/app/api/wiki/page/route.ts +84 -0
  29. package/.next/standalone/src/app/api/wiki/route.ts +98 -0
  30. package/.next/standalone/src/app/api/wiki/slugs/route.ts +50 -0
  31. package/.next/standalone/src/app/api/wiki/upload/route.ts +87 -0
  32. package/.next/standalone/src/app/api/wiki/watch/route.ts +86 -0
  33. package/.next/standalone/src/app/globals.css +716 -0
  34. package/.next/standalone/src/app/icon.svg +11 -0
  35. package/.next/standalone/src/app/layout.tsx +35 -0
  36. package/.next/standalone/src/app/page.tsx +1509 -0
  37. package/.next/standalone/src/components/confirm-dialog.tsx +56 -0
  38. package/.next/standalone/src/components/dir-picker.tsx +393 -0
  39. package/.next/standalone/src/components/editor/bubble-menu.tsx +286 -0
  40. package/.next/standalone/src/components/editor/callout-extension.ts +36 -0
  41. package/.next/standalone/src/components/editor/csv-viewer.tsx +335 -0
  42. package/.next/standalone/src/components/editor/editor-toolbar.tsx +564 -0
  43. package/.next/standalone/src/components/editor/editor.tsx +633 -0
  44. package/.next/standalone/src/components/editor/extensions/drag-handle.ts +325 -0
  45. package/.next/standalone/src/components/editor/extensions/heading-anchors.ts +55 -0
  46. package/.next/standalone/src/components/editor/extensions/resizable-image.tsx +159 -0
  47. package/.next/standalone/src/components/editor/extensions.ts +148 -0
  48. package/.next/standalone/src/components/editor/file-fallback-viewer.tsx +74 -0
  49. package/.next/standalone/src/components/editor/folder-index.tsx +235 -0
  50. package/.next/standalone/src/components/editor/google-doc-viewer.tsx +73 -0
  51. package/.next/standalone/src/components/editor/image-viewer.tsx +56 -0
  52. package/.next/standalone/src/components/editor/link-popover.tsx +111 -0
  53. package/.next/standalone/src/components/editor/media-popover.tsx +211 -0
  54. package/.next/standalone/src/components/editor/media-viewer.tsx +69 -0
  55. package/.next/standalone/src/components/editor/mermaid-viewer.tsx +285 -0
  56. package/.next/standalone/src/components/editor/node-app-viewer.tsx +269 -0
  57. package/.next/standalone/src/components/editor/notebook-viewer.tsx +370 -0
  58. package/.next/standalone/src/components/editor/office/docx-viewer.tsx +92 -0
  59. package/.next/standalone/src/components/editor/office/office-chrome.tsx +82 -0
  60. package/.next/standalone/src/components/editor/office/pptx-viewer.tsx +103 -0
  61. package/.next/standalone/src/components/editor/office/xlsx-viewer.tsx +116 -0
  62. package/.next/standalone/src/components/editor/pdf-viewer.tsx +31 -0
  63. package/.next/standalone/src/components/editor/slash-commands.tsx +454 -0
  64. package/.next/standalone/src/components/editor/source-viewer.tsx +212 -0
  65. package/.next/standalone/src/components/editor/table-menu.tsx +232 -0
  66. package/.next/standalone/src/components/editor/website-viewer.tsx +74 -0
  67. package/.next/standalone/src/components/editor/wiki-link-create-dialog.tsx +188 -0
  68. package/.next/standalone/src/components/editor/wiki-link-decorator.ts +79 -0
  69. package/.next/standalone/src/components/editor/wiki-link-extension.ts +120 -0
  70. package/.next/standalone/src/components/editor/wiki-link-picker.tsx +250 -0
  71. package/.next/standalone/src/components/layout/viewer-toolbar.tsx +54 -0
  72. package/.next/standalone/src/components/theme-provider.tsx +16 -0
  73. package/.next/standalone/src/components/theme-toggle.tsx +24 -0
  74. package/.next/standalone/src/components/ui/badge.tsx +35 -0
  75. package/.next/standalone/src/components/ui/button.tsx +56 -0
  76. package/.next/standalone/src/components/ui/card.tsx +63 -0
  77. package/.next/standalone/src/components/ui/collapsible.tsx +9 -0
  78. package/.next/standalone/src/components/ui/command.tsx +150 -0
  79. package/.next/standalone/src/components/ui/context-menu.tsx +191 -0
  80. package/.next/standalone/src/components/ui/dialog.tsx +119 -0
  81. package/.next/standalone/src/components/ui/dropdown-menu.tsx +194 -0
  82. package/.next/standalone/src/components/ui/input.tsx +22 -0
  83. package/.next/standalone/src/components/ui/label.tsx +25 -0
  84. package/.next/standalone/src/components/ui/popover.tsx +33 -0
  85. package/.next/standalone/src/components/ui/scroll-area.tsx +47 -0
  86. package/.next/standalone/src/components/ui/select.tsx +156 -0
  87. package/.next/standalone/src/components/ui/separator.tsx +30 -0
  88. package/.next/standalone/src/components/ui/sheet.tsx +140 -0
  89. package/.next/standalone/src/components/ui/skeleton.tsx +15 -0
  90. package/.next/standalone/src/components/ui/switch.tsx +28 -0
  91. package/.next/standalone/src/components/ui/tabs.tsx +54 -0
  92. package/.next/standalone/src/components/ui/textarea.tsx +21 -0
  93. package/.next/standalone/src/components/ui/tip.tsx +23 -0
  94. package/.next/standalone/src/components/ui/toggle-group.tsx +41 -0
  95. package/.next/standalone/src/components/ui/tooltip.tsx +27 -0
  96. package/.next/standalone/src/components/wiki/frontmatter-header.tsx +164 -0
  97. package/.next/standalone/src/lib/app-runner.ts +231 -0
  98. package/.next/standalone/src/lib/cabinets/tree.ts +75 -0
  99. package/.next/standalone/src/lib/config.ts +32 -0
  100. package/.next/standalone/src/lib/embeds/detect.ts +142 -0
  101. package/.next/standalone/src/lib/google/detect.ts +107 -0
  102. package/.next/standalone/src/lib/markdown/parse-frontmatter.ts +84 -0
  103. package/.next/standalone/src/lib/markdown/remark-wikilinks.ts +100 -0
  104. package/.next/standalone/src/lib/markdown/to-html.ts +168 -0
  105. package/.next/standalone/src/lib/markdown/to-markdown.ts +238 -0
  106. package/.next/standalone/src/lib/root-dir.ts +35 -0
  107. package/.next/standalone/src/lib/toast.ts +9 -0
  108. package/.next/standalone/src/lib/utils.ts +6 -0
  109. package/.next/standalone/src/lib/wiki-helpers.ts +36 -0
  110. package/.next/standalone/src/stores/ai-panel-store.ts +23 -0
  111. package/.next/standalone/src/stores/editor-store.ts +244 -0
  112. package/.next/standalone/src/stores/tree-store.ts +52 -0
  113. package/.next/standalone/src/stores/wiki-slugs-store.ts +74 -0
  114. package/.next/standalone/src/types/index.ts +47 -0
  115. package/.next/standalone/tailwind.config.ts +201 -0
  116. package/.next/standalone/tsconfig.json +41 -0
  117. package/README.md +283 -0
  118. package/bin/wiki-viewer.js +158 -0
  119. package/package.json +120 -0
@@ -0,0 +1,92 @@
1
+ "use client";
2
+
3
+ import { Loader2 } from "lucide-react";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { OfficeChrome } from "./office-chrome";
6
+
7
+ interface Props {
8
+ path: string;
9
+ title: string;
10
+ }
11
+
12
+ export function DocxViewer({ path, title }: Props) {
13
+ const containerRef = useRef<HTMLDivElement | null>(null);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ useEffect(() => {
18
+ let cancelled = false;
19
+ const container = containerRef.current;
20
+ if (!container) return;
21
+
22
+ // Clear any previous render
23
+ container.innerHTML = "";
24
+
25
+ (async () => {
26
+ setLoading(true);
27
+ setError(null);
28
+ try {
29
+ const [{ renderAsync }, res] = await Promise.all([
30
+ import("docx-preview"),
31
+ fetch(`/api/assets/${path}`),
32
+ ]);
33
+ if (cancelled) return;
34
+ if (!res.ok) throw new Error(`Failed to load file (${res.status})`);
35
+ const blob = await res.blob();
36
+ if (cancelled) return;
37
+ await renderAsync(blob, container, undefined, {
38
+ className: "docx-rendered",
39
+ inWrapper: true,
40
+ ignoreWidth: false,
41
+ ignoreHeight: false,
42
+ breakPages: true,
43
+ renderHeaders: true,
44
+ renderFooters: true,
45
+ renderFootnotes: true,
46
+ experimental: true,
47
+ useBase64URL: true,
48
+ });
49
+ if (!cancelled) setLoading(false);
50
+ } catch (err) {
51
+ if (!cancelled) {
52
+ setError(
53
+ err instanceof Error ? err.message : "Failed to render document",
54
+ );
55
+ setLoading(false);
56
+ }
57
+ }
58
+ })();
59
+
60
+ return () => {
61
+ cancelled = true;
62
+ };
63
+ }, [path]);
64
+
65
+ return (
66
+ <div className="flex-1 flex flex-col overflow-hidden">
67
+ <OfficeChrome path={path} title={title} extLabel="DOCX" />
68
+ <div className="flex-1 overflow-y-auto bg-muted/30">
69
+ {loading && !error && (
70
+ <div className="h-full flex items-center justify-center text-muted-foreground">
71
+ <Loader2 className="h-5 w-5 animate-spin mr-2" />
72
+ Rendering document…
73
+ </div>
74
+ )}
75
+ {error && (
76
+ <div className="h-full flex items-center justify-center">
77
+ <div className="text-center space-y-2">
78
+ <p className="text-sm text-destructive">{error}</p>
79
+ <p className="text-xs text-muted-foreground">
80
+ Try downloading the file and opening it externally.
81
+ </p>
82
+ </div>
83
+ </div>
84
+ )}
85
+ <div
86
+ ref={containerRef}
87
+ className="docx-viewer-body mx-auto max-w-5xl py-6 px-4"
88
+ />
89
+ </div>
90
+ </div>
91
+ );
92
+ }
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ import { Download, ExternalLink, FolderOpen } from "lucide-react";
4
+ import { ViewerToolbar } from "@/components/layout/viewer-toolbar";
5
+ import { Button } from "@/components/ui/button";
6
+
7
+ interface OfficeChromeProps {
8
+ path: string;
9
+ title: string;
10
+ extLabel: string;
11
+ /** Optional external "open in source" action (e.g. Open in Google). */
12
+ external?: { label: string; href: string };
13
+ /** Hide the "Open in Finder" button (useful for Google embeds that aren't on disk). */
14
+ hideFinder?: boolean;
15
+ }
16
+
17
+ export function OfficeChrome({
18
+ path,
19
+ extLabel,
20
+ external,
21
+ hideFinder,
22
+ }: OfficeChromeProps) {
23
+ const assetUrl = `/api/assets/${path}`;
24
+ const filename = path.split("/").pop() || path;
25
+
26
+ const revealInFinder = async () => {
27
+ try {
28
+ await fetch("/api/system/reveal", {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ path }),
32
+ });
33
+ } catch {
34
+ /* ignore */
35
+ }
36
+ };
37
+
38
+ return (
39
+ <ViewerToolbar path={path} badge={extLabel || undefined}>
40
+ {external && (
41
+ <a
42
+ href={external.href}
43
+ target="_blank"
44
+ rel="noreferrer noopener"
45
+ className="inline-flex items-center gap-1.5 text-[11px] h-7 px-2.5 rounded-md border border-border hover:bg-accent transition-colors"
46
+ >
47
+ <ExternalLink className="h-3.5 w-3.5" />
48
+ {external.label}
49
+ </a>
50
+ )}
51
+ {!hideFinder && (
52
+ <Button
53
+ variant="outline"
54
+ size="sm"
55
+ className="gap-1.5 text-[11px] h-7"
56
+ onClick={revealInFinder}
57
+ title="Open in Finder"
58
+ >
59
+ <FolderOpen className="h-3.5 w-3.5" />
60
+ Reveal
61
+ </Button>
62
+ )}
63
+ {!hideFinder && (
64
+ <Button
65
+ variant="outline"
66
+ size="sm"
67
+ className="gap-1.5 text-[11px] h-7"
68
+ onClick={() => {
69
+ const a = document.createElement("a");
70
+ a.href = assetUrl;
71
+ a.download = filename;
72
+ a.click();
73
+ }}
74
+ title="Download original"
75
+ >
76
+ <Download className="h-3.5 w-3.5" />
77
+ Download
78
+ </Button>
79
+ )}
80
+ </ViewerToolbar>
81
+ );
82
+ }
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { Loader2 } from "lucide-react";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { OfficeChrome } from "./office-chrome";
6
+
7
+ interface Props {
8
+ path: string;
9
+ title: string;
10
+ }
11
+
12
+ export function PptxViewer({ path, title }: Props) {
13
+ const containerRef = useRef<HTMLDivElement | null>(null);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ useEffect(() => {
18
+ let cancelled = false;
19
+ const container = containerRef.current;
20
+ if (!container) return;
21
+ container.innerHTML = "";
22
+
23
+ let previewer: { destroy?: () => void } | null = null;
24
+
25
+ (async () => {
26
+ setLoading(true);
27
+ setError(null);
28
+ try {
29
+ const [{ init }, res] = await Promise.all([
30
+ import("pptx-preview"),
31
+ fetch(`/api/assets/${path}`),
32
+ ]);
33
+ if (cancelled) return;
34
+ if (!res.ok) throw new Error(`Failed to load file (${res.status})`);
35
+ const buf = await res.arrayBuffer();
36
+ if (cancelled) return;
37
+
38
+ const width = container.clientWidth || 960;
39
+ const height = Math.round((width * 9) / 16);
40
+ previewer = init(container, {
41
+ width,
42
+ height,
43
+ mode: "list",
44
+ }) as unknown as {
45
+ destroy?: () => void;
46
+ preview: (buf: ArrayBuffer) => Promise<unknown>;
47
+ };
48
+ await (
49
+ previewer as unknown as {
50
+ preview: (b: ArrayBuffer) => Promise<unknown>;
51
+ }
52
+ ).preview(buf);
53
+ if (!cancelled) setLoading(false);
54
+ } catch (err) {
55
+ if (!cancelled) {
56
+ setError(
57
+ err instanceof Error
58
+ ? err.message
59
+ : "Failed to render presentation",
60
+ );
61
+ setLoading(false);
62
+ }
63
+ }
64
+ })();
65
+
66
+ return () => {
67
+ cancelled = true;
68
+ try {
69
+ previewer?.destroy?.();
70
+ } catch {
71
+ /* ignore */
72
+ }
73
+ };
74
+ }, [path]);
75
+
76
+ return (
77
+ <div className="flex-1 flex flex-col overflow-hidden">
78
+ <OfficeChrome path={path} title={title} extLabel="PPTX" />
79
+ <div className="flex-1 overflow-auto bg-muted/30 py-4">
80
+ {loading && !error && (
81
+ <div className="h-[60vh] flex items-center justify-center text-muted-foreground">
82
+ <Loader2 className="h-5 w-5 animate-spin mr-2" />
83
+ Rendering slides…
84
+ </div>
85
+ )}
86
+ {error && (
87
+ <div className="h-[60vh] flex items-center justify-center">
88
+ <div className="text-center space-y-2">
89
+ <p className="text-sm text-destructive">{error}</p>
90
+ <p className="text-xs text-muted-foreground">
91
+ Try downloading the file and opening it externally.
92
+ </p>
93
+ </div>
94
+ </div>
95
+ )}
96
+ <div
97
+ ref={containerRef}
98
+ className="pptx-viewer-body mx-auto max-w-5xl px-4"
99
+ />
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ import { Loader2 } from "lucide-react";
4
+ import { useEffect, useMemo, useState } from "react";
5
+ import { cn } from "@/lib/utils";
6
+ import { OfficeChrome } from "./office-chrome";
7
+
8
+ interface Props {
9
+ path: string;
10
+ title: string;
11
+ }
12
+
13
+ interface Sheet {
14
+ name: string;
15
+ html: string;
16
+ }
17
+
18
+ export function XlsxViewer({ path, title }: Props) {
19
+ const [sheets, setSheets] = useState<Sheet[] | null>(null);
20
+ const [active, setActive] = useState(0);
21
+ const [loading, setLoading] = useState(true);
22
+ const [error, setError] = useState<string | null>(null);
23
+
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ setLoading(true);
27
+ setError(null);
28
+ setSheets(null);
29
+ (async () => {
30
+ try {
31
+ const [XLSX, res] = await Promise.all([
32
+ import("xlsx"),
33
+ fetch(`/api/assets/${path}`),
34
+ ]);
35
+ if (cancelled) return;
36
+ if (!res.ok) throw new Error(`Failed to load file (${res.status})`);
37
+ const buf = await res.arrayBuffer();
38
+ if (cancelled) return;
39
+ const wb = XLSX.read(buf, {
40
+ type: "array",
41
+ cellDates: true,
42
+ cellStyles: true,
43
+ });
44
+ const result: Sheet[] = wb.SheetNames.map((name) => ({
45
+ name,
46
+ html: XLSX.utils.sheet_to_html(wb.Sheets[name], { editable: false }),
47
+ }));
48
+ if (cancelled) return;
49
+ setSheets(result);
50
+ setActive(0);
51
+ setLoading(false);
52
+ } catch (err) {
53
+ if (!cancelled) {
54
+ setError(
55
+ err instanceof Error ? err.message : "Failed to parse spreadsheet",
56
+ );
57
+ setLoading(false);
58
+ }
59
+ }
60
+ })();
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, [path]);
65
+
66
+ const current = useMemo(() => sheets?.[active] ?? null, [sheets, active]);
67
+
68
+ return (
69
+ <div className="flex-1 flex flex-col overflow-hidden">
70
+ <OfficeChrome path={path} title={title} extLabel="XLSX" />
71
+ {sheets && sheets.length > 1 && (
72
+ <div className="flex items-center gap-0.5 border-b border-border bg-muted/40 px-2 overflow-x-auto scrollbar-none">
73
+ {sheets.map((s, i) => (
74
+ <button
75
+ key={s.name + i}
76
+ type="button"
77
+ onClick={() => setActive(i)}
78
+ className={cn(
79
+ "px-3 py-1.5 text-[12px] rounded-t whitespace-nowrap transition-colors",
80
+ i === active
81
+ ? "bg-background text-foreground font-medium border-t border-x border-border -mb-px"
82
+ : "text-muted-foreground hover:text-foreground",
83
+ )}
84
+ >
85
+ {s.name}
86
+ </button>
87
+ ))}
88
+ </div>
89
+ )}
90
+ <div className="flex-1 overflow-auto">
91
+ {loading && !error && (
92
+ <div className="h-full flex items-center justify-center text-muted-foreground">
93
+ <Loader2 className="h-5 w-5 animate-spin mr-2" />
94
+ Parsing spreadsheet…
95
+ </div>
96
+ )}
97
+ {error && (
98
+ <div className="h-full flex items-center justify-center">
99
+ <div className="text-center space-y-2">
100
+ <p className="text-sm text-destructive">{error}</p>
101
+ <p className="text-xs text-muted-foreground">
102
+ Try downloading the file and opening it externally.
103
+ </p>
104
+ </div>
105
+ </div>
106
+ )}
107
+ {current && (
108
+ <div
109
+ className="xlsx-sheet p-3 text-[12px]"
110
+ dangerouslySetInnerHTML={{ __html: current.html }}
111
+ />
112
+ )}
113
+ </div>
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { ExternalLink } from "lucide-react";
4
+ import { ViewerToolbar } from "@/components/layout/viewer-toolbar";
5
+ import { Button } from "@/components/ui/button";
6
+
7
+ interface PdfViewerProps {
8
+ path: string;
9
+ title: string;
10
+ }
11
+
12
+ export function PdfViewer({ path, title }: PdfViewerProps) {
13
+ const pdfSrc = `/api/assets/${path}`;
14
+
15
+ return (
16
+ <div className="flex-1 flex flex-col overflow-hidden">
17
+ <ViewerToolbar path={path} badge="PDF">
18
+ <Button
19
+ variant="ghost"
20
+ size="sm"
21
+ className="h-7 gap-1.5 text-xs"
22
+ onClick={() => window.open(pdfSrc, "_blank")}
23
+ >
24
+ <ExternalLink className="h-3.5 w-3.5" />
25
+ Open in new tab
26
+ </Button>
27
+ </ViewerToolbar>
28
+ <iframe src={pdfSrc} className="flex-1 w-full border-0" title={title} />
29
+ </div>
30
+ );
31
+ }