@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,7 +1,7 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { Comment, CommentFile } from "../
|
|
4
|
+
import type { Comment, CommentFile } from "../schema";
|
|
5
5
|
|
|
6
6
|
const FORMAT_VERSION = 1;
|
|
7
7
|
const HASH_LENGTH = 16;
|
|
@@ -9,9 +9,6 @@ const MAX_SELECTION_LENGTH = 1000;
|
|
|
9
9
|
const TRUNCATION_MARKER = "\n...\n";
|
|
10
10
|
const ANCHOR_PREFIX_LENGTH = 200; // chars stored for anchor matching when text is truncated
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Truncate very long selections to first ~500 + ... + last ~500 chars.
|
|
14
|
-
*/
|
|
15
12
|
export function truncateSelection(text: string): string {
|
|
16
13
|
if (text.length <= MAX_SELECTION_LENGTH) {
|
|
17
14
|
return text;
|
|
@@ -27,13 +24,9 @@ export function truncateSelection(text: string): string {
|
|
|
27
24
|
* Comments are stored in ~/.readit/comments/{absolute-path-structure}/{filename}.comments.md
|
|
28
25
|
*/
|
|
29
26
|
export function getCommentPath(sourcePath: string): string {
|
|
30
|
-
// Resolve to absolute path
|
|
31
27
|
const absolute = path.resolve(sourcePath);
|
|
32
|
-
|
|
33
28
|
// Remove leading slash and drive letter (Windows)
|
|
34
29
|
const normalized = absolute.replace(/^\//, "").replace(/^[A-Z]:[\\/]/, "");
|
|
35
|
-
|
|
36
|
-
// Get filename without extension, add .comments.md
|
|
37
30
|
const ext = path.extname(normalized);
|
|
38
31
|
const withoutExt = normalized.slice(0, -ext.length || undefined);
|
|
39
32
|
|
|
@@ -45,9 +38,6 @@ export function getCommentPath(sourcePath: string): string {
|
|
|
45
38
|
);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
/**
|
|
49
|
-
* Compute SHA-256 hash of content, returning first 16 characters.
|
|
50
|
-
*/
|
|
51
41
|
export function computeHash(content: string): string {
|
|
52
42
|
return crypto
|
|
53
43
|
.createHash("sha256")
|
|
@@ -56,18 +46,12 @@ export function computeHash(content: string): string {
|
|
|
56
46
|
.slice(0, HASH_LENGTH);
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
/**
|
|
60
|
-
* Get line number (1-indexed) for a character offset in content.
|
|
61
|
-
*/
|
|
62
49
|
export function getLineNumber(content: string, offset: number): number {
|
|
63
50
|
if (offset <= 0 || content.length === 0) return 1;
|
|
64
51
|
const clampedOffset = Math.min(offset, content.length);
|
|
65
52
|
return content.slice(0, clampedOffset).split("\n").length;
|
|
66
53
|
}
|
|
67
54
|
|
|
68
|
-
/**
|
|
69
|
-
* Get line range string for a selection (e.g., "L42" or "L42-45").
|
|
70
|
-
*/
|
|
71
55
|
export function getLineHint(
|
|
72
56
|
content: string,
|
|
73
57
|
startOffset: number,
|
|
@@ -75,12 +59,9 @@ export function getLineHint(
|
|
|
75
59
|
): string {
|
|
76
60
|
const startLine = getLineNumber(content, startOffset);
|
|
77
61
|
const endLine = getLineNumber(content, endOffset);
|
|
78
|
-
return startLine === endLine ? `L${startLine}` : `L${startLine}
|
|
62
|
+
return startLine === endLine ? `L${startLine}` : `L${startLine}-L${endLine}`;
|
|
79
63
|
}
|
|
80
64
|
|
|
81
|
-
/**
|
|
82
|
-
* Parse a comment file's markdown content into a CommentFile structure.
|
|
83
|
-
*/
|
|
84
65
|
export function parseCommentFile(content: string): CommentFile {
|
|
85
66
|
const result: CommentFile = {
|
|
86
67
|
source: "",
|
|
@@ -93,7 +74,6 @@ export function parseCommentFile(content: string): CommentFile {
|
|
|
93
74
|
return result;
|
|
94
75
|
}
|
|
95
76
|
|
|
96
|
-
// Parse YAML front matter
|
|
97
77
|
const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
98
78
|
if (frontMatterMatch) {
|
|
99
79
|
const frontMatter = frontMatterMatch[1];
|
|
@@ -105,7 +85,6 @@ export function parseCommentFile(content: string): CommentFile {
|
|
|
105
85
|
if (hashMatch) result.hash = hashMatch[1].trim();
|
|
106
86
|
if (versionMatch) result.version = Number.parseInt(versionMatch[1], 10);
|
|
107
87
|
|
|
108
|
-
// Validate version compatibility
|
|
109
88
|
if (result.version > FORMAT_VERSION) {
|
|
110
89
|
throw new Error(
|
|
111
90
|
`Comment file requires readit v${result.version} or higher. ` +
|
|
@@ -114,7 +93,6 @@ export function parseCommentFile(content: string): CommentFile {
|
|
|
114
93
|
}
|
|
115
94
|
}
|
|
116
95
|
|
|
117
|
-
// Remove front matter and split by separator
|
|
118
96
|
const bodyContent = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
119
97
|
const blocks = bodyContent.split(/\n---\n/).filter((block) => block.trim());
|
|
120
98
|
|
|
@@ -128,9 +106,6 @@ export function parseCommentFile(content: string): CommentFile {
|
|
|
128
106
|
return result;
|
|
129
107
|
}
|
|
130
108
|
|
|
131
|
-
/**
|
|
132
|
-
* Parse a single comment block.
|
|
133
|
-
*/
|
|
134
109
|
function parseCommentBlock(block: string): Comment | undefined {
|
|
135
110
|
// Extract metadata from HTML comment: <!-- c:{id}|{lineHint}|{timestamp} -->
|
|
136
111
|
const metadataMatch = block.match(/<!--\s*c:([^|]+)\|([^|]+)\|([^>]+)\s*-->/);
|
|
@@ -144,19 +119,16 @@ function parseCommentBlock(block: string): Comment | undefined {
|
|
|
144
119
|
const anchorMatch = block.match(/<!--\s*anchor:(.+?)\s*-->/);
|
|
145
120
|
const anchorPrefix = anchorMatch ? anchorMatch[1] : undefined;
|
|
146
121
|
|
|
147
|
-
// Extract selected text from blockquote
|
|
148
122
|
const blockquoteMatch = block.match(/^>\s*(.+(?:\n>\s*.+)*)$/m);
|
|
149
123
|
if (!blockquoteMatch) {
|
|
150
124
|
return undefined;
|
|
151
125
|
}
|
|
152
126
|
|
|
153
|
-
// Remove the "> " prefix from each line
|
|
154
127
|
const selectedText = blockquoteMatch[1]
|
|
155
128
|
.split("\n")
|
|
156
129
|
.map((line) => line.replace(/^>\s*/, ""))
|
|
157
130
|
.join("\n");
|
|
158
131
|
|
|
159
|
-
// Extract comment body (everything after blockquote)
|
|
160
132
|
const afterBlockquote = block.slice(
|
|
161
133
|
block.indexOf(blockquoteMatch[0]) + blockquoteMatch[0].length,
|
|
162
134
|
);
|
|
@@ -175,13 +147,9 @@ function parseCommentBlock(block: string): Comment | undefined {
|
|
|
175
147
|
};
|
|
176
148
|
}
|
|
177
149
|
|
|
178
|
-
/**
|
|
179
|
-
* Serialize a CommentFile structure to markdown content.
|
|
180
|
-
*/
|
|
181
150
|
export function serializeComments(file: CommentFile): string {
|
|
182
151
|
const lines: string[] = [];
|
|
183
152
|
|
|
184
|
-
// YAML front matter
|
|
185
153
|
lines.push("---");
|
|
186
154
|
lines.push(`source: ${file.source}`);
|
|
187
155
|
lines.push(`hash: ${file.hash}`);
|
|
@@ -189,7 +157,6 @@ export function serializeComments(file: CommentFile): string {
|
|
|
189
157
|
lines.push("---");
|
|
190
158
|
lines.push("");
|
|
191
159
|
|
|
192
|
-
// Comments
|
|
193
160
|
for (const comment of file.comments) {
|
|
194
161
|
lines.push(serializeComment(comment));
|
|
195
162
|
lines.push("");
|
|
@@ -200,28 +167,21 @@ export function serializeComments(file: CommentFile): string {
|
|
|
200
167
|
return lines.join("\n");
|
|
201
168
|
}
|
|
202
169
|
|
|
203
|
-
/**
|
|
204
|
-
* Serialize a single comment to markdown block.
|
|
205
|
-
*/
|
|
206
170
|
function serializeComment(comment: Comment): string {
|
|
207
171
|
const lines: string[] = [];
|
|
208
172
|
|
|
209
|
-
// Metadata as HTML comment
|
|
210
173
|
const lineHint = comment.lineHint || "L0";
|
|
211
174
|
lines.push(`<!-- c:${comment.id}|${lineHint}|${comment.createdAt} -->`);
|
|
212
175
|
|
|
213
|
-
// Anchor prefix for long selections (used for anchor matching when text is truncated)
|
|
214
176
|
if (comment.anchorPrefix) {
|
|
215
177
|
lines.push(`<!-- anchor:${comment.anchorPrefix} -->`);
|
|
216
178
|
}
|
|
217
179
|
|
|
218
|
-
// Selected text as blockquote
|
|
219
180
|
const quotedLines = comment.selectedText
|
|
220
181
|
.split("\n")
|
|
221
182
|
.map((line) => `> ${line}`);
|
|
222
183
|
lines.push(...quotedLines);
|
|
223
184
|
|
|
224
|
-
// Comment body
|
|
225
185
|
if (comment.comment) {
|
|
226
186
|
lines.push("");
|
|
227
187
|
lines.push(comment.comment);
|
|
@@ -230,9 +190,6 @@ function serializeComment(comment: Comment): string {
|
|
|
230
190
|
return lines.join("\n");
|
|
231
191
|
}
|
|
232
192
|
|
|
233
|
-
/**
|
|
234
|
-
* Create a new comment with a generated ID and current timestamp.
|
|
235
|
-
*/
|
|
236
193
|
export function createComment(
|
|
237
194
|
selectedText: string,
|
|
238
195
|
commentText: string,
|
|
@@ -255,7 +212,6 @@ export function createComment(
|
|
|
255
212
|
startOffset,
|
|
256
213
|
endOffset,
|
|
257
214
|
lineHint,
|
|
258
|
-
// Store first N chars for anchor matching when text is truncated
|
|
259
215
|
anchorPrefix: needsTruncation
|
|
260
216
|
? selectedText.slice(0, ANCHOR_PREFIX_LENGTH)
|
|
261
217
|
: undefined,
|
package/src/lib/export.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import type { Comment, Document } from "../
|
|
1
|
+
import type { Comment, Document } from "../schema";
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
return `---\nSelected text: "${c.selectedText}"\nComment: ${c.comment}`;
|
|
7
|
-
})
|
|
8
|
-
.join("\n\n");
|
|
9
|
-
|
|
10
|
-
return `# Review Comments for ${fileName}\n\n${prompt}`;
|
|
3
|
+
export function formatComment(c: Comment): string {
|
|
4
|
+
const line = c.lineHint ? `[${c.lineHint}] ` : "";
|
|
5
|
+
return `${line}"${c.selectedText}"\n${c.comment}`;
|
|
11
6
|
}
|
|
12
7
|
|
|
13
|
-
export function
|
|
14
|
-
return comments
|
|
15
|
-
.map((c) => `${c.selectedText}\n\n${c.comment}`)
|
|
16
|
-
.join("\n\n---\n\n");
|
|
8
|
+
export function generatePrompt(comments: Comment[], fileName: string): string {
|
|
9
|
+
return `# Review Comments for ${fileName}\n\n${comments.map(formatComment).join("\n\n---\n\n")}`;
|
|
17
10
|
}
|
|
18
11
|
|
|
19
12
|
export function exportCommentsAsJson(
|
|
@@ -27,6 +20,7 @@ export function exportCommentsAsJson(
|
|
|
27
20
|
comments: comments.map((c) => ({
|
|
28
21
|
selectedText: c.selectedText,
|
|
29
22
|
comment: c.comment,
|
|
23
|
+
lineHint: c.lineHint,
|
|
30
24
|
createdAt: c.createdAt,
|
|
31
25
|
})),
|
|
32
26
|
};
|
package/src/lib/highlight/dom.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HighlightStyle, TextNodeInfo } from "./types";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Block-level elements that should have newlines between them.
|
|
5
|
-
* Used to normalize whitespace in text extraction.
|
|
6
|
-
*/
|
|
7
3
|
const BLOCK_ELEMENTS = new Set([
|
|
8
4
|
"P",
|
|
9
5
|
"DIV",
|
|
@@ -20,9 +16,6 @@ const BLOCK_ELEMENTS = new Set([
|
|
|
20
16
|
"BR",
|
|
21
17
|
]);
|
|
22
18
|
|
|
23
|
-
/**
|
|
24
|
-
* Find the closest block-level ancestor of a node.
|
|
25
|
-
*/
|
|
26
19
|
function findBlockParent(node: Node): Element | null {
|
|
27
20
|
let parent = node.parentElement;
|
|
28
21
|
while (parent && !BLOCK_ELEMENTS.has(parent.tagName)) {
|
|
@@ -31,10 +24,7 @@ function findBlockParent(node: Node): Element | null {
|
|
|
31
24
|
return parent;
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
/**
|
|
35
|
-
* Calculate text offset from root to a specific node position.
|
|
36
|
-
* Accounts for newlines between block elements to match getDOMTextContent.
|
|
37
|
-
*/
|
|
27
|
+
/** Accounts for newlines between block elements to match getDOMTextContent. */
|
|
38
28
|
export function getTextOffset(
|
|
39
29
|
root: Node,
|
|
40
30
|
targetNode: Node,
|
|
@@ -48,13 +38,12 @@ export function getTextOffset(
|
|
|
48
38
|
while (node) {
|
|
49
39
|
const blockParent = findBlockParent(node);
|
|
50
40
|
|
|
51
|
-
// Add newline when transitioning between different block parents
|
|
52
41
|
if (lastBlockParent && blockParent && lastBlockParent !== blockParent) {
|
|
53
42
|
if (
|
|
54
43
|
!lastBlockParent.contains(blockParent) &&
|
|
55
44
|
!blockParent.contains(lastBlockParent)
|
|
56
45
|
) {
|
|
57
|
-
offset += 1;
|
|
46
|
+
offset += 1;
|
|
58
47
|
}
|
|
59
48
|
}
|
|
60
49
|
|
|
@@ -69,10 +58,7 @@ export function getTextOffset(
|
|
|
69
58
|
return offset;
|
|
70
59
|
}
|
|
71
60
|
|
|
72
|
-
/**
|
|
73
|
-
* Extract all text content from a DOM tree.
|
|
74
|
-
* Inserts newlines between block-level elements to match browser selection behavior.
|
|
75
|
-
*/
|
|
61
|
+
/** Inserts newlines between block-level elements to match browser selection behavior. */
|
|
76
62
|
export function getDOMTextContent(root: Node): string {
|
|
77
63
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
78
64
|
let text = "";
|
|
@@ -82,7 +68,6 @@ export function getDOMTextContent(root: Node): string {
|
|
|
82
68
|
while (node) {
|
|
83
69
|
const blockParent = findBlockParent(node);
|
|
84
70
|
|
|
85
|
-
// Insert newline when transitioning between different block parents
|
|
86
71
|
if (lastBlockParent && blockParent && lastBlockParent !== blockParent) {
|
|
87
72
|
// Only add newline if blocks are siblings (not nested)
|
|
88
73
|
if (
|
|
@@ -101,10 +86,6 @@ export function getDOMTextContent(root: Node): string {
|
|
|
101
86
|
return text;
|
|
102
87
|
}
|
|
103
88
|
|
|
104
|
-
/**
|
|
105
|
-
* Collect all text nodes with their cumulative offset ranges.
|
|
106
|
-
* Accounts for newlines between block elements to match getDOMTextContent.
|
|
107
|
-
*/
|
|
108
89
|
export function collectTextNodes(root: Node): TextNodeInfo[] {
|
|
109
90
|
const textNodes: TextNodeInfo[] = [];
|
|
110
91
|
let currentOffset = 0;
|
|
@@ -116,14 +97,12 @@ export function collectTextNodes(root: Node): TextNodeInfo[] {
|
|
|
116
97
|
while (node) {
|
|
117
98
|
const blockParent = findBlockParent(node);
|
|
118
99
|
|
|
119
|
-
// Account for newline when transitioning between different block parents
|
|
120
|
-
// (same logic as getDOMTextContent)
|
|
121
100
|
if (lastBlockParent && blockParent && lastBlockParent !== blockParent) {
|
|
122
101
|
if (
|
|
123
102
|
!lastBlockParent.contains(blockParent) &&
|
|
124
103
|
!blockParent.contains(lastBlockParent)
|
|
125
104
|
) {
|
|
126
|
-
currentOffset += 1;
|
|
105
|
+
currentOffset += 1;
|
|
127
106
|
}
|
|
128
107
|
}
|
|
129
108
|
|
|
@@ -141,9 +120,6 @@ export function collectTextNodes(root: Node): TextNodeInfo[] {
|
|
|
141
120
|
return textNodes;
|
|
142
121
|
}
|
|
143
122
|
|
|
144
|
-
/**
|
|
145
|
-
* Extended style configuration for highlight marks with color and bracket mode support.
|
|
146
|
-
*/
|
|
147
123
|
export interface ExtendedHighlightStyle extends HighlightStyle {
|
|
148
124
|
colorIndex?: number;
|
|
149
125
|
isBracketMode?: boolean;
|
|
@@ -164,9 +140,6 @@ interface NodeSegment {
|
|
|
164
140
|
order: number;
|
|
165
141
|
}
|
|
166
142
|
|
|
167
|
-
/**
|
|
168
|
-
* Line threshold for bracket mode (selections spanning this many lines or more)
|
|
169
|
-
*/
|
|
170
143
|
const BRACKET_MODE_LINE_THRESHOLD = 5;
|
|
171
144
|
|
|
172
145
|
export function countLinesInRange(
|
|
@@ -183,9 +156,6 @@ export function countLinesInRange(
|
|
|
183
156
|
// applyHighlightWithStyle adds color indices and bracket mode for saved comments.
|
|
184
157
|
// Keeping them separate avoids unnecessary complexity in a shared abstraction.
|
|
185
158
|
|
|
186
|
-
/**
|
|
187
|
-
* Apply highlight mark elements to a text range (for pending selections).
|
|
188
|
-
*/
|
|
189
159
|
export function applyHighlightToRange(
|
|
190
160
|
root: HTMLElement,
|
|
191
161
|
startOffset: number,
|
|
@@ -225,7 +195,6 @@ export function applyHighlightToRange(
|
|
|
225
195
|
mark.appendChild(fragment);
|
|
226
196
|
range.insertNode(mark);
|
|
227
197
|
} catch (err) {
|
|
228
|
-
// Skip if fallback also fails, but log for debugging
|
|
229
198
|
console.warn("[highlight] Failed to apply highlight to range:", err);
|
|
230
199
|
}
|
|
231
200
|
}
|
|
@@ -380,35 +349,3 @@ export function clearHighlights(
|
|
|
380
349
|
}
|
|
381
350
|
}
|
|
382
351
|
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Collect highlight positions relative to a container and document.
|
|
386
|
-
*/
|
|
387
|
-
export function collectHighlightPositions(
|
|
388
|
-
root: HTMLElement,
|
|
389
|
-
containerRect: DOMRect,
|
|
390
|
-
scrollY = 0,
|
|
391
|
-
): HighlightPositions {
|
|
392
|
-
const positions: Record<string, number> = {};
|
|
393
|
-
const documentPositions: Record<string, number> = {};
|
|
394
|
-
|
|
395
|
-
// Collect comment highlight positions
|
|
396
|
-
const marks = root.querySelectorAll("mark[data-comment-id]");
|
|
397
|
-
for (const mark of marks) {
|
|
398
|
-
const commentId = mark.getAttribute("data-comment-id");
|
|
399
|
-
if (!commentId) continue;
|
|
400
|
-
|
|
401
|
-
// Get position relative to container (for margin notes)
|
|
402
|
-
const markRect = mark.getBoundingClientRect();
|
|
403
|
-
const relativeTop = markRect.top - containerRect.top;
|
|
404
|
-
|
|
405
|
-
// Use first occurrence of each comment id
|
|
406
|
-
if (!(commentId in positions)) {
|
|
407
|
-
positions[commentId] = relativeTop;
|
|
408
|
-
// Document-absolute position (for minimap)
|
|
409
|
-
documentPositions[commentId] = markRect.top + scrollY;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return { positions, documentPositions };
|
|
414
|
-
}
|