@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.
Files changed (221) hide show
  1. package/.claude/CLAUDE.md +118 -76
  2. package/.claude/commands/review.md +1 -1
  3. package/.claude/roadmap.md +32 -9
  4. package/.claude/user-stories.md +100 -15
  5. package/AGENTS.md +30 -26
  6. package/Makefile +32 -0
  7. package/README.md +90 -5
  8. package/biome.json +18 -8
  9. package/bun.lock +426 -710
  10. package/bunfig.toml +2 -0
  11. package/docs/perf-baseline.md +130 -0
  12. package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
  13. package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
  14. package/e2e/comments.spec.ts +14 -58
  15. package/e2e/document-load.spec.ts +1 -23
  16. package/e2e/export.spec.ts +4 -4
  17. package/e2e/perf/add-comment.spec.ts +116 -0
  18. package/e2e/perf/fixtures/generate.ts +327 -0
  19. package/e2e/perf/initial-load.spec.ts +49 -0
  20. package/e2e/perf/perf.setup.ts +23 -0
  21. package/e2e/perf/perf.teardown.ts +9 -0
  22. package/e2e/perf/screenshot-final.png +0 -0
  23. package/e2e/perf/scroll.spec.ts +39 -0
  24. package/e2e/perf/tab-switch.spec.ts +69 -0
  25. package/e2e/perf/text-selection.spec.ts +119 -0
  26. package/e2e/perf/utils/metrics.ts +350 -0
  27. package/e2e/perf/utils/perf-cli.ts +86 -0
  28. package/e2e/persistence-file.spec.ts +41 -26
  29. package/e2e/utils/selection.ts +17 -73
  30. package/go/cmd/readit/main.go +416 -0
  31. package/go/go.mod +20 -0
  32. package/go/go.sum +41 -0
  33. package/go/internal/server/anchor.go +302 -0
  34. package/go/internal/server/anchor_test.go +111 -0
  35. package/go/internal/server/comments.go +390 -0
  36. package/go/internal/server/documents.go +113 -0
  37. package/go/internal/server/embed.go +17 -0
  38. package/go/internal/server/headings.go +33 -0
  39. package/go/internal/server/headings_test.go +75 -0
  40. package/go/internal/server/htmltext.go +123 -0
  41. package/go/internal/server/markdown.go +157 -0
  42. package/go/internal/server/markdown_bench_test.go +42 -0
  43. package/go/internal/server/markdown_test.go +79 -0
  44. package/go/internal/server/server.go +453 -0
  45. package/go/internal/server/server_bench_test.go +122 -0
  46. package/go/internal/server/settings.go +110 -0
  47. package/go/internal/server/sse.go +140 -0
  48. package/go/internal/server/storage.go +275 -0
  49. package/go/internal/server/storage_test.go +118 -0
  50. package/go/internal/server/template.go +66 -0
  51. package/go/internal/server/types.go +101 -0
  52. package/go/internal/server/watcher.go +74 -0
  53. package/index.html +4 -14
  54. package/nvim-readit/lua/readit/health.lua +64 -0
  55. package/nvim-readit/lua/readit/init.lua +463 -0
  56. package/nvim-readit/plugin/readit.lua +19 -0
  57. package/package.json +24 -41
  58. package/playwright.config.ts +12 -0
  59. package/shell/_readit +158 -0
  60. package/shell/readit.zsh +87 -0
  61. package/src/App.svelte +881 -0
  62. package/src/{cli/index.ts → cli.ts} +216 -70
  63. package/src/components/ActionsMenu.svelte +95 -0
  64. package/src/components/CommentBadge.svelte +67 -0
  65. package/src/components/CommentErrorBanner.svelte +33 -0
  66. package/src/components/CommentInput.svelte +75 -0
  67. package/src/components/CommentListItem.svelte +95 -0
  68. package/src/components/CommentManager.svelte +129 -0
  69. package/src/components/CommentNav.svelte +109 -0
  70. package/src/components/DocumentViewer.svelte +218 -0
  71. package/src/components/FloatingComment.svelte +107 -0
  72. package/src/components/Header.svelte +76 -0
  73. package/src/components/InlineEditor.svelte +72 -0
  74. package/src/components/MarginNote.svelte +167 -0
  75. package/src/components/MarginNotesContainer.svelte +33 -0
  76. package/src/components/RawModal.svelte +126 -0
  77. package/src/components/ReanchorConfirm.svelte +30 -0
  78. package/src/components/SettingsModal.svelte +220 -0
  79. package/src/components/ShortcutCapture.svelte +82 -0
  80. package/src/components/ShortcutList.svelte +145 -0
  81. package/src/components/TabBar.svelte +52 -0
  82. package/src/components/TableOfContents.svelte +125 -0
  83. package/src/components/ui/ActionLink.svelte +40 -0
  84. package/src/components/ui/Button.svelte +53 -0
  85. package/src/components/ui/Dialog.svelte +97 -0
  86. package/src/components/ui/DropdownMenu.svelte +85 -0
  87. package/src/components/ui/DropdownMenuItem.svelte +38 -0
  88. package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
  89. package/src/components/ui/Text.svelte +42 -0
  90. package/src/env.d.ts +6 -0
  91. package/src/index.css +36 -166
  92. package/src/lib/__fixtures__/bench-data.ts +1 -54
  93. package/src/lib/anchor.bench.ts +47 -68
  94. package/src/lib/anchor.test.ts +5 -9
  95. package/src/lib/anchor.ts +9 -93
  96. package/src/lib/comment-storage.bench.ts +6 -20
  97. package/src/lib/comment-storage.test.ts +45 -37
  98. package/src/lib/comment-storage.ts +23 -64
  99. package/src/lib/export.bench.ts +9 -23
  100. package/src/lib/export.ts +7 -14
  101. package/src/lib/headings.test.ts +103 -0
  102. package/src/lib/headings.ts +44 -0
  103. package/src/lib/highlight/core.test.ts +1 -6
  104. package/src/lib/highlight/dom.ts +53 -280
  105. package/src/lib/highlight/highlight-registry.ts +221 -0
  106. package/src/lib/highlight/highlight.bench.ts +92 -0
  107. package/src/lib/highlight/highlighter.ts +122 -302
  108. package/src/lib/highlight/{core.ts → resolver.ts} +3 -19
  109. package/src/lib/highlight/types.ts +0 -40
  110. package/src/lib/html-text.test.ts +162 -0
  111. package/src/lib/html-text.ts +161 -0
  112. package/src/lib/i18n/en.ts +13 -36
  113. package/src/lib/i18n/ja.ts +14 -37
  114. package/src/lib/i18n/types.ts +13 -36
  115. package/src/lib/margin-layout.bench.ts +48 -15
  116. package/src/lib/margin-layout.ts +2 -31
  117. package/src/lib/markdown-renderer.test.ts +154 -0
  118. package/src/lib/markdown-renderer.ts +177 -0
  119. package/src/lib/mermaid-config.ts +38 -0
  120. package/src/lib/mermaid-renderer.ts +162 -0
  121. package/src/lib/mermaid-worker.ts +60 -0
  122. package/src/lib/positions.ts +157 -0
  123. package/src/lib/shortcut-registry.ts +138 -103
  124. package/src/lib/utils.ts +2 -48
  125. package/src/main.ts +16 -0
  126. package/src/schema.ts +92 -0
  127. package/src/{server/index.ts → server.ts} +427 -163
  128. package/src/stores/app.svelte.ts +231 -0
  129. package/src/stores/locale.svelte.ts +46 -0
  130. package/src/stores/settings.svelte.ts +90 -0
  131. package/src/stores/shortcuts.svelte.ts +104 -0
  132. package/src/stores/ui.svelte.ts +12 -0
  133. package/src/template.ts +104 -0
  134. package/src/test-setup.ts +47 -0
  135. package/svelte.config.js +5 -0
  136. package/tsconfig.json +2 -2
  137. package/vite.config.ts +31 -3
  138. package/vscode-readit/.mcp.json +7 -0
  139. package/vscode-readit/.vscodeignore +7 -0
  140. package/vscode-readit/bun.lock +78 -0
  141. package/vscode-readit/icon.svg +10 -0
  142. package/vscode-readit/package.json +110 -0
  143. package/vscode-readit/src/extension.ts +117 -0
  144. package/vscode-readit/src/server-manager.ts +272 -0
  145. package/vscode-readit/src/webview-provider.ts +204 -0
  146. package/vscode-readit/tsconfig.json +20 -0
  147. package/e2e/fixtures/sample.html +0 -13
  148. package/src/App.tsx +0 -416
  149. package/src/components/ActionsMenu.tsx +0 -112
  150. package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
  151. package/src/components/DocumentViewer/DocumentViewer.tsx +0 -259
  152. package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
  153. package/src/components/DocumentViewer/InlineCode.tsx +0 -60
  154. package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -137
  155. package/src/components/DocumentViewer/index.ts +0 -1
  156. package/src/components/FloatingTOC.tsx +0 -61
  157. package/src/components/Header.tsx +0 -65
  158. package/src/components/InlineEditor.tsx +0 -74
  159. package/src/components/MarginNote.tsx +0 -207
  160. package/src/components/MarginNotes.tsx +0 -50
  161. package/src/components/RawModal.tsx +0 -143
  162. package/src/components/ReanchorConfirm.tsx +0 -36
  163. package/src/components/SettingsModal.tsx +0 -310
  164. package/src/components/ShortcutCapture.tsx +0 -48
  165. package/src/components/ShortcutList.tsx +0 -198
  166. package/src/components/TabBar.tsx +0 -60
  167. package/src/components/TableOfContents.tsx +0 -108
  168. package/src/components/comments/CommentBadge.tsx +0 -49
  169. package/src/components/comments/CommentInput.tsx +0 -114
  170. package/src/components/comments/CommentListItem.tsx +0 -92
  171. package/src/components/comments/CommentManager.tsx +0 -113
  172. package/src/components/comments/CommentMinimap.tsx +0 -62
  173. package/src/components/comments/CommentNav.tsx +0 -109
  174. package/src/components/ui/ActionBar.tsx +0 -16
  175. package/src/components/ui/ActionLink.tsx +0 -32
  176. package/src/components/ui/Button.tsx +0 -55
  177. package/src/components/ui/Dialog.tsx +0 -156
  178. package/src/components/ui/DropdownMenu.tsx +0 -114
  179. package/src/components/ui/SeparatorDot.tsx +0 -9
  180. package/src/components/ui/Text.tsx +0 -54
  181. package/src/contexts/CommentContext.tsx +0 -229
  182. package/src/contexts/LayoutContext.tsx +0 -88
  183. package/src/contexts/LocaleContext.tsx +0 -35
  184. package/src/hooks/useClickOutside.ts +0 -35
  185. package/src/hooks/useClipboard.ts +0 -82
  186. package/src/hooks/useCommentNavigation.ts +0 -130
  187. package/src/hooks/useComments.ts +0 -323
  188. package/src/hooks/useDocument.ts +0 -156
  189. package/src/hooks/useEditorScheme.ts +0 -51
  190. package/src/hooks/useFontPreference.ts +0 -59
  191. package/src/hooks/useHeadings.test.ts +0 -159
  192. package/src/hooks/useHeadings.ts +0 -129
  193. package/src/hooks/useKeybindings.ts +0 -108
  194. package/src/hooks/useKeyboardShortcuts.ts +0 -63
  195. package/src/hooks/useLayoutMode.ts +0 -44
  196. package/src/hooks/useLocalePreference.ts +0 -42
  197. package/src/hooks/useReanchorMode.ts +0 -33
  198. package/src/hooks/useScrollMetrics.ts +0 -56
  199. package/src/hooks/useScrollSpy.ts +0 -81
  200. package/src/hooks/useTextSelection.ts +0 -123
  201. package/src/hooks/useThemePreference.ts +0 -66
  202. package/src/lib/context.bench.ts +0 -41
  203. package/src/lib/context.test.ts +0 -224
  204. package/src/lib/context.ts +0 -193
  205. package/src/lib/editor-links.ts +0 -59
  206. package/src/lib/highlight/colors.ts +0 -37
  207. package/src/lib/highlight/index.ts +0 -23
  208. package/src/lib/highlight/script-builder.ts +0 -485
  209. package/src/lib/html-processor.test.tsx +0 -170
  210. package/src/lib/html-processor.tsx +0 -95
  211. package/src/lib/i18n/completeness.test.ts +0 -51
  212. package/src/lib/i18n/translations.test.ts +0 -39
  213. package/src/lib/layout-constants.ts +0 -12
  214. package/src/lib/scroll.test.ts +0 -118
  215. package/src/lib/scroll.ts +0 -47
  216. package/src/lib/shortcut-registry.test.ts +0 -173
  217. package/src/lib/utils.test.ts +0 -110
  218. package/src/main.tsx +0 -13
  219. package/src/store/index.test.ts +0 -242
  220. package/src/store/index.ts +0 -254
  221. 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>&lt;div&gt; &amp; &quot;quotes&quot;</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(/&amp;/g, "&")
151
+ .replace(/&lt;/g, "<")
152
+ .replace(/&gt;/g, ">")
153
+ .replace(/&quot;/g, '"')
154
+ .replace(/&#39;/g, "'")
155
+ .replace(/&#x27;/g, "'")
156
+ .replace(/&nbsp;/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
+ }
@@ -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.copyAllAI": "Copy All (AI)",
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
- // Shortcut groups
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
- // Shortcut labels
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
  };
@@ -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.copyAllAI": "全てコピー (AI)",
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
- // Shortcut groups
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
- // Shortcut labels
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
  };
@@ -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.copyAllAI": string;
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
- // Shortcut groups
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
- // Shortcut labels (rendered in ShortcutList)
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;