@peaske7/readit 0.1.8 → 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 +124 -172
- package/src/{cli/index.ts → cli.ts} +37 -53
- package/src/components/ActionsMenu.tsx +6 -27
- package/src/components/DocumentViewer/DocumentViewer.tsx +77 -106
- 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} +74 -74
- 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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { HighlightComment, TextPosition } from "./types";
|
|
2
|
+
|
|
3
|
+
export function findTextPosition(
|
|
4
|
+
textContent: string,
|
|
5
|
+
selectedText: string,
|
|
6
|
+
hintOffset?: number,
|
|
7
|
+
): TextPosition | undefined {
|
|
8
|
+
if (!selectedText || !textContent) return undefined;
|
|
9
|
+
|
|
10
|
+
const occurrences: number[] = [];
|
|
11
|
+
let idx = 0;
|
|
12
|
+
for (;;) {
|
|
13
|
+
idx = textContent.indexOf(selectedText, idx);
|
|
14
|
+
if (idx === -1) break;
|
|
15
|
+
occurrences.push(idx);
|
|
16
|
+
idx += 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (occurrences.length === 0) return undefined;
|
|
20
|
+
if (occurrences.length === 1) {
|
|
21
|
+
return { start: occurrences[0], end: occurrences[0] + selectedText.length };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const target = hintOffset ?? 0;
|
|
25
|
+
let closest = occurrences[0];
|
|
26
|
+
let minDist = Math.abs(closest - target);
|
|
27
|
+
for (const occ of occurrences) {
|
|
28
|
+
const dist = Math.abs(occ - target);
|
|
29
|
+
if (dist < minDist) {
|
|
30
|
+
minDist = dist;
|
|
31
|
+
closest = occ;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { start: closest, end: closest + selectedText.length };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class Resolver {
|
|
38
|
+
private worker: Worker | null = null;
|
|
39
|
+
private seq = 0;
|
|
40
|
+
private pending = new Map<
|
|
41
|
+
number,
|
|
42
|
+
(results: Map<string, TextPosition>) => void
|
|
43
|
+
>();
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
try {
|
|
47
|
+
this.worker = new Worker(new URL("./worker.ts", import.meta.url), {
|
|
48
|
+
type: "module",
|
|
49
|
+
});
|
|
50
|
+
this.worker.onmessage = this.onMessage;
|
|
51
|
+
this.worker.onerror = () => {
|
|
52
|
+
this.worker?.terminate();
|
|
53
|
+
this.worker = null;
|
|
54
|
+
};
|
|
55
|
+
} catch {
|
|
56
|
+
this.worker = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resolve(
|
|
61
|
+
text: string,
|
|
62
|
+
comments: HighlightComment[],
|
|
63
|
+
): Promise<Map<string, TextPosition>> {
|
|
64
|
+
if (!this.worker || comments.length === 0) {
|
|
65
|
+
return Promise.resolve(this.sync(text, comments));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
const id = this.seq++;
|
|
70
|
+
this.pending.set(id, resolve);
|
|
71
|
+
this.worker!.postMessage({
|
|
72
|
+
id,
|
|
73
|
+
textContent: text,
|
|
74
|
+
comments: comments.map((c) => ({
|
|
75
|
+
id: c.id,
|
|
76
|
+
selectedText: c.selectedText,
|
|
77
|
+
startOffset: c.startOffset,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
dispose() {
|
|
84
|
+
this.worker?.terminate();
|
|
85
|
+
this.worker = null;
|
|
86
|
+
for (const resolve of this.pending.values()) resolve(new Map());
|
|
87
|
+
this.pending.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private onMessage = (e: MessageEvent) => {
|
|
91
|
+
const { id, results } = e.data;
|
|
92
|
+
const resolve = this.pending.get(id);
|
|
93
|
+
if (!resolve) return;
|
|
94
|
+
|
|
95
|
+
this.pending.delete(id);
|
|
96
|
+
const map = new Map<string, TextPosition>();
|
|
97
|
+
for (const r of results) map.set(r.id, { start: r.start, end: r.end });
|
|
98
|
+
resolve(map);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
private sync(
|
|
102
|
+
text: string,
|
|
103
|
+
comments: HighlightComment[],
|
|
104
|
+
): Map<string, TextPosition> {
|
|
105
|
+
const map = new Map<string, TextPosition>();
|
|
106
|
+
for (const c of comments) {
|
|
107
|
+
const pos = findTextPosition(text, c.selectedText, c.startOffset);
|
|
108
|
+
if (pos) map.set(c.id, pos);
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,34 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the highlighting system.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Style configuration for highlight marks.
|
|
7
|
-
*/
|
|
8
1
|
export interface HighlightStyle {
|
|
9
2
|
attribute: string;
|
|
10
3
|
attributeValue: string;
|
|
11
4
|
}
|
|
12
5
|
|
|
13
|
-
/**
|
|
14
|
-
* Text range with character offsets.
|
|
15
|
-
*/
|
|
16
|
-
export interface TextRange {
|
|
17
|
-
startOffset: number;
|
|
18
|
-
endOffset: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Resolved text position in content.
|
|
23
|
-
*/
|
|
24
6
|
export interface TextPosition {
|
|
25
7
|
start: number;
|
|
26
8
|
end: number;
|
|
27
9
|
}
|
|
28
10
|
|
|
29
|
-
/**
|
|
30
|
-
* Comment data needed for highlighting (subset of full Comment type).
|
|
31
|
-
*/
|
|
32
11
|
export interface HighlightComment {
|
|
33
12
|
id: string;
|
|
34
13
|
selectedText: string;
|
|
@@ -36,20 +15,6 @@ export interface HighlightComment {
|
|
|
36
15
|
endOffset: number;
|
|
37
16
|
}
|
|
38
17
|
|
|
39
|
-
/**
|
|
40
|
-
* Highlight positions for margin note alignment and minimap.
|
|
41
|
-
* - positions: Y-position relative to container (for margin notes)
|
|
42
|
-
* - documentPositions: Y-position from document top (for minimap)
|
|
43
|
-
*/
|
|
44
|
-
export interface HighlightPositions {
|
|
45
|
-
positions: Record<string, number>;
|
|
46
|
-
documentPositions: Record<string, number>;
|
|
47
|
-
pendingTop?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Text node with its cumulative offset range in the document.
|
|
52
|
-
*/
|
|
53
18
|
export interface TextNodeInfo {
|
|
54
19
|
node: Text;
|
|
55
20
|
start: number;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Inlined to avoid module import issues in Worker context
|
|
2
|
+
function find(
|
|
3
|
+
text: string,
|
|
4
|
+
needle: string,
|
|
5
|
+
hint?: number,
|
|
6
|
+
): { start: number; end: number } | undefined {
|
|
7
|
+
if (!needle || !text) return undefined;
|
|
8
|
+
|
|
9
|
+
const hits: number[] = [];
|
|
10
|
+
let i = 0;
|
|
11
|
+
for (;;) {
|
|
12
|
+
i = text.indexOf(needle, i);
|
|
13
|
+
if (i === -1) break;
|
|
14
|
+
hits.push(i);
|
|
15
|
+
i += 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (hits.length === 0) return undefined;
|
|
19
|
+
if (hits.length === 1)
|
|
20
|
+
return { start: hits[0], end: hits[0] + needle.length };
|
|
21
|
+
|
|
22
|
+
const target = hint ?? 0;
|
|
23
|
+
let best = hits[0];
|
|
24
|
+
let bestDist = Math.abs(best - target);
|
|
25
|
+
for (const h of hits) {
|
|
26
|
+
const d = Math.abs(h - target);
|
|
27
|
+
if (d < bestDist) {
|
|
28
|
+
bestDist = d;
|
|
29
|
+
best = h;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { start: best, end: best + needle.length };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
self.onmessage = (e: MessageEvent) => {
|
|
36
|
+
const { id, textContent, comments } = e.data;
|
|
37
|
+
const results: { id: string; start: number; end: number }[] = [];
|
|
38
|
+
|
|
39
|
+
for (const c of comments) {
|
|
40
|
+
const pos = find(textContent, c.selectedText, c.startOffset);
|
|
41
|
+
if (pos) results.push({ id: c.id, ...pos });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
self.postMessage({ id, results });
|
|
45
|
+
};
|
package/src/lib/i18n/en.ts
CHANGED
|
@@ -13,14 +13,9 @@ export const en: Translations = {
|
|
|
13
13
|
|
|
14
14
|
// Actions menu
|
|
15
15
|
"actions.ariaLabel": "Actions menu",
|
|
16
|
-
"actions.centered": "Centered",
|
|
17
|
-
"actions.fullscreen": "Fullscreen",
|
|
18
16
|
"actions.settings": "Settings",
|
|
19
17
|
"actions.reload": "Reload",
|
|
20
|
-
"actions.
|
|
21
|
-
"actions.copyAllAITitle": "Copy in prompt format for AI assistants",
|
|
22
|
-
"actions.copyAllRaw": "Copy All (Raw)",
|
|
23
|
-
"actions.copyAllRawTitle": "Copy as plain text",
|
|
18
|
+
"actions.copyAll": "Copy All",
|
|
24
19
|
"actions.exportJson": "Export JSON",
|
|
25
20
|
"actions.viewRaw": "View Raw",
|
|
26
21
|
|
|
@@ -29,37 +24,23 @@ export const en: Translations = {
|
|
|
29
24
|
"settings.theme": "Theme",
|
|
30
25
|
"settings.font": "Font",
|
|
31
26
|
"settings.language": "Language",
|
|
32
|
-
"settings.keyboardShortcuts": "Keyboard Shortcuts",
|
|
33
|
-
"settings.clickToRebind": "Click a key to rebind",
|
|
34
27
|
"settings.theme.system": "System",
|
|
35
28
|
"settings.theme.light": "Light",
|
|
36
29
|
"settings.theme.dark": "Dark",
|
|
37
30
|
"settings.font.serif": "Serif",
|
|
38
31
|
"settings.font.sansSerif": "Sans-serif",
|
|
39
|
-
"settings.editor": "Editor",
|
|
40
|
-
"settings.editor.none": "None",
|
|
41
|
-
"settings.editor.vscode": "VS Code",
|
|
42
|
-
"settings.editor.vscodeInsiders": "VS Code Insiders",
|
|
43
|
-
"settings.editor.cursor": "Cursor",
|
|
44
32
|
|
|
45
33
|
// Comment input
|
|
46
34
|
"comment.placeholder": "Add your comment...",
|
|
47
35
|
"comment.cancel": "Cancel",
|
|
48
36
|
"comment.addNote": "Add Note",
|
|
49
37
|
"comment.highlight": "Highlight",
|
|
50
|
-
"comment.copyRawTitle": "Copy raw text (⌘C)",
|
|
51
|
-
"comment.copyRawLabel": "Copy raw text",
|
|
52
|
-
"comment.copyLLMTitle": "Copy with context for LLM (⌘⇧C)",
|
|
53
|
-
"comment.copyLLMLabel": "Copy for LLM",
|
|
54
38
|
|
|
55
39
|
// Margin note
|
|
56
40
|
"marginNote.addNote": "Add note",
|
|
57
41
|
"marginNote.delete": "Delete",
|
|
58
42
|
"marginNote.edit": "Edit",
|
|
59
43
|
"marginNote.copy": "Copy",
|
|
60
|
-
"marginNote.copyTitle": "Copy raw text (⌘C)",
|
|
61
|
-
"marginNote.llm": "LLM",
|
|
62
|
-
"marginNote.llmTitle": "Copy with context for LLM (⌘⇧C)",
|
|
63
44
|
|
|
64
45
|
// Comment manager
|
|
65
46
|
"commentManager.unresolved": "unresolved",
|
|
@@ -99,39 +80,9 @@ export const en: Translations = {
|
|
|
99
80
|
"rawModal.copiedToClipboard": "Copied to clipboard",
|
|
100
81
|
"rawModal.failedToCopy": "Failed to copy",
|
|
101
82
|
|
|
102
|
-
// Shortcut groups
|
|
103
|
-
"shortcutGroup.copy": "Copy",
|
|
104
|
-
"shortcutGroup.navigate": "Navigate",
|
|
105
|
-
"shortcutGroup.other": "Other",
|
|
106
|
-
"shortcuts.resetToDefaults": "Reset to defaults",
|
|
107
|
-
"shortcuts.enableDisable": "Enable/disable shortcut",
|
|
108
|
-
"shortcutCapture.pressKeys": "Press keys...",
|
|
109
|
-
|
|
110
|
-
// Shortcut labels
|
|
111
|
-
"shortcut.copyAll.label": "Copy All (AI)",
|
|
112
|
-
"shortcut.copyAll.description": "Copy all comments in AI prompt format",
|
|
113
|
-
"shortcut.copyAllRaw.label": "Copy All (Raw)",
|
|
114
|
-
"shortcut.copyAllRaw.description": "Copy all comments as raw text",
|
|
115
|
-
"shortcut.navigateNext.label": "Next Comment",
|
|
116
|
-
"shortcut.navigateNext.description": "Navigate to next comment",
|
|
117
|
-
"shortcut.navigatePrevious.label": "Previous Comment",
|
|
118
|
-
"shortcut.navigatePrevious.description": "Navigate to previous comment",
|
|
119
|
-
"shortcut.copySelectionRaw.label": "Copy Selection",
|
|
120
|
-
"shortcut.copySelectionRaw.description": "Copy selected text",
|
|
121
|
-
"shortcut.copySelectionLLM.label": "Copy Selection (LLM)",
|
|
122
|
-
"shortcut.copySelectionLLM.description":
|
|
123
|
-
"Copy selected text with context for LLM",
|
|
124
|
-
"shortcut.clearSelection.label": "Clear Selection",
|
|
125
|
-
"shortcut.clearSelection.description": "Clear text selection",
|
|
126
|
-
|
|
127
83
|
// Toast messages
|
|
128
84
|
"toast.copied": 'Copied: "{{text}}"',
|
|
129
|
-
"toast.copiedForLLM": 'Copied for LLM: "{{text}}"',
|
|
130
85
|
"toast.copiedAllComments": "Copied all comments",
|
|
131
|
-
"toast.copiedAllRaw": "Copied all comments as raw text",
|
|
132
|
-
|
|
133
|
-
// Floating TOC
|
|
134
|
-
"floatingTOC.label": "Table of Contents",
|
|
135
86
|
|
|
136
87
|
// Comment badge
|
|
137
88
|
"commentBadge.title": "{{count}} comment",
|
package/src/lib/i18n/ja.ts
CHANGED
|
@@ -13,14 +13,9 @@ export const ja: Translations = {
|
|
|
13
13
|
|
|
14
14
|
// Actions menu
|
|
15
15
|
"actions.ariaLabel": "操作メニュー",
|
|
16
|
-
"actions.centered": "中央揃え",
|
|
17
|
-
"actions.fullscreen": "全画面",
|
|
18
16
|
"actions.settings": "設定",
|
|
19
17
|
"actions.reload": "再読み込み",
|
|
20
|
-
"actions.
|
|
21
|
-
"actions.copyAllAITitle": "AIアシスタント用プロンプト形式でコピー",
|
|
22
|
-
"actions.copyAllRaw": "全てコピー (テキスト)",
|
|
23
|
-
"actions.copyAllRawTitle": "プレーンテキストとしてコピー",
|
|
18
|
+
"actions.copyAll": "全てコピー",
|
|
24
19
|
"actions.exportJson": "JSONエクスポート",
|
|
25
20
|
"actions.viewRaw": "生データを表示",
|
|
26
21
|
|
|
@@ -29,37 +24,23 @@ export const ja: Translations = {
|
|
|
29
24
|
"settings.theme": "テーマ",
|
|
30
25
|
"settings.font": "フォント",
|
|
31
26
|
"settings.language": "言語",
|
|
32
|
-
"settings.keyboardShortcuts": "キーボードショートカット",
|
|
33
|
-
"settings.clickToRebind": "キーをクリックして変更",
|
|
34
27
|
"settings.theme.system": "システム",
|
|
35
28
|
"settings.theme.light": "ライト",
|
|
36
29
|
"settings.theme.dark": "ダーク",
|
|
37
30
|
"settings.font.serif": "明朝体",
|
|
38
31
|
"settings.font.sansSerif": "ゴシック体",
|
|
39
|
-
"settings.editor": "エディター",
|
|
40
|
-
"settings.editor.none": "なし",
|
|
41
|
-
"settings.editor.vscode": "VS Code",
|
|
42
|
-
"settings.editor.vscodeInsiders": "VS Code Insiders",
|
|
43
|
-
"settings.editor.cursor": "Cursor",
|
|
44
32
|
|
|
45
33
|
// Comment input
|
|
46
34
|
"comment.placeholder": "コメントを入力...",
|
|
47
35
|
"comment.cancel": "キャンセル",
|
|
48
36
|
"comment.addNote": "メモを追加",
|
|
49
37
|
"comment.highlight": "ハイライト",
|
|
50
|
-
"comment.copyRawTitle": "テキストをコピー (⌘C)",
|
|
51
|
-
"comment.copyRawLabel": "テキストをコピー",
|
|
52
|
-
"comment.copyLLMTitle": "LLM用にコンテキスト付きでコピー (⌘⇧C)",
|
|
53
|
-
"comment.copyLLMLabel": "LLM用にコピー",
|
|
54
38
|
|
|
55
39
|
// Margin note
|
|
56
40
|
"marginNote.addNote": "メモを追加",
|
|
57
41
|
"marginNote.delete": "削除",
|
|
58
42
|
"marginNote.edit": "編集",
|
|
59
43
|
"marginNote.copy": "コピー",
|
|
60
|
-
"marginNote.copyTitle": "テキストをコピー (⌘C)",
|
|
61
|
-
"marginNote.llm": "LLM",
|
|
62
|
-
"marginNote.llmTitle": "LLM用にコンテキスト付きでコピー (⌘⇧C)",
|
|
63
44
|
|
|
64
45
|
// Comment manager
|
|
65
46
|
"commentManager.unresolved": "未解決",
|
|
@@ -101,39 +82,9 @@ export const ja: Translations = {
|
|
|
101
82
|
"rawModal.copiedToClipboard": "クリップボードにコピーしました",
|
|
102
83
|
"rawModal.failedToCopy": "コピーに失敗しました",
|
|
103
84
|
|
|
104
|
-
// Shortcut groups
|
|
105
|
-
"shortcutGroup.copy": "コピー",
|
|
106
|
-
"shortcutGroup.navigate": "ナビゲーション",
|
|
107
|
-
"shortcutGroup.other": "その他",
|
|
108
|
-
"shortcuts.resetToDefaults": "初期設定に戻す",
|
|
109
|
-
"shortcuts.enableDisable": "ショートカットの有効/無効",
|
|
110
|
-
"shortcutCapture.pressKeys": "キーを入力...",
|
|
111
|
-
|
|
112
|
-
// Shortcut labels
|
|
113
|
-
"shortcut.copyAll.label": "全てコピー (AI)",
|
|
114
|
-
"shortcut.copyAll.description": "全コメントをAIプロンプト形式でコピー",
|
|
115
|
-
"shortcut.copyAllRaw.label": "全てコピー (テキスト)",
|
|
116
|
-
"shortcut.copyAllRaw.description": "全コメントをテキストとしてコピー",
|
|
117
|
-
"shortcut.navigateNext.label": "次のコメント",
|
|
118
|
-
"shortcut.navigateNext.description": "次のコメントに移動",
|
|
119
|
-
"shortcut.navigatePrevious.label": "前のコメント",
|
|
120
|
-
"shortcut.navigatePrevious.description": "前のコメントに移動",
|
|
121
|
-
"shortcut.copySelectionRaw.label": "選択をコピー",
|
|
122
|
-
"shortcut.copySelectionRaw.description": "選択テキストをコピー",
|
|
123
|
-
"shortcut.copySelectionLLM.label": "選択をコピー (LLM)",
|
|
124
|
-
"shortcut.copySelectionLLM.description":
|
|
125
|
-
"選択テキストをLLM用コンテキスト付きでコピー",
|
|
126
|
-
"shortcut.clearSelection.label": "選択を解除",
|
|
127
|
-
"shortcut.clearSelection.description": "テキスト選択を解除",
|
|
128
|
-
|
|
129
85
|
// Toast messages
|
|
130
86
|
"toast.copied": 'コピーしました: "{{text}}"',
|
|
131
|
-
"toast.copiedForLLM": 'LLM用にコピーしました: "{{text}}"',
|
|
132
87
|
"toast.copiedAllComments": "全てのコメントをコピーしました",
|
|
133
|
-
"toast.copiedAllRaw": "全てのコメントをテキストとしてコピーしました",
|
|
134
|
-
|
|
135
|
-
// Floating TOC
|
|
136
|
-
"floatingTOC.label": "目次",
|
|
137
88
|
|
|
138
89
|
// Comment badge
|
|
139
90
|
"commentBadge.title": "{{count}}件のコメント",
|
package/src/lib/i18n/types.ts
CHANGED
|
@@ -18,14 +18,9 @@ export interface Translations {
|
|
|
18
18
|
|
|
19
19
|
// Actions menu
|
|
20
20
|
"actions.ariaLabel": string;
|
|
21
|
-
"actions.centered": string;
|
|
22
|
-
"actions.fullscreen": string;
|
|
23
21
|
"actions.settings": string;
|
|
24
22
|
"actions.reload": string;
|
|
25
|
-
"actions.
|
|
26
|
-
"actions.copyAllAITitle": string;
|
|
27
|
-
"actions.copyAllRaw": string;
|
|
28
|
-
"actions.copyAllRawTitle": string;
|
|
23
|
+
"actions.copyAll": string;
|
|
29
24
|
"actions.exportJson": string;
|
|
30
25
|
"actions.viewRaw": string;
|
|
31
26
|
|
|
@@ -34,37 +29,23 @@ export interface Translations {
|
|
|
34
29
|
"settings.theme": string;
|
|
35
30
|
"settings.font": string;
|
|
36
31
|
"settings.language": string;
|
|
37
|
-
"settings.keyboardShortcuts": string;
|
|
38
|
-
"settings.clickToRebind": string;
|
|
39
32
|
"settings.theme.system": string;
|
|
40
33
|
"settings.theme.light": string;
|
|
41
34
|
"settings.theme.dark": string;
|
|
42
35
|
"settings.font.serif": string;
|
|
43
36
|
"settings.font.sansSerif": string;
|
|
44
|
-
"settings.editor": string;
|
|
45
|
-
"settings.editor.none": string;
|
|
46
|
-
"settings.editor.vscode": string;
|
|
47
|
-
"settings.editor.vscodeInsiders": string;
|
|
48
|
-
"settings.editor.cursor": string;
|
|
49
37
|
|
|
50
38
|
// Comment input
|
|
51
39
|
"comment.placeholder": string;
|
|
52
40
|
"comment.cancel": string;
|
|
53
41
|
"comment.addNote": string;
|
|
54
42
|
"comment.highlight": string;
|
|
55
|
-
"comment.copyRawTitle": string;
|
|
56
|
-
"comment.copyRawLabel": string;
|
|
57
|
-
"comment.copyLLMTitle": string;
|
|
58
|
-
"comment.copyLLMLabel": string;
|
|
59
43
|
|
|
60
44
|
// Margin note
|
|
61
45
|
"marginNote.addNote": string;
|
|
62
46
|
"marginNote.delete": string;
|
|
63
47
|
"marginNote.edit": string;
|
|
64
48
|
"marginNote.copy": string;
|
|
65
|
-
"marginNote.copyTitle": string;
|
|
66
|
-
"marginNote.llm": string;
|
|
67
|
-
"marginNote.llmTitle": string;
|
|
68
49
|
|
|
69
50
|
// Comment manager
|
|
70
51
|
"commentManager.unresolved": string;
|
|
@@ -104,38 +85,9 @@ export interface Translations {
|
|
|
104
85
|
"rawModal.copiedToClipboard": string;
|
|
105
86
|
"rawModal.failedToCopy": string;
|
|
106
87
|
|
|
107
|
-
// Shortcut groups
|
|
108
|
-
"shortcutGroup.copy": string;
|
|
109
|
-
"shortcutGroup.navigate": string;
|
|
110
|
-
"shortcutGroup.other": string;
|
|
111
|
-
"shortcuts.resetToDefaults": string;
|
|
112
|
-
"shortcuts.enableDisable": string;
|
|
113
|
-
"shortcutCapture.pressKeys": string;
|
|
114
|
-
|
|
115
|
-
// Shortcut labels (rendered in ShortcutList)
|
|
116
|
-
"shortcut.copyAll.label": string;
|
|
117
|
-
"shortcut.copyAll.description": string;
|
|
118
|
-
"shortcut.copyAllRaw.label": string;
|
|
119
|
-
"shortcut.copyAllRaw.description": string;
|
|
120
|
-
"shortcut.navigateNext.label": string;
|
|
121
|
-
"shortcut.navigateNext.description": string;
|
|
122
|
-
"shortcut.navigatePrevious.label": string;
|
|
123
|
-
"shortcut.navigatePrevious.description": string;
|
|
124
|
-
"shortcut.copySelectionRaw.label": string;
|
|
125
|
-
"shortcut.copySelectionRaw.description": string;
|
|
126
|
-
"shortcut.copySelectionLLM.label": string;
|
|
127
|
-
"shortcut.copySelectionLLM.description": string;
|
|
128
|
-
"shortcut.clearSelection.label": string;
|
|
129
|
-
"shortcut.clearSelection.description": string;
|
|
130
|
-
|
|
131
88
|
// Toast messages
|
|
132
89
|
"toast.copied": string;
|
|
133
|
-
"toast.copiedForLLM": string;
|
|
134
90
|
"toast.copiedAllComments": string;
|
|
135
|
-
"toast.copiedAllRaw": string;
|
|
136
|
-
|
|
137
|
-
// Floating TOC
|
|
138
|
-
"floatingTOC.label": string;
|
|
139
91
|
|
|
140
92
|
// Comment badge
|
|
141
93
|
"commentBadge.title": string;
|
package/src/lib/margin-layout.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
MARGIN_NOTE_MIN_GAP_PX,
|
|
4
|
-
} from "./layout-constants";
|
|
1
|
+
const MARGIN_NOTE_MIN_GAP_PX = 150;
|
|
2
|
+
const COMMENT_INPUT_HEIGHT_PX = 160;
|
|
5
3
|
|
|
6
4
|
interface NotePosition {
|
|
7
5
|
commentId: string;
|
|
@@ -10,18 +8,7 @@ interface NotePosition {
|
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Resolves margin note positions to avoid overlaps.
|
|
13
|
-
*
|
|
14
|
-
* Algorithm:
|
|
15
|
-
* 1. Build initial positions from highlight positions
|
|
16
|
-
* 2. Handle input zone collision (push notes up or down to avoid input)
|
|
17
|
-
* 3. Resolve note-to-note overlaps:
|
|
18
|
-
* - Pass 1: Notes above input zone → push UP
|
|
19
|
-
* - Pass 2: Notes at/below input zone → push DOWN
|
|
20
|
-
*
|
|
21
|
-
* @param commentIds - Comment IDs in document order (sorted by startOffset)
|
|
22
|
-
* @param highlightPositions - Map of comment ID to top position
|
|
23
|
-
* @param pendingSelectionTop - Top position of input zone (if any)
|
|
24
|
-
* @returns Map of comment ID to resolved top position
|
|
11
|
+
* Pass 1: push notes above input zone UP. Pass 2: push notes at/below DOWN.
|
|
25
12
|
*/
|
|
26
13
|
export function resolveMarginNotePositions(
|
|
27
14
|
commentIds: string[],
|
|
@@ -36,10 +23,8 @@ export function resolveMarginNotePositions(
|
|
|
36
23
|
top: highlightPositions[id],
|
|
37
24
|
}));
|
|
38
25
|
|
|
39
|
-
// Sort by top position
|
|
40
26
|
positions.sort((a, b) => a.top - b.top);
|
|
41
27
|
|
|
42
|
-
// Handle input zone collision - check visual overlap, not just top position
|
|
43
28
|
if (pendingSelectionTop !== null && pendingSelectionTop !== undefined) {
|
|
44
29
|
const inputStart = pendingSelectionTop;
|
|
45
30
|
const inputEnd = pendingSelectionTop + COMMENT_INPUT_HEIGHT_PX;
|
|
@@ -47,48 +32,41 @@ export function resolveMarginNotePositions(
|
|
|
47
32
|
for (const pos of positions) {
|
|
48
33
|
const noteBottom = pos.top + MARGIN_NOTE_MIN_GAP_PX;
|
|
49
34
|
|
|
50
|
-
// Check if note visually overlaps with input zone
|
|
51
35
|
const overlaps = noteBottom > inputStart && pos.top < inputEnd;
|
|
52
36
|
|
|
53
37
|
if (overlaps) {
|
|
54
38
|
if (pos.top < inputStart) {
|
|
55
|
-
// Note is above input but overlaps - push UP
|
|
56
39
|
pos.top = Math.max(0, inputStart - MARGIN_NOTE_MIN_GAP_PX);
|
|
57
40
|
} else {
|
|
58
|
-
// Note is within/below input zone - push DOWN
|
|
59
41
|
pos.top = inputEnd;
|
|
60
42
|
}
|
|
61
43
|
}
|
|
62
44
|
}
|
|
63
|
-
// Re-sort after potential position changes
|
|
64
45
|
positions.sort((a, b) => a.top - b.top);
|
|
65
46
|
}
|
|
66
47
|
|
|
67
|
-
// Resolve note-to-note overlaps
|
|
68
48
|
const inputStartForOverlap = pendingSelectionTop ?? Infinity;
|
|
69
49
|
const inputEndForOverlap =
|
|
70
50
|
pendingSelectionTop != null
|
|
71
51
|
? pendingSelectionTop + COMMENT_INPUT_HEIGHT_PX
|
|
72
52
|
: Infinity;
|
|
73
53
|
|
|
74
|
-
// Pass 1:
|
|
54
|
+
// Pass 1: push notes above input UP (bottom to top)
|
|
75
55
|
for (let i = positions.length - 2; i >= 0; i--) {
|
|
76
56
|
const curr = positions[i];
|
|
77
57
|
const next = positions[i + 1];
|
|
78
|
-
// Only process if next note is above the input zone
|
|
79
58
|
if (next.top >= inputStartForOverlap) continue;
|
|
80
59
|
if (next.top - curr.top < MARGIN_NOTE_MIN_GAP_PX) {
|
|
81
60
|
curr.top = Math.max(0, next.top - MARGIN_NOTE_MIN_GAP_PX);
|
|
82
61
|
}
|
|
83
62
|
}
|
|
84
63
|
|
|
85
|
-
// Pass 2:
|
|
64
|
+
// Pass 2: push notes at/below input DOWN (top to bottom)
|
|
86
65
|
for (let i = 1; i < positions.length; i++) {
|
|
87
66
|
const prev = positions[i - 1];
|
|
88
67
|
const curr = positions[i];
|
|
89
68
|
if (curr.top - prev.top < MARGIN_NOTE_MIN_GAP_PX) {
|
|
90
69
|
let newTop = prev.top + MARGIN_NOTE_MIN_GAP_PX;
|
|
91
|
-
// If new position lands in input zone, skip to below input
|
|
92
70
|
if (newTop >= inputStartForOverlap && newTop < inputEndForOverlap) {
|
|
93
71
|
newTop = inputEndForOverlap;
|
|
94
72
|
}
|