@peaske7/readit 0.1.7 → 0.2.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/README.md +0 -3
- package/biome.json +1 -1
- package/bun.lock +43 -185
- package/docs/perf-baseline.md +75 -0
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
- package/e2e/perf/add-comment.spec.ts +118 -0
- package/e2e/perf/fixtures/generate.ts +331 -0
- package/e2e/perf/initial-load.spec.ts +49 -0
- package/e2e/perf/perf.setup.ts +23 -0
- package/e2e/perf/perf.teardown.ts +9 -0
- package/e2e/perf/scroll.spec.ts +39 -0
- package/e2e/perf/tab-switch.spec.ts +69 -0
- package/e2e/perf/text-selection.spec.ts +119 -0
- package/e2e/perf/utils/metrics.ts +286 -0
- package/e2e/perf/utils/perf-cli.ts +86 -0
- package/package.json +9 -18
- package/playwright.config.ts +12 -0
- package/src/App.tsx +133 -178
- package/src/{cli/index.ts → cli.ts} +211 -107
- package/src/components/ActionsMenu.tsx +6 -27
- package/src/components/DocumentViewer/DocumentViewer.tsx +78 -105
- package/src/components/DocumentViewer/MermaidDiagram.tsx +6 -7
- package/src/components/Header.tsx +9 -20
- package/src/components/InlineEditor.tsx +5 -5
- package/src/components/MarginNote.tsx +71 -93
- package/src/components/MarginNotes.tsx +7 -34
- package/src/components/RawModal.tsx +9 -8
- package/src/components/ReanchorConfirm.tsx +2 -2
- package/src/components/SettingsModal.tsx +11 -89
- package/src/components/TabBar.tsx +4 -4
- package/src/components/TableOfContents.tsx +5 -5
- package/src/components/comments/CommentInput.tsx +7 -35
- package/src/components/comments/CommentListItem.tsx +9 -11
- package/src/components/comments/CommentManager.tsx +53 -37
- package/src/components/comments/CommentNav.tsx +14 -14
- package/src/components/ui/ActionLink.tsx +14 -18
- package/src/components/ui/Button.tsx +42 -43
- package/src/components/ui/Dialog.tsx +73 -113
- package/src/components/ui/DropdownMenu.tsx +113 -69
- package/src/components/ui/Text.tsx +30 -37
- package/src/contexts/CommentContext.tsx +75 -106
- package/src/contexts/LocaleContext.tsx +45 -4
- package/src/contexts/PositionsContext.tsx +16 -0
- package/src/contexts/SettingsContext.tsx +133 -0
- package/src/hooks/useClickOutside.ts +0 -4
- package/src/hooks/useCommentNavigation.ts +6 -29
- package/src/hooks/useComments.ts +6 -18
- package/src/hooks/useDocument.ts +35 -34
- package/src/hooks/useHeadings.test.ts +8 -50
- package/src/hooks/useHeadings.ts +5 -88
- package/src/hooks/useScrollSpy.ts +10 -14
- package/src/hooks/useTextSelection.ts +1 -38
- package/src/lib/__fixtures__/bench-data.ts +1 -41
- package/src/lib/anchor.bench.ts +57 -67
- package/src/lib/anchor.test.ts +5 -1
- package/src/lib/anchor.ts +13 -93
- package/src/lib/comment-storage.test.ts +4 -4
- package/src/lib/comment-storage.ts +2 -46
- package/src/lib/export.ts +7 -13
- package/src/lib/highlight/core.test.ts +1 -1
- package/src/lib/highlight/dom.ts +5 -68
- package/src/lib/highlight/highlighter.ts +102 -262
- package/src/lib/highlight/resolver.ts +112 -0
- package/src/lib/highlight/types.ts +0 -35
- package/src/lib/highlight/worker.ts +45 -0
- package/src/lib/i18n/en.ts +1 -50
- package/src/lib/i18n/ja.ts +1 -50
- package/src/lib/i18n/types.ts +1 -49
- package/src/lib/margin-layout.ts +5 -27
- package/src/lib/positions.ts +150 -0
- package/src/lib/utils.ts +2 -19
- package/src/schema.ts +81 -0
- package/src/{server/index.ts → server.ts} +111 -81
- package/src/{store/index.ts → store.ts} +14 -46
- package/vite.config.ts +8 -0
- package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
- package/src/components/DocumentViewer/InlineCode.tsx +0 -60
- package/src/components/DocumentViewer/index.ts +0 -1
- package/src/components/FloatingTOC.tsx +0 -61
- package/src/components/ShortcutCapture.tsx +0 -48
- package/src/components/ShortcutList.tsx +0 -198
- package/src/components/comments/CommentMinimap.tsx +0 -62
- package/src/components/ui/ActionBar.tsx +0 -16
- package/src/components/ui/SeparatorDot.tsx +0 -9
- package/src/contexts/LayoutContext.tsx +0 -88
- package/src/hooks/useClipboard.ts +0 -82
- package/src/hooks/useEditorScheme.ts +0 -51
- package/src/hooks/useFontPreference.ts +0 -59
- package/src/hooks/useKeybindings.ts +0 -108
- package/src/hooks/useKeyboardShortcuts.ts +0 -63
- package/src/hooks/useLayoutMode.ts +0 -44
- package/src/hooks/useLocalePreference.ts +0 -42
- package/src/hooks/useReanchorMode.ts +0 -33
- package/src/hooks/useScrollMetrics.ts +0 -56
- package/src/hooks/useThemePreference.ts +0 -66
- package/src/lib/comment-storage.bench.ts +0 -63
- package/src/lib/context.bench.ts +0 -41
- package/src/lib/context.test.ts +0 -224
- package/src/lib/context.ts +0 -193
- package/src/lib/editor-links.ts +0 -59
- package/src/lib/export.bench.ts +0 -35
- package/src/lib/highlight/colors.ts +0 -37
- package/src/lib/highlight/core.ts +0 -54
- package/src/lib/highlight/index.ts +0 -23
- package/src/lib/highlight/script-builder.ts +0 -485
- package/src/lib/html-processor.test.tsx +0 -170
- package/src/lib/html-processor.tsx +0 -95
- package/src/lib/i18n/completeness.test.ts +0 -51
- package/src/lib/i18n/translations.test.ts +0 -39
- package/src/lib/layout-constants.ts +0 -12
- package/src/lib/margin-layout.bench.ts +0 -28
- package/src/lib/scroll.test.ts +0 -118
- package/src/lib/scroll.ts +0 -47
- package/src/lib/shortcut-registry.test.ts +0 -173
- package/src/lib/shortcut-registry.ts +0 -209
- package/src/lib/utils.test.ts +0 -110
- package/src/store/index.test.ts +0 -242
- package/src/types/index.ts +0 -127
package/src/App.tsx
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Toaster } from "sonner";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { Toaster, toast } from "sonner";
|
|
3
3
|
import { CommentInput } from "./components/comments/CommentInput";
|
|
4
|
-
import { CommentMinimap } from "./components/comments/CommentMinimap";
|
|
5
4
|
import { CommentNav } from "./components/comments/CommentNav";
|
|
6
|
-
import { DocumentViewer } from "./components/DocumentViewer";
|
|
7
|
-
import { FloatingTOC } from "./components/FloatingTOC";
|
|
5
|
+
import { DocumentViewer } from "./components/DocumentViewer/DocumentViewer";
|
|
8
6
|
import { Header } from "./components/Header";
|
|
9
7
|
import { MarginNotes } from "./components/MarginNotes";
|
|
10
8
|
import { ReanchorConfirm } from "./components/ReanchorConfirm";
|
|
11
9
|
import { TabBar } from "./components/TabBar";
|
|
12
10
|
import { TableOfContents } from "./components/TableOfContents";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
import {
|
|
12
|
+
CommentProvider,
|
|
13
|
+
useCommentActions,
|
|
14
|
+
useCommentData,
|
|
15
|
+
} from "./contexts/CommentContext";
|
|
16
16
|
import { useLocale } from "./contexts/LocaleContext";
|
|
17
|
-
import {
|
|
17
|
+
import { PositionsProvider, usePositions } from "./contexts/PositionsContext";
|
|
18
|
+
import { SettingsProvider } from "./contexts/SettingsContext";
|
|
18
19
|
import { useDocument } from "./hooks/useDocument";
|
|
19
20
|
import { useHeadings } from "./hooks/useHeadings";
|
|
20
|
-
import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
|
|
21
|
-
import { useScrollMetrics } from "./hooks/useScrollMetrics";
|
|
22
21
|
import { useScrollSpy } from "./hooks/useScrollSpy";
|
|
23
22
|
import { useTextSelection } from "./hooks/useTextSelection";
|
|
24
|
-
import {
|
|
25
|
-
import { ShortcutActions } from "./lib/shortcut-registry";
|
|
23
|
+
import { exportCommentsAsJson, generatePrompt } from "./lib/export";
|
|
26
24
|
import { cn } from "./lib/utils";
|
|
27
25
|
import { appStore, useAppStore } from "./store";
|
|
28
26
|
|
|
@@ -33,103 +31,58 @@ const TOASTER_OPTIONS = {
|
|
|
33
31
|
classNames: {
|
|
34
32
|
toast: cn(
|
|
35
33
|
"backdrop-blur-sm bg-white/90 dark:bg-zinc-900/90 border border-zinc-100 dark:border-zinc-800 px-3 py-2 shadow-sm rounded-md",
|
|
36
|
-
|
|
34
|
+
"text-xs text-zinc-500 dark:text-zinc-400",
|
|
37
35
|
),
|
|
38
36
|
},
|
|
39
37
|
};
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
interface AppContentProps {
|
|
40
|
+
document: NonNullable<ReturnType<typeof useDocument>["document"]>;
|
|
41
|
+
reload: ReturnType<typeof useDocument>["reload"];
|
|
42
|
+
isActive: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function AppContent({ document, reload, isActive }: AppContentProps) {
|
|
42
46
|
const { t } = useLocale();
|
|
43
|
-
const {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
addComment,
|
|
47
|
-
reanchorComment,
|
|
48
|
-
reanchorTarget,
|
|
49
|
-
cancelReanchor,
|
|
50
|
-
hoveredCommentId,
|
|
51
|
-
setHoveredCommentId,
|
|
52
|
-
navigatePrevious,
|
|
53
|
-
navigateNext,
|
|
54
|
-
} = use(CommentContext)!;
|
|
47
|
+
const { comments, sortedComments, reanchorTarget } = useCommentData();
|
|
48
|
+
const { addComment, reanchorComment, cancelReanchor, setHoveredCommentId } =
|
|
49
|
+
useCommentActions();
|
|
55
50
|
|
|
56
|
-
const {
|
|
51
|
+
const { selection, pendingSelectionTop, onTextSelect, clearSelection } =
|
|
52
|
+
useTextSelection();
|
|
57
53
|
|
|
58
|
-
const
|
|
59
|
-
selection,
|
|
60
|
-
highlightPositions,
|
|
61
|
-
documentPositions,
|
|
62
|
-
pendingSelectionTop,
|
|
63
|
-
onTextSelect,
|
|
64
|
-
onPositionsChange,
|
|
65
|
-
clearSelection,
|
|
66
|
-
} = useTextSelection();
|
|
67
|
-
|
|
68
|
-
const {
|
|
69
|
-
copyAll,
|
|
70
|
-
copyAllRaw,
|
|
71
|
-
exportJson,
|
|
72
|
-
copySelectionRaw,
|
|
73
|
-
copySelectionForLLM,
|
|
74
|
-
} = useClipboard({
|
|
75
|
-
comments,
|
|
76
|
-
document: document ?? undefined,
|
|
77
|
-
selection: selection ?? undefined,
|
|
78
|
-
clearSelection,
|
|
79
|
-
t,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const { shortcuts, isFullscreen } = use(LayoutContext)!;
|
|
83
|
-
|
|
84
|
-
useKeyboardShortcuts(shortcuts, {
|
|
85
|
-
[ShortcutActions.COPY_ALL]: copyAll,
|
|
86
|
-
[ShortcutActions.COPY_ALL_RAW]: copyAllRaw,
|
|
87
|
-
[ShortcutActions.NAVIGATE_NEXT]: navigateNext,
|
|
88
|
-
[ShortcutActions.NAVIGATE_PREVIOUS]: navigatePrevious,
|
|
89
|
-
[ShortcutActions.COPY_SELECTION_RAW]: copySelectionRaw,
|
|
90
|
-
[ShortcutActions.COPY_SELECTION_LLM]: copySelectionForLLM,
|
|
91
|
-
[ShortcutActions.CLEAR_SELECTION]: clearSelection,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const scrollMetrics = useScrollMetrics();
|
|
95
|
-
|
|
96
|
-
const headings = useHeadings(
|
|
97
|
-
document?.content ?? null,
|
|
98
|
-
document?.type ?? null,
|
|
99
|
-
);
|
|
100
|
-
const activeHeadingId = useScrollSpy(headings.map((h) => h.id));
|
|
101
|
-
|
|
102
|
-
const scrollToHeading = useCallback(
|
|
103
|
-
(id: string) => {
|
|
104
|
-
let elementRect: DOMRect | undefined;
|
|
105
|
-
let iframeTopOffset: number | undefined;
|
|
106
|
-
|
|
107
|
-
if (document?.type === "html") {
|
|
108
|
-
const iframe = window.document.querySelector("iframe");
|
|
109
|
-
const el = iframe?.contentDocument?.getElementById(id);
|
|
110
|
-
if (!el || !iframe) return;
|
|
111
|
-
elementRect = el.getBoundingClientRect();
|
|
112
|
-
iframeTopOffset = iframe.getBoundingClientRect().top;
|
|
113
|
-
} else {
|
|
114
|
-
elementRect = window.document
|
|
115
|
-
.getElementById(id)
|
|
116
|
-
?.getBoundingClientRect();
|
|
117
|
-
}
|
|
118
|
-
if (!elementRect) return;
|
|
54
|
+
const pos = usePositions();
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
pos.setIds(sortedComments.map((c) => c.id));
|
|
58
|
+
}, [pos, sortedComments]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
pos.setPending(selection ? pendingSelectionTop : undefined);
|
|
61
|
+
}, [pos, selection, pendingSelectionTop]);
|
|
62
|
+
|
|
63
|
+
const copyAll = useCallback(() => {
|
|
64
|
+
if (!document) return;
|
|
65
|
+
navigator.clipboard.writeText(generatePrompt(comments, document.fileName));
|
|
66
|
+
toast.success(t("toast.copiedAllComments"));
|
|
67
|
+
}, [comments, document, t]);
|
|
68
|
+
|
|
69
|
+
const exportJson = useCallback(() => {
|
|
70
|
+
if (!document) return;
|
|
71
|
+
exportCommentsAsJson(comments, document);
|
|
72
|
+
}, [comments, document]);
|
|
73
|
+
|
|
74
|
+
const headings = useHeadings(document?.content ?? null);
|
|
75
|
+
const headingIds = useMemo(() => headings.map((h) => h.id), [headings]);
|
|
76
|
+
const activeHeadingId = useScrollSpy(headingIds, isActive);
|
|
77
|
+
|
|
78
|
+
const scrollToHeading = useCallback((id: string) => {
|
|
79
|
+
const rect = window.document.getElementById(id)?.getBoundingClientRect();
|
|
80
|
+
if (!rect) return;
|
|
81
|
+
|
|
82
|
+
const elementTop = window.scrollY + rect.top;
|
|
83
|
+
const scrollTarget = Math.max(0, elementTop - window.innerHeight * 0.25);
|
|
84
|
+
window.scrollTo({ top: scrollTarget, behavior: "smooth" });
|
|
85
|
+
}, []);
|
|
133
86
|
|
|
134
87
|
const handleHighlightClick = useCallback((commentId: string) => {
|
|
135
88
|
const marginNote = window.document.querySelector(
|
|
@@ -140,35 +93,31 @@ function AppContent() {
|
|
|
140
93
|
}
|
|
141
94
|
}, []);
|
|
142
95
|
|
|
143
|
-
|
|
144
|
-
const eventSource = new EventSource("/api/heartbeat");
|
|
145
|
-
return () => eventSource.close();
|
|
146
|
-
}, []);
|
|
147
|
-
|
|
148
|
-
// Scroll save/restore for tab switching
|
|
96
|
+
// Scroll save/restore for tab switching (visibility-based, not mount-based)
|
|
149
97
|
const setScrollY = useAppStore((s) => s.setScrollY);
|
|
150
98
|
const savedScrollY = useAppStore(
|
|
151
|
-
(s) => s.
|
|
99
|
+
(s) => s.documents.get(document.filePath)?.scrollY ?? 0,
|
|
152
100
|
);
|
|
153
|
-
const
|
|
101
|
+
const prevActiveRef = useRef(isActive);
|
|
154
102
|
|
|
155
|
-
// Save scroll position on unmount
|
|
156
103
|
useEffect(() => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
}, [setScrollY]);
|
|
104
|
+
const wasActive = prevActiveRef.current;
|
|
105
|
+
prevActiveRef.current = isActive;
|
|
161
106
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
107
|
+
if (wasActive && !isActive) {
|
|
108
|
+
// Tab becoming hidden — save scroll position
|
|
109
|
+
setScrollY(window.scrollY, document.filePath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!wasActive && isActive) {
|
|
113
|
+
// Tab becoming visible — restore scroll after layout recalc
|
|
167
114
|
requestAnimationFrame(() => {
|
|
168
|
-
|
|
115
|
+
requestAnimationFrame(() => {
|
|
116
|
+
window.scrollTo(0, savedScrollY);
|
|
117
|
+
});
|
|
169
118
|
});
|
|
170
|
-
}
|
|
171
|
-
}, [savedScrollY]);
|
|
119
|
+
}
|
|
120
|
+
}, [isActive, savedScrollY, setScrollY, document.filePath]);
|
|
172
121
|
|
|
173
122
|
const handleAddComment = useCallback(
|
|
174
123
|
(commentText: string) => {
|
|
@@ -215,23 +164,15 @@ function AppContent() {
|
|
|
215
164
|
|
|
216
165
|
return (
|
|
217
166
|
<div className="min-h-screen bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 flex flex-col">
|
|
218
|
-
<Toaster
|
|
219
|
-
position="bottom-right"
|
|
220
|
-
icons={TOASTER_ICONS}
|
|
221
|
-
toastOptions={TOASTER_OPTIONS}
|
|
222
|
-
/>
|
|
223
167
|
<Header
|
|
224
168
|
fileName={document.fileName}
|
|
225
169
|
onCopyAll={copyAll}
|
|
226
|
-
onCopyAllRaw={copyAllRaw}
|
|
227
170
|
onExportJson={exportJson}
|
|
228
171
|
onReload={reload}
|
|
229
172
|
/>
|
|
230
173
|
|
|
231
|
-
<div
|
|
232
|
-
|
|
233
|
-
>
|
|
234
|
-
{!isFullscreen && headings.length > 0 && (
|
|
174
|
+
<div className="flex-1 flex gap-4 w-full max-w-7xl mx-auto">
|
|
175
|
+
{headings.length > 0 && (
|
|
235
176
|
<aside className="w-48 flex-shrink-0 py-6 pl-6 hidden xl:block">
|
|
236
177
|
<div className="sticky top-64 max-h-[calc(100vh-17rem)] overflow-y-auto">
|
|
237
178
|
<TableOfContents
|
|
@@ -242,23 +183,14 @@ function AppContent() {
|
|
|
242
183
|
</div>
|
|
243
184
|
</aside>
|
|
244
185
|
)}
|
|
245
|
-
{isFullscreen && (
|
|
246
|
-
<FloatingTOC
|
|
247
|
-
headings={headings}
|
|
248
|
-
activeId={activeHeadingId}
|
|
249
|
-
onHeadingClick={scrollToHeading}
|
|
250
|
-
/>
|
|
251
|
-
)}
|
|
252
186
|
|
|
253
187
|
<div className="flex-1 px-6 py-6">
|
|
254
188
|
<DocumentViewer
|
|
255
189
|
content={document.content}
|
|
256
|
-
type={document.type}
|
|
257
190
|
comments={comments}
|
|
258
191
|
headings={headings}
|
|
259
|
-
|
|
192
|
+
isActive={isActive}
|
|
260
193
|
onTextSelect={onTextSelect}
|
|
261
|
-
onHighlightPositionsChange={onPositionsChange}
|
|
262
194
|
onHighlightHover={setHoveredCommentId}
|
|
263
195
|
onHighlightClick={handleHighlightClick}
|
|
264
196
|
/>
|
|
@@ -282,27 +214,15 @@ function AppContent() {
|
|
|
282
214
|
selectedText={selection.text}
|
|
283
215
|
onSubmit={handleAddComment}
|
|
284
216
|
onCancel={clearSelection}
|
|
285
|
-
onCopyRaw={copySelectionRaw}
|
|
286
|
-
onCopyForLLM={copySelectionForLLM}
|
|
287
217
|
/>
|
|
288
218
|
)}
|
|
289
219
|
</div>
|
|
290
220
|
)}
|
|
291
221
|
|
|
292
|
-
<MarginNotes
|
|
293
|
-
sortedComments={sortedComments}
|
|
294
|
-
highlightPositions={highlightPositions}
|
|
295
|
-
pendingSelectionTop={selection ? pendingSelectionTop : undefined}
|
|
296
|
-
/>
|
|
222
|
+
<MarginNotes sortedComments={sortedComments} />
|
|
297
223
|
</div>
|
|
298
224
|
</div>
|
|
299
225
|
|
|
300
|
-
<CommentMinimap
|
|
301
|
-
documentPositions={documentPositions}
|
|
302
|
-
documentHeight={scrollMetrics.documentHeight}
|
|
303
|
-
viewportHeight={scrollMetrics.viewportHeight}
|
|
304
|
-
/>
|
|
305
|
-
|
|
306
226
|
<CommentNav />
|
|
307
227
|
|
|
308
228
|
<footer className="py-4 text-center text-sm text-zinc-400 dark:text-zinc-500">
|
|
@@ -338,11 +258,18 @@ function useTabKeyboardShortcuts() {
|
|
|
338
258
|
|
|
339
259
|
function App() {
|
|
340
260
|
const { t } = useLocale();
|
|
341
|
-
const {
|
|
261
|
+
const { error, isInitialized, reload } = useDocument();
|
|
342
262
|
const documentOrder = useAppStore((s) => s.documentOrder);
|
|
263
|
+
const activeDocumentPath = useAppStore((s) => s.activeDocumentPath);
|
|
264
|
+
const documents = useAppStore((s) => s.documents);
|
|
343
265
|
|
|
344
266
|
useTabKeyboardShortcuts();
|
|
345
267
|
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
const eventSource = new EventSource("/api/heartbeat");
|
|
270
|
+
return () => eventSource.close();
|
|
271
|
+
}, []);
|
|
272
|
+
|
|
346
273
|
if (error) {
|
|
347
274
|
return (
|
|
348
275
|
<div className="min-h-screen bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 flex items-center justify-center">
|
|
@@ -382,30 +309,58 @@ function App() {
|
|
|
382
309
|
);
|
|
383
310
|
}
|
|
384
311
|
|
|
385
|
-
if (!document) {
|
|
386
|
-
return (
|
|
387
|
-
<div className="min-h-screen bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 flex items-center justify-center">
|
|
388
|
-
<div className="text-zinc-500 dark:text-zinc-400">
|
|
389
|
-
{t("app.loading")}
|
|
390
|
-
</div>
|
|
391
|
-
</div>
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
312
|
return (
|
|
396
313
|
<>
|
|
397
314
|
<TabBar />
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
315
|
+
<Toaster
|
|
316
|
+
position="bottom-right"
|
|
317
|
+
icons={TOASTER_ICONS}
|
|
318
|
+
toastOptions={TOASTER_OPTIONS}
|
|
319
|
+
/>
|
|
320
|
+
<SettingsProvider>
|
|
321
|
+
{documentOrder.map((filePath) => {
|
|
322
|
+
const docState = documents.get(filePath);
|
|
323
|
+
const isActive = filePath === activeDocumentPath;
|
|
324
|
+
const hasContent = !!docState?.document.content;
|
|
325
|
+
|
|
326
|
+
// Don't mount inactive tabs that haven't loaded content yet
|
|
327
|
+
if (!hasContent && !isActive) return null;
|
|
328
|
+
|
|
329
|
+
// Active tab without content — show loading placeholder
|
|
330
|
+
if (!hasContent) {
|
|
331
|
+
return (
|
|
332
|
+
<div
|
|
333
|
+
key={filePath}
|
|
334
|
+
className="min-h-screen bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 flex items-center justify-center"
|
|
335
|
+
>
|
|
336
|
+
<div className="text-zinc-500 dark:text-zinc-400">
|
|
337
|
+
{t("app.loading")}
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<div
|
|
345
|
+
key={filePath}
|
|
346
|
+
style={isActive ? undefined : { display: "none" }}
|
|
347
|
+
>
|
|
348
|
+
<PositionsProvider>
|
|
349
|
+
<CommentProvider
|
|
350
|
+
filePath={filePath}
|
|
351
|
+
clean={docState.document.clean}
|
|
352
|
+
>
|
|
353
|
+
<AppContent
|
|
354
|
+
document={docState.document}
|
|
355
|
+
reload={reload}
|
|
356
|
+
isActive={isActive}
|
|
357
|
+
/>
|
|
358
|
+
</CommentProvider>
|
|
359
|
+
</PositionsProvider>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
})}
|
|
363
|
+
</SettingsProvider>
|
|
409
364
|
</>
|
|
410
365
|
);
|
|
411
366
|
}
|