@peaske7/readit 0.1.8 → 0.2.1
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/.claude/CLAUDE.md +118 -76
- package/.claude/commands/review.md +1 -1
- package/.claude/roadmap.md +32 -9
- package/.claude/user-stories.md +100 -15
- package/AGENTS.md +30 -26
- package/Makefile +32 -0
- package/README.md +90 -5
- package/biome.json +18 -8
- package/bun.lock +426 -710
- package/bunfig.toml +2 -0
- package/docs/perf-baseline.md +130 -0
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
- package/e2e/comments.spec.ts +14 -58
- package/e2e/document-load.spec.ts +1 -23
- package/e2e/export.spec.ts +4 -4
- package/e2e/perf/add-comment.spec.ts +116 -0
- package/e2e/perf/fixtures/generate.ts +327 -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/screenshot-final.png +0 -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 +350 -0
- package/e2e/perf/utils/perf-cli.ts +86 -0
- package/e2e/persistence-file.spec.ts +41 -26
- package/e2e/utils/selection.ts +17 -73
- package/go/cmd/readit/main.go +416 -0
- package/go/go.mod +20 -0
- package/go/go.sum +41 -0
- package/go/internal/server/anchor.go +302 -0
- package/go/internal/server/anchor_test.go +111 -0
- package/go/internal/server/comments.go +390 -0
- package/go/internal/server/documents.go +113 -0
- package/go/internal/server/embed.go +17 -0
- package/go/internal/server/headings.go +33 -0
- package/go/internal/server/headings_test.go +75 -0
- package/go/internal/server/htmltext.go +123 -0
- package/go/internal/server/markdown.go +157 -0
- package/go/internal/server/markdown_bench_test.go +42 -0
- package/go/internal/server/markdown_test.go +79 -0
- package/go/internal/server/server.go +453 -0
- package/go/internal/server/server_bench_test.go +122 -0
- package/go/internal/server/settings.go +110 -0
- package/go/internal/server/sse.go +140 -0
- package/go/internal/server/storage.go +275 -0
- package/go/internal/server/storage_test.go +118 -0
- package/go/internal/server/template.go +66 -0
- package/go/internal/server/types.go +101 -0
- package/go/internal/server/watcher.go +74 -0
- package/index.html +4 -14
- package/nvim-readit/lua/readit/health.lua +64 -0
- package/nvim-readit/lua/readit/init.lua +463 -0
- package/nvim-readit/plugin/readit.lua +19 -0
- package/package.json +24 -41
- package/playwright.config.ts +12 -0
- package/shell/_readit +158 -0
- package/shell/readit.zsh +87 -0
- package/src/App.svelte +881 -0
- package/src/{cli/index.ts → cli.ts} +216 -70
- package/src/components/ActionsMenu.svelte +95 -0
- package/src/components/CommentBadge.svelte +67 -0
- package/src/components/CommentErrorBanner.svelte +33 -0
- package/src/components/CommentInput.svelte +75 -0
- package/src/components/CommentListItem.svelte +95 -0
- package/src/components/CommentManager.svelte +129 -0
- package/src/components/CommentNav.svelte +109 -0
- package/src/components/DocumentViewer.svelte +218 -0
- package/src/components/FloatingComment.svelte +107 -0
- package/src/components/Header.svelte +76 -0
- package/src/components/InlineEditor.svelte +72 -0
- package/src/components/MarginNote.svelte +167 -0
- package/src/components/MarginNotesContainer.svelte +33 -0
- package/src/components/RawModal.svelte +126 -0
- package/src/components/ReanchorConfirm.svelte +30 -0
- package/src/components/SettingsModal.svelte +220 -0
- package/src/components/ShortcutCapture.svelte +82 -0
- package/src/components/ShortcutList.svelte +145 -0
- package/src/components/TabBar.svelte +52 -0
- package/src/components/TableOfContents.svelte +125 -0
- package/src/components/ui/ActionLink.svelte +40 -0
- package/src/components/ui/Button.svelte +53 -0
- package/src/components/ui/Dialog.svelte +97 -0
- package/src/components/ui/DropdownMenu.svelte +85 -0
- package/src/components/ui/DropdownMenuItem.svelte +38 -0
- package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
- package/src/components/ui/Text.svelte +42 -0
- package/src/env.d.ts +6 -0
- package/src/index.css +36 -166
- package/src/lib/__fixtures__/bench-data.ts +1 -54
- package/src/lib/anchor.bench.ts +47 -68
- package/src/lib/anchor.test.ts +5 -9
- package/src/lib/anchor.ts +9 -93
- package/src/lib/comment-storage.bench.ts +6 -20
- package/src/lib/comment-storage.test.ts +45 -37
- package/src/lib/comment-storage.ts +23 -64
- package/src/lib/export.bench.ts +9 -23
- package/src/lib/export.ts +7 -14
- package/src/lib/headings.test.ts +103 -0
- package/src/lib/headings.ts +44 -0
- package/src/lib/highlight/core.test.ts +1 -6
- package/src/lib/highlight/dom.ts +53 -280
- package/src/lib/highlight/highlight-registry.ts +221 -0
- package/src/lib/highlight/highlight.bench.ts +92 -0
- package/src/lib/highlight/highlighter.ts +122 -302
- package/src/lib/highlight/{core.ts → resolver.ts} +3 -19
- package/src/lib/highlight/types.ts +0 -40
- package/src/lib/html-text.test.ts +162 -0
- package/src/lib/html-text.ts +161 -0
- package/src/lib/i18n/en.ts +13 -36
- package/src/lib/i18n/ja.ts +14 -37
- package/src/lib/i18n/types.ts +13 -36
- package/src/lib/margin-layout.bench.ts +48 -15
- package/src/lib/margin-layout.ts +2 -31
- package/src/lib/markdown-renderer.test.ts +154 -0
- package/src/lib/markdown-renderer.ts +177 -0
- package/src/lib/mermaid-config.ts +38 -0
- package/src/lib/mermaid-renderer.ts +162 -0
- package/src/lib/mermaid-worker.ts +60 -0
- package/src/lib/positions.ts +157 -0
- package/src/lib/shortcut-registry.ts +138 -103
- package/src/lib/utils.ts +2 -48
- package/src/main.ts +16 -0
- package/src/schema.ts +92 -0
- package/src/{server/index.ts → server.ts} +427 -163
- package/src/stores/app.svelte.ts +231 -0
- package/src/stores/locale.svelte.ts +46 -0
- package/src/stores/settings.svelte.ts +90 -0
- package/src/stores/shortcuts.svelte.ts +104 -0
- package/src/stores/ui.svelte.ts +12 -0
- package/src/template.ts +104 -0
- package/src/test-setup.ts +47 -0
- package/svelte.config.js +5 -0
- package/tsconfig.json +2 -2
- package/vite.config.ts +31 -3
- package/vscode-readit/.mcp.json +7 -0
- package/vscode-readit/.vscodeignore +7 -0
- package/vscode-readit/bun.lock +78 -0
- package/vscode-readit/icon.svg +10 -0
- package/vscode-readit/package.json +110 -0
- package/vscode-readit/src/extension.ts +117 -0
- package/vscode-readit/src/server-manager.ts +272 -0
- package/vscode-readit/src/webview-provider.ts +204 -0
- package/vscode-readit/tsconfig.json +20 -0
- package/e2e/fixtures/sample.html +0 -13
- package/src/App.tsx +0 -416
- package/src/components/ActionsMenu.tsx +0 -112
- package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
- package/src/components/DocumentViewer/DocumentViewer.tsx +0 -259
- package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
- package/src/components/DocumentViewer/InlineCode.tsx +0 -60
- package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -137
- package/src/components/DocumentViewer/index.ts +0 -1
- package/src/components/FloatingTOC.tsx +0 -61
- package/src/components/Header.tsx +0 -65
- package/src/components/InlineEditor.tsx +0 -74
- package/src/components/MarginNote.tsx +0 -207
- package/src/components/MarginNotes.tsx +0 -50
- package/src/components/RawModal.tsx +0 -143
- package/src/components/ReanchorConfirm.tsx +0 -36
- package/src/components/SettingsModal.tsx +0 -310
- package/src/components/ShortcutCapture.tsx +0 -48
- package/src/components/ShortcutList.tsx +0 -198
- package/src/components/TabBar.tsx +0 -60
- package/src/components/TableOfContents.tsx +0 -108
- package/src/components/comments/CommentBadge.tsx +0 -49
- package/src/components/comments/CommentInput.tsx +0 -114
- package/src/components/comments/CommentListItem.tsx +0 -92
- package/src/components/comments/CommentManager.tsx +0 -113
- package/src/components/comments/CommentMinimap.tsx +0 -62
- package/src/components/comments/CommentNav.tsx +0 -109
- package/src/components/ui/ActionBar.tsx +0 -16
- package/src/components/ui/ActionLink.tsx +0 -32
- package/src/components/ui/Button.tsx +0 -55
- package/src/components/ui/Dialog.tsx +0 -156
- package/src/components/ui/DropdownMenu.tsx +0 -114
- package/src/components/ui/SeparatorDot.tsx +0 -9
- package/src/components/ui/Text.tsx +0 -54
- package/src/contexts/CommentContext.tsx +0 -229
- package/src/contexts/LayoutContext.tsx +0 -88
- package/src/contexts/LocaleContext.tsx +0 -35
- package/src/hooks/useClickOutside.ts +0 -35
- package/src/hooks/useClipboard.ts +0 -82
- package/src/hooks/useCommentNavigation.ts +0 -130
- package/src/hooks/useComments.ts +0 -323
- package/src/hooks/useDocument.ts +0 -156
- package/src/hooks/useEditorScheme.ts +0 -51
- package/src/hooks/useFontPreference.ts +0 -59
- package/src/hooks/useHeadings.test.ts +0 -159
- package/src/hooks/useHeadings.ts +0 -129
- 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/useScrollSpy.ts +0 -81
- package/src/hooks/useTextSelection.ts +0 -123
- package/src/hooks/useThemePreference.ts +0 -66
- 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/highlight/colors.ts +0 -37
- 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/scroll.test.ts +0 -118
- package/src/lib/scroll.ts +0 -47
- package/src/lib/shortcut-registry.test.ts +0 -173
- package/src/lib/utils.test.ts +0 -110
- package/src/main.tsx +0 -13
- package/src/store/index.test.ts +0 -242
- package/src/store/index.ts +0 -254
- package/src/types/index.ts +0 -127
|
@@ -1,34 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the highlighting system.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Style configuration for highlight marks.
|
|
7
|
-
*/
|
|
8
|
-
export interface HighlightStyle {
|
|
9
|
-
attribute: string;
|
|
10
|
-
attributeValue: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
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
1
|
export interface TextPosition {
|
|
25
2
|
start: number;
|
|
26
3
|
end: number;
|
|
27
4
|
}
|
|
28
5
|
|
|
29
|
-
/**
|
|
30
|
-
* Comment data needed for highlighting (subset of full Comment type).
|
|
31
|
-
*/
|
|
32
6
|
export interface HighlightComment {
|
|
33
7
|
id: string;
|
|
34
8
|
selectedText: string;
|
|
@@ -36,20 +10,6 @@ export interface HighlightComment {
|
|
|
36
10
|
endOffset: number;
|
|
37
11
|
}
|
|
38
12
|
|
|
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
13
|
export interface TextNodeInfo {
|
|
54
14
|
node: Text;
|
|
55
15
|
start: number;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { JSDOM } from "jsdom";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { getDOMTextContent } from "./highlight/dom";
|
|
4
|
+
import { extractTextFromHtml } from "./html-text";
|
|
5
|
+
import { renderMarkdown } from "./markdown-renderer";
|
|
6
|
+
|
|
7
|
+
const DOM_GLOBALS = ["document", "NodeFilter"] as const;
|
|
8
|
+
|
|
9
|
+
function browserExtract(html: string): string {
|
|
10
|
+
const dom = new JSDOM(
|
|
11
|
+
`<!DOCTYPE html><body><article>${html}</article></body>`,
|
|
12
|
+
);
|
|
13
|
+
const saved = Object.fromEntries(
|
|
14
|
+
DOM_GLOBALS.map((k) => [k, (globalThis as Record<string, unknown>)[k]]),
|
|
15
|
+
);
|
|
16
|
+
try {
|
|
17
|
+
for (const k of DOM_GLOBALS) {
|
|
18
|
+
(globalThis as Record<string, unknown>)[k] = dom.window[k];
|
|
19
|
+
}
|
|
20
|
+
const container = dom.window.document.querySelector("article")!;
|
|
21
|
+
return getDOMTextContent(container);
|
|
22
|
+
} finally {
|
|
23
|
+
for (const k of DOM_GLOBALS) {
|
|
24
|
+
if (saved[k] !== undefined) {
|
|
25
|
+
(globalThis as Record<string, unknown>)[k] = saved[k];
|
|
26
|
+
} else {
|
|
27
|
+
delete (globalThis as Record<string, unknown>)[k];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("extractTextFromHtml", () => {
|
|
34
|
+
it("extracts plain text from paragraphs", () => {
|
|
35
|
+
const html = "<p>Hello world</p><p>Second paragraph</p>";
|
|
36
|
+
expect(extractTextFromHtml(html)).toBe("Hello world\nSecond paragraph");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("handles headings", () => {
|
|
40
|
+
const html = "<h1>Title</h1><p>Content</p>";
|
|
41
|
+
expect(extractTextFromHtml(html)).toBe("Title\nContent");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("handles nested block elements (no extra newline)", () => {
|
|
45
|
+
const html = "<blockquote><p>Quoted text</p></blockquote><p>After</p>";
|
|
46
|
+
const result = extractTextFromHtml(html);
|
|
47
|
+
expect(result).toContain("Quoted text");
|
|
48
|
+
expect(result).toContain("After");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("handles lists", () => {
|
|
52
|
+
const html = "<ul><li>Item 1</li><li>Item 2</li></ul>";
|
|
53
|
+
expect(extractTextFromHtml(html)).toBe("Item 1\nItem 2");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("handles inline elements within blocks", () => {
|
|
57
|
+
const html = "<p>Text with <strong>bold</strong> and <em>italic</em></p>";
|
|
58
|
+
expect(extractTextFromHtml(html)).toBe("Text with bold and italic");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("decodes HTML entities", () => {
|
|
62
|
+
const html = "<p><div> & "quotes"</p>";
|
|
63
|
+
expect(extractTextFromHtml(html)).toBe('<div> & "quotes"');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("handles code blocks", () => {
|
|
67
|
+
const html =
|
|
68
|
+
'<pre><code>function hello() {\n return "world";\n}</code></pre>';
|
|
69
|
+
expect(extractTextFromHtml(html)).toContain("function hello()");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("extractTextFromHtml conformance with getDOMTextContent", () => {
|
|
74
|
+
it("matches browser extraction for simple markdown", async () => {
|
|
75
|
+
const md = `# Hello
|
|
76
|
+
|
|
77
|
+
This is a paragraph.
|
|
78
|
+
|
|
79
|
+
## Section
|
|
80
|
+
|
|
81
|
+
Another paragraph here.
|
|
82
|
+
`;
|
|
83
|
+
const { html } = await renderMarkdown(md);
|
|
84
|
+
const serverText = extractTextFromHtml(html);
|
|
85
|
+
const browserText = browserExtract(html);
|
|
86
|
+
expect(serverText).toBe(browserText);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("matches browser extraction for lists", async () => {
|
|
90
|
+
const md = `- Item 1
|
|
91
|
+
- Item 2
|
|
92
|
+
- Item 3
|
|
93
|
+
`;
|
|
94
|
+
const { html } = await renderMarkdown(md);
|
|
95
|
+
const serverText = extractTextFromHtml(html);
|
|
96
|
+
const browserText = browserExtract(html);
|
|
97
|
+
expect(serverText).toBe(browserText);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("matches browser extraction for code blocks", async () => {
|
|
101
|
+
const md = `# Code
|
|
102
|
+
|
|
103
|
+
\`\`\`typescript
|
|
104
|
+
function hello() {
|
|
105
|
+
return "world";
|
|
106
|
+
}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
After code.
|
|
110
|
+
`;
|
|
111
|
+
const { html } = await renderMarkdown(md);
|
|
112
|
+
const serverText = extractTextFromHtml(html);
|
|
113
|
+
const browserText = browserExtract(html);
|
|
114
|
+
expect(serverText).toBe(browserText);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("matches browser extraction for tables", async () => {
|
|
118
|
+
const md = `| A | B |
|
|
119
|
+
|---|---|
|
|
120
|
+
| 1 | 2 |
|
|
121
|
+
| 3 | 4 |
|
|
122
|
+
`;
|
|
123
|
+
const { html } = await renderMarkdown(md);
|
|
124
|
+
const serverText = extractTextFromHtml(html);
|
|
125
|
+
const browserText = browserExtract(html);
|
|
126
|
+
expect(serverText).toBe(browserText);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("matches browser extraction for complex document", async () => {
|
|
130
|
+
const md = `# Performance Test Document
|
|
131
|
+
|
|
132
|
+
This section covers topic 1 in detail. It contains various formatting including **bold**, *italic*, and \`inline code\`.
|
|
133
|
+
|
|
134
|
+
## Section 2
|
|
135
|
+
|
|
136
|
+
- Item 1 in section 2
|
|
137
|
+
- Item 2 in section 2
|
|
138
|
+
- Item 3 in section 2
|
|
139
|
+
|
|
140
|
+
\`\`\`typescript
|
|
141
|
+
function section2() {
|
|
142
|
+
const value = 2 * 42;
|
|
143
|
+
return "result from section 2: " + value;
|
|
144
|
+
}
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
The conclusion of section 2 summarizes the key findings.
|
|
148
|
+
|
|
149
|
+
| Column A | Column B | Column C |
|
|
150
|
+
|----------|----------|----------|
|
|
151
|
+
| Cell 1 | Cell 2 | Cell 3 |
|
|
152
|
+
|
|
153
|
+
> A blockquote with some text.
|
|
154
|
+
|
|
155
|
+
Final paragraph.
|
|
156
|
+
`;
|
|
157
|
+
const { html } = await renderMarkdown(md);
|
|
158
|
+
const serverText = extractTextFromHtml(html);
|
|
159
|
+
const browserText = browserExtract(html);
|
|
160
|
+
expect(serverText).toBe(browserText);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const BLOCK_ELEMENTS = new Set([
|
|
2
|
+
"p",
|
|
3
|
+
"div",
|
|
4
|
+
"h1",
|
|
5
|
+
"h2",
|
|
6
|
+
"h3",
|
|
7
|
+
"h4",
|
|
8
|
+
"h5",
|
|
9
|
+
"h6",
|
|
10
|
+
"pre",
|
|
11
|
+
"blockquote",
|
|
12
|
+
"li",
|
|
13
|
+
"tr",
|
|
14
|
+
"br",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
interface TextNode {
|
|
18
|
+
text: string;
|
|
19
|
+
blockAncestorPath: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function extractTextFromHtml(html: string): string {
|
|
23
|
+
const textNodes = collectTextNodesFromHtml(html);
|
|
24
|
+
if (textNodes.length === 0) return "";
|
|
25
|
+
|
|
26
|
+
let result = "";
|
|
27
|
+
let lastBlockPath: string[] | null = null;
|
|
28
|
+
|
|
29
|
+
for (const node of textNodes) {
|
|
30
|
+
if (lastBlockPath) {
|
|
31
|
+
const lastBlock = lastBlockPath;
|
|
32
|
+
const currBlock = node.blockAncestorPath;
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
lastBlock.length > 0 &&
|
|
36
|
+
currBlock.length > 0 &&
|
|
37
|
+
!isNested(lastBlock, currBlock)
|
|
38
|
+
) {
|
|
39
|
+
if (
|
|
40
|
+
lastBlock[lastBlock.length - 1] !== currBlock[currBlock.length - 1]
|
|
41
|
+
) {
|
|
42
|
+
result += "\n";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
result += node.text;
|
|
48
|
+
lastBlockPath = node.blockAncestorPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isNested(pathA: string[], pathB: string[]): boolean {
|
|
55
|
+
const blockA = pathA[pathA.length - 1];
|
|
56
|
+
const blockB = pathB[pathB.length - 1];
|
|
57
|
+
|
|
58
|
+
if (pathB.includes(blockA)) return true;
|
|
59
|
+
if (pathA.includes(blockB)) return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function collectTextNodesFromHtml(html: string): TextNode[] {
|
|
64
|
+
const nodes: TextNode[] = [];
|
|
65
|
+
const stack: { tag: string; id: string }[] = [];
|
|
66
|
+
let idCounter = 0;
|
|
67
|
+
|
|
68
|
+
const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*\/?>/g;
|
|
69
|
+
let lastIndex = 0;
|
|
70
|
+
let match: RegExpExecArray | null;
|
|
71
|
+
|
|
72
|
+
match = tagPattern.exec(html);
|
|
73
|
+
while (match !== null) {
|
|
74
|
+
if (match.index > lastIndex) {
|
|
75
|
+
const text = decodeEntities(html.slice(lastIndex, match.index));
|
|
76
|
+
if (text) {
|
|
77
|
+
nodes.push({
|
|
78
|
+
text,
|
|
79
|
+
blockAncestorPath: getBlockAncestorPath(stack),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const fullTag = match[0];
|
|
85
|
+
const tagName = match[1].toLowerCase();
|
|
86
|
+
const isClosing = fullTag.startsWith("</");
|
|
87
|
+
const isSelfClosing = fullTag.endsWith("/>") || isVoidElement(tagName);
|
|
88
|
+
|
|
89
|
+
if (isClosing) {
|
|
90
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
91
|
+
if (stack[i].tag === tagName) {
|
|
92
|
+
stack.splice(i);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (!isSelfClosing) {
|
|
97
|
+
stack.push({ tag: tagName, id: `e${idCounter++}` });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
lastIndex = match.index + fullTag.length;
|
|
101
|
+
match = tagPattern.exec(html);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (lastIndex < html.length) {
|
|
105
|
+
const text = decodeEntities(html.slice(lastIndex));
|
|
106
|
+
if (text) {
|
|
107
|
+
nodes.push({
|
|
108
|
+
text,
|
|
109
|
+
blockAncestorPath: getBlockAncestorPath(stack),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return nodes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getBlockAncestorPath(stack: { tag: string; id: string }[]): string[] {
|
|
118
|
+
const path: string[] = [];
|
|
119
|
+
for (const entry of stack) {
|
|
120
|
+
if (BLOCK_ELEMENTS.has(entry.tag)) {
|
|
121
|
+
path.push(entry.id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return path;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const VOID_ELEMENTS = new Set([
|
|
128
|
+
"area",
|
|
129
|
+
"base",
|
|
130
|
+
"br",
|
|
131
|
+
"col",
|
|
132
|
+
"embed",
|
|
133
|
+
"hr",
|
|
134
|
+
"img",
|
|
135
|
+
"input",
|
|
136
|
+
"link",
|
|
137
|
+
"meta",
|
|
138
|
+
"param",
|
|
139
|
+
"source",
|
|
140
|
+
"track",
|
|
141
|
+
"wbr",
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
function isVoidElement(tag: string): boolean {
|
|
145
|
+
return VOID_ELEMENTS.has(tag);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function decodeEntities(text: string): string {
|
|
149
|
+
return text
|
|
150
|
+
.replace(/&/g, "&")
|
|
151
|
+
.replace(/</g, "<")
|
|
152
|
+
.replace(/>/g, ">")
|
|
153
|
+
.replace(/"/g, '"')
|
|
154
|
+
.replace(/'/g, "'")
|
|
155
|
+
.replace(/'/g, "'")
|
|
156
|
+
.replace(/ /g, "\u00A0")
|
|
157
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
|
|
158
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) =>
|
|
159
|
+
String.fromCharCode(Number.parseInt(hex, 16)),
|
|
160
|
+
);
|
|
161
|
+
}
|
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,15 +80,24 @@ export const en: Translations = {
|
|
|
99
80
|
"rawModal.copiedToClipboard": "Copied to clipboard",
|
|
100
81
|
"rawModal.failedToCopy": "Failed to copy",
|
|
101
82
|
|
|
102
|
-
//
|
|
83
|
+
// Toast messages
|
|
84
|
+
"toast.copied": 'Copied: "{{text}}"',
|
|
85
|
+
"toast.copiedAllComments": "Copied all comments",
|
|
86
|
+
|
|
87
|
+
// Comment badge
|
|
88
|
+
"commentBadge.title": "{{count}} comment",
|
|
89
|
+
"commentBadge.titlePlural": "{{count}} comments",
|
|
90
|
+
|
|
91
|
+
// Keyboard shortcuts
|
|
92
|
+
"shortcuts.title": "Keyboard Shortcuts",
|
|
103
93
|
"shortcutGroup.copy": "Copy",
|
|
104
94
|
"shortcutGroup.navigate": "Navigate",
|
|
105
95
|
"shortcutGroup.other": "Other",
|
|
106
96
|
"shortcuts.resetToDefaults": "Reset to defaults",
|
|
107
97
|
"shortcuts.enableDisable": "Enable/disable shortcut",
|
|
108
98
|
"shortcutCapture.pressKeys": "Press keys...",
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
"shortcutCapture.reserved": "{{binding}} is reserved",
|
|
100
|
+
"shortcutCapture.ariaLabel": "Press a key combination",
|
|
111
101
|
"shortcut.copyAll.label": "Copy All (AI)",
|
|
112
102
|
"shortcut.copyAll.description": "Copy all comments in AI prompt format",
|
|
113
103
|
"shortcut.copyAllRaw.label": "Copy All (Raw)",
|
|
@@ -123,17 +113,4 @@ export const en: Translations = {
|
|
|
123
113
|
"Copy selected text with context for LLM",
|
|
124
114
|
"shortcut.clearSelection.label": "Clear Selection",
|
|
125
115
|
"shortcut.clearSelection.description": "Clear text selection",
|
|
126
|
-
|
|
127
|
-
// Toast messages
|
|
128
|
-
"toast.copied": 'Copied: "{{text}}"',
|
|
129
|
-
"toast.copiedForLLM": 'Copied for LLM: "{{text}}"',
|
|
130
|
-
"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
|
-
|
|
136
|
-
// Comment badge
|
|
137
|
-
"commentBadge.title": "{{count}} comment",
|
|
138
|
-
"commentBadge.titlePlural": "{{count}} comments",
|
|
139
116
|
};
|
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,15 +82,24 @@ export const ja: Translations = {
|
|
|
101
82
|
"rawModal.copiedToClipboard": "クリップボードにコピーしました",
|
|
102
83
|
"rawModal.failedToCopy": "コピーに失敗しました",
|
|
103
84
|
|
|
104
|
-
//
|
|
85
|
+
// Toast messages
|
|
86
|
+
"toast.copied": 'コピーしました: "{{text}}"',
|
|
87
|
+
"toast.copiedAllComments": "全てのコメントをコピーしました",
|
|
88
|
+
|
|
89
|
+
// Comment badge
|
|
90
|
+
"commentBadge.title": "{{count}}件のコメント",
|
|
91
|
+
"commentBadge.titlePlural": "{{count}}件のコメント",
|
|
92
|
+
|
|
93
|
+
// Keyboard shortcuts
|
|
94
|
+
"shortcuts.title": "キーボードショートカット",
|
|
105
95
|
"shortcutGroup.copy": "コピー",
|
|
106
96
|
"shortcutGroup.navigate": "ナビゲーション",
|
|
107
97
|
"shortcutGroup.other": "その他",
|
|
108
98
|
"shortcuts.resetToDefaults": "初期設定に戻す",
|
|
109
99
|
"shortcuts.enableDisable": "ショートカットの有効/無効",
|
|
110
100
|
"shortcutCapture.pressKeys": "キーを入力...",
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
"shortcutCapture.reserved": "{{binding}} は予約されています",
|
|
102
|
+
"shortcutCapture.ariaLabel": "キーの組み合わせを入力",
|
|
113
103
|
"shortcut.copyAll.label": "全てコピー (AI)",
|
|
114
104
|
"shortcut.copyAll.description": "全コメントをAIプロンプト形式でコピー",
|
|
115
105
|
"shortcut.copyAllRaw.label": "全てコピー (テキスト)",
|
|
@@ -122,20 +112,7 @@ export const ja: Translations = {
|
|
|
122
112
|
"shortcut.copySelectionRaw.description": "選択テキストをコピー",
|
|
123
113
|
"shortcut.copySelectionLLM.label": "選択をコピー (LLM)",
|
|
124
114
|
"shortcut.copySelectionLLM.description":
|
|
125
|
-
"選択テキストをLLM
|
|
115
|
+
"選択テキストをLLMコンテキスト付きでコピー",
|
|
126
116
|
"shortcut.clearSelection.label": "選択を解除",
|
|
127
117
|
"shortcut.clearSelection.description": "テキスト選択を解除",
|
|
128
|
-
|
|
129
|
-
// Toast messages
|
|
130
|
-
"toast.copied": 'コピーしました: "{{text}}"',
|
|
131
|
-
"toast.copiedForLLM": 'LLM用にコピーしました: "{{text}}"',
|
|
132
|
-
"toast.copiedAllComments": "全てのコメントをコピーしました",
|
|
133
|
-
"toast.copiedAllRaw": "全てのコメントをテキストとしてコピーしました",
|
|
134
|
-
|
|
135
|
-
// Floating TOC
|
|
136
|
-
"floatingTOC.label": "目次",
|
|
137
|
-
|
|
138
|
-
// Comment badge
|
|
139
|
-
"commentBadge.title": "{{count}}件のコメント",
|
|
140
|
-
"commentBadge.titlePlural": "{{count}}件のコメント",
|
|
141
118
|
};
|
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,15 +85,24 @@ export interface Translations {
|
|
|
104
85
|
"rawModal.copiedToClipboard": string;
|
|
105
86
|
"rawModal.failedToCopy": string;
|
|
106
87
|
|
|
107
|
-
//
|
|
88
|
+
// Toast messages
|
|
89
|
+
"toast.copied": string;
|
|
90
|
+
"toast.copiedAllComments": string;
|
|
91
|
+
|
|
92
|
+
// Comment badge
|
|
93
|
+
"commentBadge.title": string;
|
|
94
|
+
"commentBadge.titlePlural": string;
|
|
95
|
+
|
|
96
|
+
// Keyboard shortcuts
|
|
97
|
+
"shortcuts.title": string;
|
|
108
98
|
"shortcutGroup.copy": string;
|
|
109
99
|
"shortcutGroup.navigate": string;
|
|
110
100
|
"shortcutGroup.other": string;
|
|
111
101
|
"shortcuts.resetToDefaults": string;
|
|
112
102
|
"shortcuts.enableDisable": string;
|
|
113
103
|
"shortcutCapture.pressKeys": string;
|
|
114
|
-
|
|
115
|
-
|
|
104
|
+
"shortcutCapture.reserved": string;
|
|
105
|
+
"shortcutCapture.ariaLabel": string;
|
|
116
106
|
"shortcut.copyAll.label": string;
|
|
117
107
|
"shortcut.copyAll.description": string;
|
|
118
108
|
"shortcut.copyAllRaw.label": string;
|
|
@@ -127,19 +117,6 @@ export interface Translations {
|
|
|
127
117
|
"shortcut.copySelectionLLM.description": string;
|
|
128
118
|
"shortcut.clearSelection.label": string;
|
|
129
119
|
"shortcut.clearSelection.description": string;
|
|
130
|
-
|
|
131
|
-
// Toast messages
|
|
132
|
-
"toast.copied": string;
|
|
133
|
-
"toast.copiedForLLM": string;
|
|
134
|
-
"toast.copiedAllComments": string;
|
|
135
|
-
"toast.copiedAllRaw": string;
|
|
136
|
-
|
|
137
|
-
// Floating TOC
|
|
138
|
-
"floatingTOC.label": string;
|
|
139
|
-
|
|
140
|
-
// Comment badge
|
|
141
|
-
"commentBadge.title": string;
|
|
142
|
-
"commentBadge.titlePlural": string;
|
|
143
120
|
}
|
|
144
121
|
|
|
145
122
|
export type TranslationKey = keyof Translations;
|