@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
|
@@ -1,50 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { Comment } from "../types";
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import type { Comment } from "../schema";
|
|
4
3
|
import { MarginNote } from "./MarginNote";
|
|
5
4
|
|
|
6
5
|
interface MarginNotesProps {
|
|
7
|
-
/** Comments pre-sorted by startOffset */
|
|
8
6
|
sortedComments: Comment[];
|
|
9
|
-
highlightPositions: Record<string, number>;
|
|
10
|
-
pendingSelectionTop?: number;
|
|
11
7
|
}
|
|
12
8
|
|
|
13
|
-
export function MarginNotes({
|
|
9
|
+
export const MarginNotes = memo(function MarginNotes({
|
|
14
10
|
sortedComments,
|
|
15
|
-
highlightPositions,
|
|
16
|
-
pendingSelectionTop,
|
|
17
11
|
}: MarginNotesProps) {
|
|
18
|
-
// Calculate resolved positions (avoiding overlaps with input and other notes)
|
|
19
|
-
const resolvedPositions = useMemo(
|
|
20
|
-
() =>
|
|
21
|
-
resolveMarginNotePositions(
|
|
22
|
-
sortedComments.map((c) => c.id),
|
|
23
|
-
highlightPositions,
|
|
24
|
-
pendingSelectionTop,
|
|
25
|
-
),
|
|
26
|
-
[sortedComments, highlightPositions, pendingSelectionTop],
|
|
27
|
-
);
|
|
28
|
-
|
|
29
12
|
if (sortedComments.length === 0) {
|
|
30
13
|
return null;
|
|
31
14
|
}
|
|
32
15
|
|
|
33
16
|
return (
|
|
34
17
|
<div className="relative w-64">
|
|
35
|
-
{sortedComments.map((comment, index) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<MarginNote
|
|
41
|
-
key={comment.id}
|
|
42
|
-
comment={comment}
|
|
43
|
-
top={top}
|
|
44
|
-
commentIndex={index}
|
|
45
|
-
/>
|
|
46
|
-
);
|
|
47
|
-
})}
|
|
18
|
+
{sortedComments.map((comment, index) => (
|
|
19
|
+
<MarginNote key={comment.id} comment={comment} commentIndex={index} />
|
|
20
|
+
))}
|
|
48
21
|
</div>
|
|
49
22
|
);
|
|
50
|
-
}
|
|
23
|
+
});
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Dialog,
|
|
9
9
|
DialogBody,
|
|
10
10
|
DialogContent,
|
|
11
|
-
DialogDescription,
|
|
12
11
|
DialogHeader,
|
|
13
12
|
DialogTitle,
|
|
14
13
|
} from "./ui/Dialog";
|
|
@@ -88,7 +87,7 @@ export function RawModal({ isOpen, onClose }: RawModalProps) {
|
|
|
88
87
|
if (!open) onClose();
|
|
89
88
|
}}
|
|
90
89
|
>
|
|
91
|
-
<DialogContent className="max-w-2xl max-h-[80vh]">
|
|
90
|
+
<DialogContent className="max-w-2xl max-h-[80vh]" onClose={onClose}>
|
|
92
91
|
<DialogHeader>
|
|
93
92
|
<DialogTitle>{t("rawModal.title")}</DialogTitle>
|
|
94
93
|
{state.status === "success" && (
|
|
@@ -105,9 +104,9 @@ export function RawModal({ isOpen, onClose }: RawModalProps) {
|
|
|
105
104
|
</DialogHeader>
|
|
106
105
|
|
|
107
106
|
{(state.status === "success" || state.status === "empty") && (
|
|
108
|
-
<
|
|
107
|
+
<div className="px-4 py-2 border-b border-zinc-50 dark:border-zinc-800 text-xs text-zinc-400 dark:text-zinc-500 font-mono truncate">
|
|
109
108
|
{state.path}
|
|
110
|
-
</
|
|
109
|
+
</div>
|
|
111
110
|
)}
|
|
112
111
|
|
|
113
112
|
<DialogBody>
|
|
@@ -130,10 +129,12 @@ export function RawModal({ isOpen, onClose }: RawModalProps) {
|
|
|
130
129
|
)}
|
|
131
130
|
|
|
132
131
|
{state.status === "success" && (
|
|
133
|
-
<Text
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
<Text
|
|
133
|
+
variant="body"
|
|
134
|
+
as="pre"
|
|
135
|
+
className="text-xs font-mono whitespace-pre-wrap break-words leading-relaxed"
|
|
136
|
+
>
|
|
137
|
+
{state.content}
|
|
137
138
|
</Text>
|
|
138
139
|
)}
|
|
139
140
|
</DialogBody>
|
|
@@ -20,8 +20,8 @@ export function ReanchorConfirm({
|
|
|
20
20
|
<Text variant="body" className="mb-2">
|
|
21
21
|
{t("reanchor.question")}
|
|
22
22
|
</Text>
|
|
23
|
-
<Text variant="caption"
|
|
24
|
-
|
|
23
|
+
<Text variant="caption" className="italic line-clamp-2 mb-2">
|
|
24
|
+
"{selectionText}"
|
|
25
25
|
</Text>
|
|
26
26
|
<div className="flex gap-3 text-sm">
|
|
27
27
|
<Button variant="link" size="sm" onClick={onConfirm}>
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import { Check, ChevronDown
|
|
2
|
-
import { useLayoutContext } from "../contexts/LayoutContext";
|
|
1
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
3
2
|
import { useLocale } from "../contexts/LocaleContext";
|
|
3
|
+
import { useSettings } from "../contexts/SettingsContext";
|
|
4
4
|
import { type Locale, Locales } from "../lib/i18n";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
import {
|
|
7
|
-
type EditorScheme,
|
|
8
|
-
EditorSchemes,
|
|
9
7
|
FontFamilies,
|
|
10
8
|
type FontFamily,
|
|
11
9
|
type ThemeMode,
|
|
12
10
|
ThemeModes,
|
|
13
|
-
} from "../
|
|
14
|
-
import { ShortcutList } from "./ShortcutList";
|
|
11
|
+
} from "../schema";
|
|
15
12
|
import {
|
|
16
13
|
Dialog,
|
|
17
14
|
DialogBody,
|
|
@@ -74,8 +71,6 @@ function ThemePreviewBadge() {
|
|
|
74
71
|
);
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
/* ─── Font selector ──────────────────────────────────────────── */
|
|
78
|
-
|
|
79
74
|
function FontPreviewBadge({ fontClass }: { fontClass: string }) {
|
|
80
75
|
return (
|
|
81
76
|
<span
|
|
@@ -89,8 +84,6 @@ function FontPreviewBadge({ fontClass }: { fontClass: string }) {
|
|
|
89
84
|
);
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
/* ─── Shared trigger style ───────────────────────────────────── */
|
|
93
|
-
|
|
94
87
|
const triggerClassName = cn(
|
|
95
88
|
"inline-flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-sm",
|
|
96
89
|
"border border-zinc-200 dark:border-zinc-700",
|
|
@@ -100,21 +93,8 @@ const triggerClassName = cn(
|
|
|
100
93
|
"transition-colors cursor-pointer",
|
|
101
94
|
);
|
|
102
95
|
|
|
103
|
-
/* ─── Settings Modal ─────────────────────────────────────────── */
|
|
104
|
-
|
|
105
96
|
export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
106
|
-
const {
|
|
107
|
-
fontFamily,
|
|
108
|
-
setFontFamily,
|
|
109
|
-
editorScheme,
|
|
110
|
-
setEditorScheme,
|
|
111
|
-
themeMode,
|
|
112
|
-
setThemeMode,
|
|
113
|
-
shortcuts,
|
|
114
|
-
updateBinding,
|
|
115
|
-
toggleShortcutEnabled,
|
|
116
|
-
resetShortcutsToDefaults,
|
|
117
|
-
} = useLayoutContext();
|
|
97
|
+
const { fontFamily, setFontFamily, themeMode, setThemeMode } = useSettings();
|
|
118
98
|
const { locale, setLocale, t } = useLocale();
|
|
119
99
|
|
|
120
100
|
const themeOptions = [
|
|
@@ -136,22 +116,10 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
|
136
116
|
},
|
|
137
117
|
];
|
|
138
118
|
|
|
139
|
-
const editorOptions = [
|
|
140
|
-
{ value: EditorSchemes.NONE, label: t("settings.editor.none") },
|
|
141
|
-
{ value: EditorSchemes.VSCODE, label: t("settings.editor.vscode") },
|
|
142
|
-
{
|
|
143
|
-
value: EditorSchemes.VSCODE_INSIDERS,
|
|
144
|
-
label: t("settings.editor.vscodeInsiders"),
|
|
145
|
-
},
|
|
146
|
-
{ value: EditorSchemes.CURSOR, label: t("settings.editor.cursor") },
|
|
147
|
-
];
|
|
148
|
-
|
|
149
119
|
const activeTheme =
|
|
150
120
|
themeOptions.find((o) => o.value === themeMode) ?? themeOptions[0];
|
|
151
121
|
const activeFont =
|
|
152
122
|
fontOptions.find((o) => o.value === fontFamily) ?? fontOptions[0];
|
|
153
|
-
const activeEditor =
|
|
154
|
-
editorOptions.find((o) => o.value === editorScheme) ?? editorOptions[0];
|
|
155
123
|
const activeLocale =
|
|
156
124
|
LOCALE_OPTIONS.find((o) => o.value === locale) ?? LOCALE_OPTIONS[0];
|
|
157
125
|
|
|
@@ -162,15 +130,15 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
|
162
130
|
if (!open) onClose();
|
|
163
131
|
}}
|
|
164
132
|
>
|
|
165
|
-
<DialogContent className="max-w-md">
|
|
133
|
+
<DialogContent className="max-w-md" onClose={onClose}>
|
|
166
134
|
<DialogHeader>
|
|
167
135
|
<DialogTitle>{t("settings.title")}</DialogTitle>
|
|
168
136
|
</DialogHeader>
|
|
169
137
|
|
|
170
138
|
<DialogBody className="space-y-4">
|
|
171
139
|
<div>
|
|
172
|
-
<Text variant="overline"
|
|
173
|
-
|
|
140
|
+
<Text variant="overline" as="h3" className="mb-3">
|
|
141
|
+
{t("settings.theme")}
|
|
174
142
|
</Text>
|
|
175
143
|
<DropdownMenu>
|
|
176
144
|
<DropdownMenuTrigger asChild>
|
|
@@ -201,8 +169,8 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
|
201
169
|
</div>
|
|
202
170
|
|
|
203
171
|
<div>
|
|
204
|
-
<Text variant="overline"
|
|
205
|
-
|
|
172
|
+
<Text variant="overline" as="h3" className="mb-3">
|
|
173
|
+
{t("settings.font")}
|
|
206
174
|
</Text>
|
|
207
175
|
<DropdownMenu>
|
|
208
176
|
<DropdownMenuTrigger asChild>
|
|
@@ -231,8 +199,8 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
|
231
199
|
</div>
|
|
232
200
|
|
|
233
201
|
<div>
|
|
234
|
-
<Text variant="overline"
|
|
235
|
-
|
|
202
|
+
<Text variant="overline" as="h3" className="mb-3">
|
|
203
|
+
{t("settings.language")}
|
|
236
204
|
</Text>
|
|
237
205
|
<DropdownMenu>
|
|
238
206
|
<DropdownMenuTrigger asChild>
|
|
@@ -257,52 +225,6 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
|
|
257
225
|
</DropdownMenuContent>
|
|
258
226
|
</DropdownMenu>
|
|
259
227
|
</div>
|
|
260
|
-
|
|
261
|
-
<div>
|
|
262
|
-
<Text variant="overline" asChild>
|
|
263
|
-
<h3 className="mb-3">{t("settings.editor")}</h3>
|
|
264
|
-
</Text>
|
|
265
|
-
<DropdownMenu>
|
|
266
|
-
<DropdownMenuTrigger asChild>
|
|
267
|
-
<button type="button" className={triggerClassName}>
|
|
268
|
-
<ExternalLink className="size-3 text-zinc-400 dark:text-zinc-500" />
|
|
269
|
-
<span>{activeEditor.label}</span>
|
|
270
|
-
<ChevronDown className="size-3 text-zinc-400 dark:text-zinc-500" />
|
|
271
|
-
</button>
|
|
272
|
-
</DropdownMenuTrigger>
|
|
273
|
-
<DropdownMenuContent align="start" className="min-w-[160px]">
|
|
274
|
-
{editorOptions.map((option) => (
|
|
275
|
-
<DropdownMenuItem
|
|
276
|
-
key={option.value}
|
|
277
|
-
onSelect={() =>
|
|
278
|
-
setEditorScheme(option.value as EditorScheme)
|
|
279
|
-
}
|
|
280
|
-
className="flex items-center gap-2"
|
|
281
|
-
>
|
|
282
|
-
<span className="flex-1">{option.label}</span>
|
|
283
|
-
{editorScheme === option.value && (
|
|
284
|
-
<Check className="size-3.5 text-zinc-500 dark:text-zinc-400" />
|
|
285
|
-
)}
|
|
286
|
-
</DropdownMenuItem>
|
|
287
|
-
))}
|
|
288
|
-
</DropdownMenuContent>
|
|
289
|
-
</DropdownMenu>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
<div>
|
|
293
|
-
<Text variant="overline" asChild>
|
|
294
|
-
<h3 className="mb-1">{t("settings.keyboardShortcuts")}</h3>
|
|
295
|
-
</Text>
|
|
296
|
-
<p className="text-xs text-zinc-400 dark:text-zinc-500 mb-3">
|
|
297
|
-
{t("settings.clickToRebind")}
|
|
298
|
-
</p>
|
|
299
|
-
<ShortcutList
|
|
300
|
-
shortcuts={shortcuts}
|
|
301
|
-
onUpdateBinding={updateBinding}
|
|
302
|
-
onToggleEnabled={toggleShortcutEnabled}
|
|
303
|
-
onResetToDefaults={resetShortcutsToDefaults}
|
|
304
|
-
/>
|
|
305
|
-
</div>
|
|
306
228
|
</DialogBody>
|
|
307
229
|
</DialogContent>
|
|
308
230
|
</Dialog>
|
|
@@ -13,7 +13,7 @@ export function TabBar() {
|
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<div
|
|
16
|
-
className="flex border-b border-zinc-200 bg-zinc-50 px-2 overflow-x-auto"
|
|
16
|
+
className="flex border-b border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-2 overflow-x-auto"
|
|
17
17
|
role="tablist"
|
|
18
18
|
>
|
|
19
19
|
{documentOrder.map((filePath) => {
|
|
@@ -30,8 +30,8 @@ export function TabBar() {
|
|
|
30
30
|
className={cn(
|
|
31
31
|
"flex items-center gap-1.5 px-3 py-1.5 text-sm border-b-2 whitespace-nowrap cursor-pointer select-none",
|
|
32
32
|
isActive
|
|
33
|
-
? "border-zinc-900 text-zinc-900"
|
|
34
|
-
: "border-transparent text-zinc-500 hover:text-zinc-700 hover:bg-zinc-100",
|
|
33
|
+
? "border-zinc-900 dark:border-zinc-100 text-zinc-900 dark:text-zinc-100"
|
|
34
|
+
: "border-transparent text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
35
35
|
)}
|
|
36
36
|
onClick={() => setActiveDocument(filePath)}
|
|
37
37
|
onKeyDown={(e) => {
|
|
@@ -44,7 +44,7 @@ export function TabBar() {
|
|
|
44
44
|
<span>{docState.document.fileName}</span>
|
|
45
45
|
<button
|
|
46
46
|
type="button"
|
|
47
|
-
className="ml-1 rounded p-0.5 hover:bg-zinc-200"
|
|
47
|
+
className="ml-1 rounded p-0.5 hover:bg-zinc-200 dark:hover:bg-zinc-700"
|
|
48
48
|
onClick={(e) => {
|
|
49
49
|
e.stopPropagation();
|
|
50
50
|
closeDocument(filePath);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { use, useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { SettingsContext } from "../contexts/SettingsContext";
|
|
3
3
|
import type { Heading } from "../hooks/useHeadings";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
|
-
import { FontFamilies } from "../
|
|
5
|
+
import { FontFamilies } from "../schema";
|
|
6
6
|
|
|
7
7
|
interface TableOfContentsProps {
|
|
8
8
|
headings: Heading[];
|
|
@@ -15,9 +15,9 @@ export function TableOfContents({
|
|
|
15
15
|
activeId,
|
|
16
16
|
onHeadingClick,
|
|
17
17
|
}: TableOfContentsProps) {
|
|
18
|
-
const
|
|
19
|
-
const fontClass =
|
|
20
|
-
?
|
|
18
|
+
const settings = use(SettingsContext);
|
|
19
|
+
const fontClass = settings
|
|
20
|
+
? settings.fontFamily === FontFamilies.SANS_SERIF
|
|
21
21
|
? "font-sans"
|
|
22
22
|
: "font-serif"
|
|
23
23
|
: undefined;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { BotMessageSquare, Copy } from "lucide-react";
|
|
2
1
|
import { use, useEffect, useRef, useState } from "react";
|
|
3
|
-
import { LayoutContext } from "../../contexts/LayoutContext";
|
|
4
2
|
import { useLocale } from "../../contexts/LocaleContext";
|
|
3
|
+
import { SettingsContext } from "../../contexts/SettingsContext";
|
|
5
4
|
import { cn } from "../../lib/utils";
|
|
6
|
-
import { FontFamilies } from "../../
|
|
5
|
+
import { FontFamilies } from "../../schema";
|
|
7
6
|
import { Button } from "../ui/Button";
|
|
8
7
|
import { Text } from "../ui/Text";
|
|
9
8
|
|
|
@@ -11,21 +10,17 @@ interface CommentInputProps {
|
|
|
11
10
|
selectedText: string | null;
|
|
12
11
|
onSubmit: (commentText: string) => void;
|
|
13
12
|
onCancel: () => void;
|
|
14
|
-
onCopyRaw: () => void;
|
|
15
|
-
onCopyForLLM: () => void;
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
export function CommentInput({
|
|
19
16
|
selectedText,
|
|
20
17
|
onSubmit,
|
|
21
18
|
onCancel,
|
|
22
|
-
onCopyRaw,
|
|
23
|
-
onCopyForLLM,
|
|
24
19
|
}: CommentInputProps) {
|
|
25
20
|
const { t } = useLocale();
|
|
26
|
-
const
|
|
27
|
-
const fontClass =
|
|
28
|
-
?
|
|
21
|
+
const settings = use(SettingsContext);
|
|
22
|
+
const fontClass = settings
|
|
23
|
+
? settings.fontFamily === FontFamilies.SANS_SERIF
|
|
29
24
|
? "font-sans"
|
|
30
25
|
: "font-serif"
|
|
31
26
|
: undefined;
|
|
@@ -34,7 +29,6 @@ export function CommentInput({
|
|
|
34
29
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
35
30
|
|
|
36
31
|
useEffect(() => {
|
|
37
|
-
// Only auto-focus on devices with precise pointing (desktop)
|
|
38
32
|
if (textareaRef.current && window.matchMedia("(pointer: fine)").matches) {
|
|
39
33
|
textareaRef.current.focus();
|
|
40
34
|
}
|
|
@@ -64,8 +58,8 @@ export function CommentInput({
|
|
|
64
58
|
data-comment-input
|
|
65
59
|
className="border-t border-zinc-200 dark:border-zinc-700 pt-3 pb-2"
|
|
66
60
|
>
|
|
67
|
-
<Text variant="caption"
|
|
68
|
-
|
|
61
|
+
<Text variant="caption" as="div" className="italic mb-2 line-clamp-2">
|
|
62
|
+
"{selectedText}"
|
|
69
63
|
</Text>
|
|
70
64
|
<textarea
|
|
71
65
|
ref={textareaRef}
|
|
@@ -80,28 +74,6 @@ export function CommentInput({
|
|
|
80
74
|
onKeyDown={handleKeyDown}
|
|
81
75
|
/>
|
|
82
76
|
<div className="flex justify-end items-center gap-3 mt-2 text-sm">
|
|
83
|
-
<div className="flex gap-1">
|
|
84
|
-
<Button
|
|
85
|
-
variant="ghost"
|
|
86
|
-
size="icon"
|
|
87
|
-
className="size-7 text-zinc-300 dark:text-zinc-600 hover:text-zinc-500 dark:hover:text-zinc-400"
|
|
88
|
-
onClick={onCopyRaw}
|
|
89
|
-
title={t("comment.copyRawTitle")}
|
|
90
|
-
aria-label={t("comment.copyRawLabel")}
|
|
91
|
-
>
|
|
92
|
-
<Copy size={14} />
|
|
93
|
-
</Button>
|
|
94
|
-
<Button
|
|
95
|
-
variant="ghost"
|
|
96
|
-
size="icon"
|
|
97
|
-
className="size-7 text-zinc-300 dark:text-zinc-600 hover:text-zinc-500 dark:hover:text-zinc-400"
|
|
98
|
-
onClick={onCopyForLLM}
|
|
99
|
-
title={t("comment.copyLLMTitle")}
|
|
100
|
-
aria-label={t("comment.copyLLMLabel")}
|
|
101
|
-
>
|
|
102
|
-
<BotMessageSquare size={14} />
|
|
103
|
-
</Button>
|
|
104
|
-
</div>
|
|
105
77
|
<Button variant="ghost" size="sm" onClick={onCancel}>
|
|
106
78
|
{t("comment.cancel")}
|
|
107
79
|
</Button>
|
|
@@ -2,15 +2,13 @@ import { useState } from "react";
|
|
|
2
2
|
import { useCommentContext } from "../../contexts/CommentContext";
|
|
3
3
|
import { useLocale } from "../../contexts/LocaleContext";
|
|
4
4
|
import { cn } from "../../lib/utils";
|
|
5
|
-
import type { Comment } from "../../
|
|
5
|
+
import type { Comment } from "../../schema";
|
|
6
6
|
import { InlineEditor } from "../InlineEditor";
|
|
7
|
-
import { ActionBar } from "../ui/ActionBar";
|
|
8
7
|
import { ActionLink } from "../ui/ActionLink";
|
|
9
8
|
import { Text } from "../ui/Text";
|
|
10
9
|
|
|
11
10
|
interface CommentListItemProps {
|
|
12
11
|
comment: Comment;
|
|
13
|
-
/** Called after navigation actions (Go to, Re-anchor) to close parent dropdown */
|
|
14
12
|
onAction?: () => void;
|
|
15
13
|
}
|
|
16
14
|
|
|
@@ -42,12 +40,12 @@ export function CommentListItem({ comment, onAction }: CommentListItemProps) {
|
|
|
42
40
|
)}
|
|
43
41
|
>
|
|
44
42
|
<div className="flex items-center gap-1.5 mb-1">
|
|
45
|
-
<Text variant="caption"
|
|
46
|
-
|
|
43
|
+
<Text variant="caption" as="span" className="italic line-clamp-1">
|
|
44
|
+
"{comment.selectedText}"
|
|
47
45
|
</Text>
|
|
48
46
|
{isUnresolved && (
|
|
49
|
-
<Text variant="caption"
|
|
50
|
-
|
|
47
|
+
<Text variant="caption" as="span" className="shrink-0">
|
|
48
|
+
· {t("commentList.unresolved")}
|
|
51
49
|
</Text>
|
|
52
50
|
)}
|
|
53
51
|
</div>
|
|
@@ -63,11 +61,11 @@ export function CommentListItem({ comment, onAction }: CommentListItemProps) {
|
|
|
63
61
|
/>
|
|
64
62
|
) : (
|
|
65
63
|
<>
|
|
66
|
-
<Text variant="body"
|
|
67
|
-
|
|
64
|
+
<Text variant="body" className="line-clamp-2">
|
|
65
|
+
{comment.comment}
|
|
68
66
|
</Text>
|
|
69
67
|
|
|
70
|
-
<
|
|
68
|
+
<div className="flex items-center text-xs text-zinc-400 opacity-0 group-hover:opacity-100 transition-opacity gap-3 mt-1.5">
|
|
71
69
|
<ActionLink onClick={() => setIsEditing(true)}>
|
|
72
70
|
{t("commentList.edit")}
|
|
73
71
|
</ActionLink>
|
|
@@ -84,7 +82,7 @@ export function CommentListItem({ comment, onAction }: CommentListItemProps) {
|
|
|
84
82
|
{t("commentList.reanchor")}
|
|
85
83
|
</ActionLink>
|
|
86
84
|
)}
|
|
87
|
-
</
|
|
85
|
+
</div>
|
|
88
86
|
</>
|
|
89
87
|
)}
|
|
90
88
|
</div>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Copy, Trash2 } from "lucide-react";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import {
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
import {
|
|
5
|
+
useCommentActions,
|
|
6
|
+
useCommentData,
|
|
7
|
+
} from "../../contexts/CommentContext";
|
|
4
8
|
import { useLocale } from "../../contexts/LocaleContext";
|
|
9
|
+
import { generatePrompt } from "../../lib/export";
|
|
10
|
+
import { useAppStore } from "../../store";
|
|
5
11
|
import { Button } from "../ui/Button";
|
|
6
12
|
import { Text } from "../ui/Text";
|
|
7
13
|
import { CommentListItem } from "./CommentListItem";
|
|
@@ -12,7 +18,17 @@ interface CommentManagerProps {
|
|
|
12
18
|
|
|
13
19
|
export function CommentManager({ onClose }: CommentManagerProps) {
|
|
14
20
|
const { t } = useLocale();
|
|
15
|
-
const { comments
|
|
21
|
+
const { comments } = useCommentData();
|
|
22
|
+
const { deleteAll } = useCommentActions();
|
|
23
|
+
const fileName = useAppStore(
|
|
24
|
+
(s) => s.getActiveDocumentState()?.document.fileName ?? "",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const copyAll = useCallback(() => {
|
|
28
|
+
const text = generatePrompt(comments, fileName);
|
|
29
|
+
navigator.clipboard.writeText(text);
|
|
30
|
+
toast.success(t("toast.copiedAllComments"));
|
|
31
|
+
}, [comments, fileName, t]);
|
|
16
32
|
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
17
33
|
|
|
18
34
|
const unresolvedCount = comments.filter(
|
|
@@ -58,45 +74,45 @@ export function CommentManager({ onClose }: CommentManagerProps) {
|
|
|
58
74
|
</div>
|
|
59
75
|
</div>
|
|
60
76
|
) : (
|
|
61
|
-
<Text
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
<Text
|
|
78
|
+
variant="caption"
|
|
79
|
+
as="div"
|
|
80
|
+
className="flex items-center justify-between px-3 py-2 border-b border-zinc-100"
|
|
81
|
+
>
|
|
82
|
+
<span>
|
|
83
|
+
{resolvedCount}
|
|
84
|
+
{unresolvedCount > 0 && (
|
|
85
|
+
<span>
|
|
86
|
+
{" "}
|
|
87
|
+
· {unresolvedCount} {t("commentManager.unresolved")}
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
</span>
|
|
91
|
+
<span className="flex items-center gap-1">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="p-1 rounded hover:bg-zinc-100 text-zinc-400 hover:text-zinc-600 transition-colors"
|
|
95
|
+
onClick={copyAll}
|
|
96
|
+
title={t("commentManager.copyAllTitle")}
|
|
97
|
+
>
|
|
98
|
+
<Copy size={13} />
|
|
99
|
+
</button>
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
className="p-1 rounded hover:bg-zinc-100 text-zinc-400 hover:text-red-500 transition-colors"
|
|
103
|
+
onClick={() => setConfirmingDelete(true)}
|
|
104
|
+
title={t("commentManager.deleteAllTitle")}
|
|
105
|
+
>
|
|
106
|
+
<Trash2 size={13} />
|
|
107
|
+
</button>
|
|
108
|
+
</span>
|
|
91
109
|
</Text>
|
|
92
110
|
)}
|
|
93
111
|
|
|
94
112
|
<div className="overflow-y-auto max-h-80">
|
|
95
113
|
{sortedComments.length === 0 ? (
|
|
96
|
-
<Text variant="caption"
|
|
97
|
-
|
|
98
|
-
{t("commentManager.noComments")}
|
|
99
|
-
</div>
|
|
114
|
+
<Text variant="caption" as="div" className="px-3 py-4 text-center">
|
|
115
|
+
{t("commentManager.noComments")}
|
|
100
116
|
</Text>
|
|
101
117
|
) : (
|
|
102
118
|
sortedComments.map((comment) => (
|
|
@@ -74,20 +74,20 @@ export function CommentNav() {
|
|
|
74
74
|
<ChevronLeft className="w-4 h-4" />
|
|
75
75
|
</Button>
|
|
76
76
|
|
|
77
|
-
<Text
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
<Text
|
|
78
|
+
variant="body"
|
|
79
|
+
as="span"
|
|
80
|
+
className={cn(
|
|
81
|
+
"px-3 tabular-nums select-none min-w-[4rem] text-center",
|
|
82
|
+
"transition-transform duration-200 ease-out",
|
|
83
|
+
animating === "prev" && "-translate-x-0.5",
|
|
84
|
+
animating === "next" && "translate-x-0.5",
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
{t("commentNav.of", {
|
|
88
|
+
current: currentIndex + 1,
|
|
89
|
+
total: totalComments,
|
|
90
|
+
})}
|
|
91
91
|
</Text>
|
|
92
92
|
|
|
93
93
|
<Button
|