@plannotator/ui 0.22.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.
Files changed (305) hide show
  1. package/README.md +60 -0
  2. package/assets/diff-options.png +0 -0
  3. package/assets/icon-codex.png +0 -0
  4. package/assets/look-flat.png +0 -0
  5. package/assets/look-grid.png +0 -0
  6. package/assets/review-sections.png +0 -0
  7. package/assets/review-tree.png +0 -0
  8. package/assets/workspaces.webp +0 -0
  9. package/components/AISettingsTab.tsx +145 -0
  10. package/components/ActionMenu.tsx +101 -0
  11. package/components/AgentControls.tsx +216 -0
  12. package/components/AgentsTab.tsx +1294 -0
  13. package/components/AnnotationPanel.tsx +731 -0
  14. package/components/AnnotationSidebar.tsx +86 -0
  15. package/components/AnnotationToolbar.tsx +323 -0
  16. package/components/AnnotationToolstrip.tsx +355 -0
  17. package/components/ApproveDropdown.tsx +170 -0
  18. package/components/AttachmentsButton.tsx +410 -0
  19. package/components/BlockRenderer.tsx +163 -0
  20. package/components/BorderTrail.tsx +38 -0
  21. package/components/CodeFilePicker.tsx +64 -0
  22. package/components/CodeFilePopout.tsx +602 -0
  23. package/components/CodePathValidationContext.tsx +14 -0
  24. package/components/CommentPopover.tsx +512 -0
  25. package/components/CompletionOverlay.tsx +99 -0
  26. package/components/ConfirmDialog.tsx +128 -0
  27. package/components/DocBadges.tsx +231 -0
  28. package/components/EditorAnnotationCard.tsx +65 -0
  29. package/components/ExportModal.tsx +565 -0
  30. package/components/FloatingQuickLabelPicker.tsx +144 -0
  31. package/components/GitHubIcon.tsx +16 -0
  32. package/components/GitLabIcon.tsx +30 -0
  33. package/components/GraphvizBlock.tsx +511 -0
  34. package/components/ImageAnnotator/Canvas.tsx +118 -0
  35. package/components/ImageAnnotator/Toolbar.tsx +218 -0
  36. package/components/ImageAnnotator/index.tsx +272 -0
  37. package/components/ImageAnnotator/types.ts +39 -0
  38. package/components/ImageAnnotator/utils.ts +147 -0
  39. package/components/ImageThumbnail.tsx +126 -0
  40. package/components/ImportModal.tsx +146 -0
  41. package/components/InlineMarkdown.tsx +1094 -0
  42. package/components/KeyboardShortcuts.tsx +208 -0
  43. package/components/Landing.tsx +496 -0
  44. package/components/ListItemBody.tsx +62 -0
  45. package/components/ListMarker.tsx +73 -0
  46. package/components/LookAndFeelAnnouncementDialog.tsx +230 -0
  47. package/components/MarkdownEditor.tsx +44 -0
  48. package/components/MenuVersionSection.tsx +91 -0
  49. package/components/MermaidBlock.tsx +579 -0
  50. package/components/ModeToggle.tsx +68 -0
  51. package/components/OpenInAppButton.tsx +299 -0
  52. package/components/OverlayScrollArea.tsx +82 -0
  53. package/components/PermissionModeSetup.tsx +92 -0
  54. package/components/PinpointOverlay.tsx +99 -0
  55. package/components/PlanAIAnnouncementDialog.tsx +187 -0
  56. package/components/PlanHeaderMenu.tsx +299 -0
  57. package/components/PopoutDialog.tsx +91 -0
  58. package/components/Popover.tsx +27 -0
  59. package/components/ProviderIcons.tsx +51 -0
  60. package/components/PullRequestIcon.tsx +12 -0
  61. package/components/QuickLabelDropdown.tsx +63 -0
  62. package/components/RenderedMarkdown.tsx +54 -0
  63. package/components/RepoIcon.tsx +12 -0
  64. package/components/ResizeHandle.tsx +86 -0
  65. package/components/ReviewAgentsIcon.tsx +18 -0
  66. package/components/SearchableSelect.tsx +157 -0
  67. package/components/Settings.tsx +2220 -0
  68. package/components/SparklesIcon.tsx +45 -0
  69. package/components/StickyHeaderLane.tsx +260 -0
  70. package/components/TableOfContents.tsx +157 -0
  71. package/components/TaterSpritePullup.tsx +34 -0
  72. package/components/TaterSpriteRunning.tsx +54 -0
  73. package/components/TaterSpriteSitting.tsx +37 -0
  74. package/components/TextShimmer.tsx +57 -0
  75. package/components/ThemeProvider.tsx +162 -0
  76. package/components/ThemeTab.tsx +139 -0
  77. package/components/ToolbarButtons.tsx +116 -0
  78. package/components/Tooltip.tsx +45 -0
  79. package/components/Viewer.tsx +977 -0
  80. package/components/ai/AIProviderBar.tsx +95 -0
  81. package/components/ai/DocumentAIChatPanel.tsx +313 -0
  82. package/components/blocks/AlertBlock.tsx +58 -0
  83. package/components/blocks/Callout.tsx +60 -0
  84. package/components/blocks/CodeBlock.tsx +75 -0
  85. package/components/blocks/HtmlBlock.tsx +122 -0
  86. package/components/blocks/MathBlock.tsx +36 -0
  87. package/components/blocks/TableBlock.tsx +146 -0
  88. package/components/blocks/TablePopout.tsx +279 -0
  89. package/components/blocks/TableToolbar.tsx +153 -0
  90. package/components/blocks/proseBody.tsx +102 -0
  91. package/components/core/button.tsx +44 -0
  92. package/components/core/textarea.tsx +26 -0
  93. package/components/diagramLanguages.ts +14 -0
  94. package/components/goal-setup/GoalSetupSurface.tsx +1354 -0
  95. package/components/html-viewer/HtmlViewer.tsx +386 -0
  96. package/components/html-viewer/bridge-script.ts +505 -0
  97. package/components/html-viewer/index.ts +1 -0
  98. package/components/html-viewer/useHtmlAnnotation.ts +400 -0
  99. package/components/icons/AgentIcons.tsx +66 -0
  100. package/components/icons/AppIcon.tsx +56 -0
  101. package/components/icons/MessagesIcon.tsx +11 -0
  102. package/components/icons/ObsidianIcons.tsx +208 -0
  103. package/components/icons/app/android-studio.svg +369 -0
  104. package/components/icons/app/antigravity.svg +97 -0
  105. package/components/icons/app/cursor.svg +16 -0
  106. package/components/icons/app/file-explorer.svg +20 -0
  107. package/components/icons/app/finder.png +0 -0
  108. package/components/icons/app/ghostty.svg +13 -0
  109. package/components/icons/app/iterm2.svg +13 -0
  110. package/components/icons/app/powershell.svg +14 -0
  111. package/components/icons/app/sublime-text.svg +17 -0
  112. package/components/icons/app/terminal.png +0 -0
  113. package/components/icons/app/textmate.png +0 -0
  114. package/components/icons/app/vscode.svg +39 -0
  115. package/components/icons/app/warp.png +0 -0
  116. package/components/icons/app/xcode.png +0 -0
  117. package/components/icons/app/zed-dark.svg +15 -0
  118. package/components/icons/app/zed.svg +15 -0
  119. package/components/icons/themeIcons.tsx +47 -0
  120. package/components/mermaidSvg.ts +33 -0
  121. package/components/plan-diff/PlanCleanDiffView.tsx +884 -0
  122. package/components/plan-diff/PlanDiffBadge.tsx +48 -0
  123. package/components/plan-diff/PlanDiffModeSwitcher.tsx +103 -0
  124. package/components/plan-diff/PlanDiffViewer.tsx +214 -0
  125. package/components/plan-diff/PlanRawDiffView.tsx +102 -0
  126. package/components/plan-diff/VSCodeIcon.tsx +132 -0
  127. package/components/settings/HooksTab.tsx +208 -0
  128. package/components/sidebar/ArchiveBrowser.tsx +98 -0
  129. package/components/sidebar/CountBadge.tsx +12 -0
  130. package/components/sidebar/FileBrowser.tsx +498 -0
  131. package/components/sidebar/MessagesBrowser.tsx +109 -0
  132. package/components/sidebar/SidebarContainer.tsx +353 -0
  133. package/components/sidebar/SidebarTabs.tsx +155 -0
  134. package/components/sidebar/VersionBrowser.tsx +141 -0
  135. package/components/types.d.ts +7 -0
  136. package/components/ui/badge.tsx +44 -0
  137. package/components/ui/button.tsx +83 -0
  138. package/components/ui/card.tsx +58 -0
  139. package/components/ui/dialog.tsx +106 -0
  140. package/components/ui/dropdown-menu.tsx +240 -0
  141. package/components/ui/state-pill.tsx +45 -0
  142. package/components/ui/tabs.tsx +46 -0
  143. package/components/ui/textarea.tsx +25 -0
  144. package/config/configStore.ts +214 -0
  145. package/config/index.ts +4 -0
  146. package/config/reviewView.ts +42 -0
  147. package/config/settings.ts +310 -0
  148. package/config/useConfig.ts +20 -0
  149. package/configure.ts +69 -0
  150. package/globals.d.ts +14 -0
  151. package/hooks/pfm/useCodeFilePopout.ts +111 -0
  152. package/hooks/useAIChat.ts +532 -0
  153. package/hooks/useAIProviderConfig.ts +115 -0
  154. package/hooks/useActiveSection.ts +78 -0
  155. package/hooks/useAgentJobs.ts +306 -0
  156. package/hooks/useAgentSettings.ts +579 -0
  157. package/hooks/useAgents.ts +90 -0
  158. package/hooks/useAnnotationDraft.ts +523 -0
  159. package/hooks/useAnnotationHighlighter.ts +1170 -0
  160. package/hooks/useArchive.ts +171 -0
  161. package/hooks/useAutoClose.ts +104 -0
  162. package/hooks/useCodeAnnotationDraft.ts +194 -0
  163. package/hooks/useCodeFilePopout.ts +1 -0
  164. package/hooks/useDismissOnOutsideAndEscape.ts +50 -0
  165. package/hooks/useDraggable.ts +108 -0
  166. package/hooks/useEditorAnnotations.ts +64 -0
  167. package/hooks/useExternalAnnotationHighlights.ts +105 -0
  168. package/hooks/useExternalAnnotations.ts +268 -0
  169. package/hooks/useFileBrowser.ts +402 -0
  170. package/hooks/useInputMethodSwitch.ts +89 -0
  171. package/hooks/useIsMobile.ts +17 -0
  172. package/hooks/useLinkedDoc.ts +494 -0
  173. package/hooks/useOverlayViewport.ts +36 -0
  174. package/hooks/usePinpoint.ts +182 -0
  175. package/hooks/usePlanDiff.ts +176 -0
  176. package/hooks/usePrintMode.ts +27 -0
  177. package/hooks/useResizablePanel.ts +173 -0
  178. package/hooks/useScrollViewport.ts +38 -0
  179. package/hooks/useSharing.ts +441 -0
  180. package/hooks/useSidebar.ts +55 -0
  181. package/hooks/useUpdateCheck.ts +132 -0
  182. package/hooks/useValidatedCodePaths.ts +94 -0
  183. package/icons/GitUser.tsx +17 -0
  184. package/lib/utils.ts +11 -0
  185. package/package.json +117 -0
  186. package/plannotator.webp +0 -0
  187. package/print.css +427 -0
  188. package/shortcuts/code-review/ai.shortcuts.ts +25 -0
  189. package/shortcuts/code-review/allFilesDiff.shortcuts.ts +46 -0
  190. package/shortcuts/code-review/annotationToolbar.shortcuts.ts +30 -0
  191. package/shortcuts/code-review/fileTree.shortcuts.ts +35 -0
  192. package/shortcuts/code-review/prComments.shortcuts.ts +18 -0
  193. package/shortcuts/code-review/suggestionModal.shortcuts.ts +25 -0
  194. package/shortcuts/code-review/tourDialog.shortcuts.ts +18 -0
  195. package/shortcuts/core.ts +399 -0
  196. package/shortcuts/index.ts +21 -0
  197. package/shortcuts/plan-review/annotationPanel.shortcuts.ts +25 -0
  198. package/shortcuts/plan-review/annotationToolbar.shortcuts.ts +39 -0
  199. package/shortcuts/plan-review/commentPopover.shortcuts.ts +21 -0
  200. package/shortcuts/plan-review/goalSetup.shortcuts.ts +10 -0
  201. package/shortcuts/plan-review/imageAnnotator.shortcuts.ts +42 -0
  202. package/shortcuts/plan-review/inputMethod.shortcuts.ts +30 -0
  203. package/shortcuts/plan-review/sidebar.shortcuts.ts +34 -0
  204. package/shortcuts/plan-review/viewer.shortcuts.ts +24 -0
  205. package/shortcuts/runtime.ts +259 -0
  206. package/sprite_package_additional/index.html +34 -0
  207. package/sprite_package_additional/sprite.png +0 -0
  208. package/sprite_package_new/index.html +34 -0
  209. package/sprite_package_new/sprite.png +0 -0
  210. package/sprite_package_pulluphang/index.html +34 -0
  211. package/sprite_package_pulluphang/sprite.png +0 -0
  212. package/styles.css +1 -0
  213. package/theme.css +893 -0
  214. package/themes/adwaita.css +62 -0
  215. package/themes/andromeeda.css +66 -0
  216. package/themes/aurora-x.css +66 -0
  217. package/themes/ayu-dark.css +66 -0
  218. package/themes/caffeine.css +113 -0
  219. package/themes/catppuccin.css +62 -0
  220. package/themes/claude-plus.css +60 -0
  221. package/themes/cursor-hc.css +34 -0
  222. package/themes/cursor-midnight.css +34 -0
  223. package/themes/cursor.css +62 -0
  224. package/themes/dark-plus.css +66 -0
  225. package/themes/doom-64.css +109 -0
  226. package/themes/dracula.css +33 -0
  227. package/themes/everforest-hard.css +62 -0
  228. package/themes/everforest-soft.css +62 -0
  229. package/themes/everforest.css +62 -0
  230. package/themes/github.css +66 -0
  231. package/themes/gruvbox.css +62 -0
  232. package/themes/houston.css +66 -0
  233. package/themes/kanagawa-dragon.css +34 -0
  234. package/themes/kanagawa-lotus.css +34 -0
  235. package/themes/kanagawa-wave.css +34 -0
  236. package/themes/laserwave.css +66 -0
  237. package/themes/material.css +62 -0
  238. package/themes/min.css +66 -0
  239. package/themes/monokai-pro.css +34 -0
  240. package/themes/neutral.css +59 -0
  241. package/themes/night-owl.css +66 -0
  242. package/themes/nord.css +66 -0
  243. package/themes/one-dark-pro.css +66 -0
  244. package/themes/one-light.css +66 -0
  245. package/themes/paulmillr.css +34 -0
  246. package/themes/plannotator.css +60 -0
  247. package/themes/plastic.css +66 -0
  248. package/themes/poimandres.css +66 -0
  249. package/themes/quantum-rose.css +109 -0
  250. package/themes/red.css +66 -0
  251. package/themes/rose-pine.css +62 -0
  252. package/themes/simple.css +124 -0
  253. package/themes/slack.css +66 -0
  254. package/themes/snazzy-light.css +66 -0
  255. package/themes/soft-pop.css +60 -0
  256. package/themes/solar-dusk.css +109 -0
  257. package/themes/solarized.css +66 -0
  258. package/themes/synthwave-84.css +34 -0
  259. package/themes/terminal.css +62 -0
  260. package/themes/tinacious.css +57 -0
  261. package/themes/tokyo-night.css +62 -0
  262. package/themes/vesper.css +62 -0
  263. package/themes/vitesse-black.css +66 -0
  264. package/themes/vitesse.css +62 -0
  265. package/types.ts +264 -0
  266. package/utils/agentSwitch.ts +71 -0
  267. package/utils/aiChatFormat.ts +33 -0
  268. package/utils/aiPrompt.ts +29 -0
  269. package/utils/aiProvider.ts +217 -0
  270. package/utils/anchors.ts +11 -0
  271. package/utils/annotateAgentTerminal.ts +29 -0
  272. package/utils/annotationHelpers.ts +101 -0
  273. package/utils/bear.ts +59 -0
  274. package/utils/blockTargeting.ts +240 -0
  275. package/utils/callback.ts +99 -0
  276. package/utils/commentContent.ts +8 -0
  277. package/utils/defaultNotesApp.ts +20 -0
  278. package/utils/diffFonts.ts +33 -0
  279. package/utils/editorMode.ts +31 -0
  280. package/utils/fileBrowser.ts +40 -0
  281. package/utils/generateId.ts +7 -0
  282. package/utils/generateIdentity.ts +29 -0
  283. package/utils/identity.ts +112 -0
  284. package/utils/inlineTransforms.ts +41 -0
  285. package/utils/inputMethod.ts +17 -0
  286. package/utils/lookAndFeelAnnouncement.ts +18 -0
  287. package/utils/obsidian.ts +201 -0
  288. package/utils/octarine.ts +53 -0
  289. package/utils/parser.ts +991 -0
  290. package/utils/permissionMode.ts +77 -0
  291. package/utils/planAIAnnouncement.ts +17 -0
  292. package/utils/planAgentInstructions.ts +137 -0
  293. package/utils/planDiffEngine.ts +590 -0
  294. package/utils/planSave.ts +49 -0
  295. package/utils/platform.ts +11 -0
  296. package/utils/quickLabels.ts +77 -0
  297. package/utils/reviewAgentInstructions.ts +182 -0
  298. package/utils/sanitizeHtml.ts +45 -0
  299. package/utils/sharing.ts +350 -0
  300. package/utils/slugify.ts +37 -0
  301. package/utils/storage.ts +141 -0
  302. package/utils/themeRegistry.ts +567 -0
  303. package/utils/uiPreferences.ts +34 -0
  304. package/utils/upload.ts +56 -0
  305. package/utils/wideMode.ts +48 -0
@@ -0,0 +1,505 @@
1
+ /**
2
+ * Bridge script injected into the HTML viewer iframe.
3
+ *
4
+ * Handles text selection, annotation marks, theme updates, and resize
5
+ * notifications. Communicates with the parent via postMessage using a
6
+ * "plannotator-bridge-*" message protocol.
7
+ *
8
+ * This is a string constant — it gets prepended to the iframe's srcdoc.
9
+ * No external dependencies.
10
+ */
11
+
12
+ export const ANNOTATION_HIGHLIGHT_CSS = `
13
+ .annotation-highlight {
14
+ border-radius: 2px;
15
+ padding: 0 2px;
16
+ margin: 0 -2px;
17
+ cursor: pointer;
18
+ }
19
+ .annotation-highlight.deletion {
20
+ background: oklch(from var(--destructive, #c0392b) l c h / 0.35);
21
+ text-decoration: line-through;
22
+ text-decoration-color: var(--destructive, #c0392b);
23
+ text-decoration-thickness: 2px;
24
+ }
25
+ .annotation-highlight.comment {
26
+ background: oklch(0.70 0.18 60 / 0.3);
27
+ border-bottom: 2px solid var(--accent, #d97757);
28
+ }
29
+ .annotation-highlight.focused {
30
+ background: oklch(from var(--focus-highlight, #4493f8) l c h / 0.45) !important;
31
+ box-shadow: 0 0 8px oklch(from var(--focus-highlight, #4493f8) l c h / 0.4);
32
+ border-bottom: 2px solid var(--focus-highlight, #4493f8);
33
+ filter: none;
34
+ }
35
+ .annotation-highlight:hover {
36
+ filter: brightness(1.2);
37
+ }
38
+ .plannotator-pinpoint-hover {
39
+ outline: 2px solid var(--focus-highlight, #4493f8) !important;
40
+ outline-offset: 1px;
41
+ cursor: crosshair !important;
42
+ }
43
+ /* SVG nodes can't take a CSS outline — stroke their shapes instead. */
44
+ .plannotator-pinpoint-hover rect, .plannotator-pinpoint-hover path,
45
+ .plannotator-pinpoint-hover circle, .plannotator-pinpoint-hover ellipse, .plannotator-pinpoint-hover polygon {
46
+ stroke: var(--focus-highlight, #4493f8) !important; stroke-width: 2.5px !important;
47
+ }
48
+ `;
49
+
50
+ export const BRIDGE_SCRIPT = `(function() {
51
+ var PREFIX = 'plannotator-bridge-';
52
+
53
+ // --- Theme ---
54
+ window.addEventListener('message', function(e) {
55
+ if (!e.data || e.data.type !== PREFIX + 'theme') return;
56
+ var root = document.documentElement;
57
+ var tokens = e.data.tokens;
58
+ for (var key in tokens) {
59
+ if (tokens.hasOwnProperty(key)) root.style.setProperty(key, tokens[key]);
60
+ }
61
+ root.classList.remove('light');
62
+ if (e.data.isLight) root.classList.add('light');
63
+ });
64
+
65
+ // --- Resize ---
66
+ var lastHeight = 0;
67
+ function postResize() {
68
+ if (!document.body) return;
69
+ var h = document.body.scrollHeight;
70
+ if (h !== lastHeight) {
71
+ lastHeight = h;
72
+ parent.postMessage({ type: PREFIX + 'resize', height: h }, '*');
73
+ }
74
+ }
75
+ window.addEventListener('load', postResize);
76
+
77
+ // --- Selection ---
78
+ var pendingSelection = null;
79
+ var pendingRange = null; // live range for the pending selection (scroll tracking)
80
+ var currentInputMethod = 'drag'; // 'drag' = text selection, 'pinpoint' = click an element
81
+ var pinpointHover = null;
82
+ // A plain click on an element-annotation target opens the toolbar, but the same
83
+ // click's mouseup schedules a handleSelection() that would see an empty selection
84
+ // and immediately clear it. This flag suppresses that one trailing clear.
85
+ var skipNextClear = false;
86
+
87
+ document.addEventListener('mouseup', function(e) {
88
+ if (currentInputMethod === 'pinpoint') return; // pinpoint uses click, not drag-select
89
+ if (e.target && e.target.closest && e.target.closest('.annotation-highlight')) return;
90
+ setTimeout(handleSelection, 10);
91
+ });
92
+
93
+ function handleSelection() {
94
+ var sel = window.getSelection();
95
+ if (!sel || sel.isCollapsed || !sel.rangeCount) {
96
+ // Trailing clear from a plain-click element annotation — consume it once.
97
+ if (skipNextClear) { skipNextClear = false; return; }
98
+ if (pendingSelection) {
99
+ parent.postMessage({ type: PREFIX + 'selection-clear' }, '*');
100
+ pendingSelection = null;
101
+ pendingRange = null;
102
+ }
103
+ return;
104
+ }
105
+ skipNextClear = false; // a real text selection happened
106
+ var range = sel.getRangeAt(0);
107
+ var text = sel.toString().trim();
108
+ if (!text) return;
109
+
110
+ var rect = range.getBoundingClientRect();
111
+ pendingRange = range;
112
+ pendingSelection = {
113
+ text: text,
114
+ startContainerPath: getNodePath(range.startContainer),
115
+ startOffset: range.startOffset,
116
+ endContainerPath: getNodePath(range.endContainer),
117
+ endOffset: range.endOffset
118
+ };
119
+
120
+ parent.postMessage({
121
+ type: PREFIX + 'selection',
122
+ text: text,
123
+ rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
124
+ }, '*');
125
+ }
126
+
127
+ // Keep the toolbar/popover attached while the iframe content scrolls: re-post the
128
+ // pending selection's live rect (parent has no way to see an in-iframe scroll).
129
+ // Capture phase so inner scroll containers count too.
130
+ var scrollRaf = 0;
131
+ function postSelectionRect() {
132
+ scrollRaf = 0;
133
+ if (!pendingSelection || !pendingRange) return;
134
+ var r = pendingRange.getBoundingClientRect();
135
+ if (r.bottom < 0 || r.top > window.innerHeight) {
136
+ // Selection scrolled out of view — close the toolbar (matches markdown).
137
+ parent.postMessage({ type: PREFIX + 'selection-clear' }, '*');
138
+ return;
139
+ }
140
+ parent.postMessage({
141
+ type: PREFIX + 'selection-rect',
142
+ rect: { top: r.top, left: r.left, width: r.width, height: r.height }
143
+ }, '*');
144
+ }
145
+ window.addEventListener('scroll', function() {
146
+ if (!pendingSelection) return;
147
+ if (!scrollRaf) scrollRaf = requestAnimationFrame(postSelectionRect);
148
+ }, true);
149
+
150
+ // --- Mark Creation ---
151
+ window.addEventListener('message', function(e) {
152
+ if (!e.data || !e.data.type) return;
153
+ var type = e.data.type;
154
+
155
+ if (type === PREFIX + 'create-mark') {
156
+ var id = e.data.id;
157
+ var annType = e.data.annotationType || 'comment';
158
+ if (pendingSelection) {
159
+ // Text selections wrap a <mark>; element pinpoints (e.g. SVG nodes) carry
160
+ // no range, so there's no inline mark to apply — the annotation is still
161
+ // captured on the parent side from the posted text.
162
+ if (pendingSelection.startContainerPath) applyMark(id, annType, pendingSelection);
163
+ pendingSelection = null;
164
+ pendingRange = null;
165
+ window.getSelection().removeAllRanges();
166
+ }
167
+ }
168
+
169
+ else if (type === PREFIX + 'find-and-mark') {
170
+ var found = findTextAndMark(e.data.id, e.data.originalText, e.data.annotationType || 'comment');
171
+ parent.postMessage({
172
+ type: PREFIX + 'mark-applied',
173
+ id: e.data.id,
174
+ success: found
175
+ }, '*');
176
+ }
177
+
178
+ else if (type === PREFIX + 'remove-mark') {
179
+ removeMark(e.data.id);
180
+ }
181
+
182
+ else if (type === PREFIX + 'clear-marks') {
183
+ var marks = document.querySelectorAll('.annotation-highlight[data-bind-id]');
184
+ for (var i = marks.length - 1; i >= 0; i--) unwrapMark(marks[i]);
185
+ }
186
+
187
+ else if (type === PREFIX + 'scroll-to') {
188
+ var mark = document.querySelector('[data-bind-id="' + e.data.id + '"]');
189
+ if (mark) {
190
+ mark.scrollIntoView({ behavior: 'smooth', block: 'center' });
191
+ mark.classList.add('focused');
192
+ setTimeout(function() { mark.classList.remove('focused'); }, 2000);
193
+ }
194
+ }
195
+
196
+ else if (type === PREFIX + 'focus-mark') {
197
+ var all = document.querySelectorAll('.annotation-highlight');
198
+ for (var j = 0; j < all.length; j++) all[j].classList.remove('focused');
199
+ if (e.data.id) {
200
+ var target = document.querySelector('[data-bind-id="' + e.data.id + '"]');
201
+ if (target) target.classList.add('focused');
202
+ }
203
+ }
204
+
205
+ else if (type === PREFIX + 'set-input-method') {
206
+ currentInputMethod = e.data.method === 'pinpoint' ? 'pinpoint' : 'drag';
207
+ if (currentInputMethod !== 'pinpoint') {
208
+ if (pinpointHover) { pinpointHover.classList.remove('plannotator-pinpoint-hover'); pinpointHover = null; }
209
+ if (pinpointLabelEl) pinpointLabelEl.style.display = 'none';
210
+ }
211
+ }
212
+ });
213
+
214
+ // --- Pinpoint: hover to outline a whole element, click to select its text ---
215
+ // Reuses the normal selection pipeline — a pinpoint click just sets the iframe
216
+ // selection over the element's text, then runs handleSelection() like a drag.
217
+ var PINPOINT_SKIP = { SCRIPT: 1, STYLE: 1, NOSCRIPT: 1, HTML: 1, BODY: 1, HEAD: 1 };
218
+ var PINPOINT_INLINE = { A: 1, SPAN: 1, EM: 1, STRONG: 1, B: 1, I: 1, CODE: 1, SMALL: 1, LABEL: 1, MARK: 1, SUP: 1, SUB: 1, U: 1, ABBR: 1, TIME: 1 };
219
+
220
+ function resolvePinpointEl(node) {
221
+ var el = node;
222
+ while (el && el.nodeType === 3) el = el.parentNode; // text node -> parent
223
+ if (!el || el.nodeType !== 1) return null;
224
+ // SVG: a click on a shape/text leaf (rect, path, text) resolves to the whole
225
+ // node group, so clicking anywhere on an SVG node — not just its label — picks it.
226
+ if (el.ownerSVGElement && el.closest) {
227
+ var g = el.closest('g');
228
+ if (g && g.textContent && g.textContent.trim()) el = g;
229
+ }
230
+ if (PINPOINT_SKIP[el.tagName]) return null;
231
+ // Climb out of inline elements to their containing block.
232
+ while (el.parentElement && PINPOINT_INLINE[el.tagName] && !PINPOINT_SKIP[el.parentElement.tagName]) {
233
+ el = el.parentElement;
234
+ }
235
+ if (PINPOINT_SKIP[el.tagName]) return null;
236
+ if (!el.textContent || !el.textContent.trim()) return null; // need text to annotate
237
+ return el;
238
+ }
239
+
240
+ // Floating label naming the element under the cursor (like the markdown overlay).
241
+ var PINPOINT_LABELS = { H1:'Heading', H2:'Heading', H3:'Heading', H4:'Heading', H5:'Heading', H6:'Heading', P:'Paragraph', UL:'List', OL:'List', LI:'List item', A:'Link', BUTTON:'Button', IMG:'Image', TABLE:'Table', THEAD:'Table', TBODY:'Table', TR:'Row', TD:'Cell', TH:'Header cell', SECTION:'Section', NAV:'Navigation', HEADER:'Header', FOOTER:'Footer', ARTICLE:'Article', ASIDE:'Sidebar', BLOCKQUOTE:'Quote', PRE:'Code', CODE:'Code', FIGURE:'Figure', FIGCAPTION:'Caption', MAIN:'Main', FORM:'Form', INPUT:'Input', LABEL:'Label' };
242
+ var pinpointLabelEl = null;
243
+ function getPinpointLabelEl() {
244
+ if (!pinpointLabelEl) {
245
+ pinpointLabelEl = document.createElement('div');
246
+ pinpointLabelEl.setAttribute('data-plannotator-pinpoint-label', '');
247
+ pinpointLabelEl.style.cssText = 'position:fixed;z-index:2147483647;pointer-events:none;display:none;font:600 11px/1.3 system-ui,-apple-system,sans-serif;padding:2px 7px;border-radius:5px;background:var(--focus-highlight,#4493f8);color:#fff;white-space:nowrap;box-shadow:0 1px 5px rgba(0,0,0,.35);';
248
+ document.body.appendChild(pinpointLabelEl);
249
+ }
250
+ return pinpointLabelEl;
251
+ }
252
+ function hidePinpointLabel() { if (pinpointLabelEl) pinpointLabelEl.style.display = 'none'; }
253
+
254
+ document.addEventListener('mousemove', function(e) {
255
+ if (currentInputMethod !== 'pinpoint') return;
256
+ var el = resolvePinpointEl(e.target);
257
+ if (el !== pinpointHover) {
258
+ if (pinpointHover) pinpointHover.classList.remove('plannotator-pinpoint-hover');
259
+ pinpointHover = el;
260
+ if (el) el.classList.add('plannotator-pinpoint-hover');
261
+ }
262
+ if (!el) { hidePinpointLabel(); return; }
263
+ var r = el.getBoundingClientRect();
264
+ var lbl = getPinpointLabelEl();
265
+ lbl.textContent = PINPOINT_LABELS[el.tagName] || el.tagName.toLowerCase();
266
+ lbl.style.display = 'block';
267
+ var top = r.top - 22;
268
+ lbl.style.top = (top < 2 ? r.top + 2 : top) + 'px';
269
+ lbl.style.left = Math.max(2, r.left) + 'px';
270
+ });
271
+
272
+ // Pop the toolbar for a whole element: select its text if possible (so a <mark>
273
+ // can wrap it), else post its text + box directly so the toolbar still anchors
274
+ // (e.g. an SVG node, whose <text> doesn't select like HTML text).
275
+ function annotateElement(el) {
276
+ if (!el) return;
277
+ if (pinpointHover) { pinpointHover.classList.remove('plannotator-pinpoint-hover'); pinpointHover = null; }
278
+ hidePinpointLabel();
279
+ // SVG content can't hold an HTML <mark> wrapper — wrapping an SVG <text> in a
280
+ // <mark> un-renders it (the text disappears). So never text-wrap SVG: treat it
281
+ // as a whole-element annotation (post its text + box, no mark). HTML elements
282
+ // still try a real text selection first so a <mark> can highlight the words.
283
+ var txt = '';
284
+ if (!el.ownerSVGElement) {
285
+ try {
286
+ var sel = window.getSelection();
287
+ sel.removeAllRanges();
288
+ var range = document.createRange();
289
+ range.selectNodeContents(el);
290
+ sel.addRange(range);
291
+ txt = (sel.toString() || '').trim();
292
+ } catch (ex) {}
293
+ }
294
+ if (txt) { handleSelection(); return; }
295
+ var elText = (el.textContent || '').trim();
296
+ if (!elText) return;
297
+ var r = el.getBoundingClientRect();
298
+ pendingSelection = { element: true };
299
+ pendingRange = null;
300
+ skipNextClear = true; // don't let this click's mouseup clear the toolbar we just opened
301
+ parent.postMessage({ type: PREFIX + 'selection', text: elText,
302
+ rect: { top: r.top, left: r.left, width: r.width, height: r.height } }, '*');
303
+ }
304
+
305
+ document.addEventListener('click', function(e) {
306
+ if (currentInputMethod !== 'pinpoint') return;
307
+ // Existing marks are handled by the mark-click listener.
308
+ if (e.target && e.target.closest && e.target.closest('.annotation-highlight[data-bind-id]')) return;
309
+ var el = resolvePinpointEl(e.target);
310
+ if (!el) return;
311
+ // Suppress the page's own behavior (links, buttons) — we're annotating.
312
+ e.preventDefault();
313
+ e.stopPropagation();
314
+ annotateElement(el);
315
+ }, true);
316
+
317
+ // Author opt-in: a plain click on any element tagged [data-annotate] pops the
318
+ // toolbar — no pinpoint mode. Lets an HTML doc (e.g. a flow graph) wire its own
319
+ // nodes to Plannotator's toolbar. Bubble phase so the page's own click handlers
320
+ // run first; an active text selection is respected, not clobbered.
321
+ document.addEventListener('click', function(e) {
322
+ if (currentInputMethod === 'pinpoint') return; // pinpoint handler covers this
323
+ var t = e.target && e.target.closest && e.target.closest('[data-annotate]');
324
+ if (!t) return;
325
+ if (e.target.closest('.annotation-highlight[data-bind-id]')) return;
326
+ var s = window.getSelection();
327
+ if (s && !s.isCollapsed && (s.toString() || '').trim()) return; // respect a drag-selection
328
+ annotateElement(t);
329
+ });
330
+
331
+ // --- Mark Click ---
332
+ document.addEventListener('click', function(e) {
333
+ var mark = e.target.closest ? e.target.closest('.annotation-highlight[data-bind-id]') : null;
334
+ if (mark) {
335
+ e.stopPropagation();
336
+ parent.postMessage({
337
+ type: PREFIX + 'mark-click',
338
+ id: mark.getAttribute('data-bind-id')
339
+ }, '*');
340
+ }
341
+ });
342
+
343
+ // --- Type-to-comment ---
344
+ // While a selection is pending, focus is inside this iframe, so the parent's
345
+ // toolbar keydown listener never sees the keystroke. Forward a single printable
346
+ // char to the parent so it can open a comment pre-filled with it.
347
+ document.addEventListener('keydown', function(e) {
348
+ if (!pendingSelection) return;
349
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
350
+ if (!e.key || e.key.length !== 1) return; // single printable char only
351
+ e.preventDefault();
352
+ parent.postMessage({ type: PREFIX + 'keytype', key: e.key }, '*');
353
+ // Hand keyboard focus back to the parent window so the comment textarea can
354
+ // take it. Blurring the <iframe> from the parent isn't enough — the inner
355
+ // document keeps focus — so the iframe must relinquish it. parent.focus() is
356
+ // allowed cross-origin (like postMessage); also drop the active element.
357
+ try { if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); } catch (ex) {}
358
+ try { parent.focus(); } catch (ex) {}
359
+ });
360
+
361
+ // --- Helpers ---
362
+
363
+ function getNodePath(node) {
364
+ var path = [];
365
+ while (node && node !== document.body) {
366
+ if (node.parentNode) {
367
+ var siblings = node.parentNode.childNodes;
368
+ var idx = 0;
369
+ for (var i = 0; i < siblings.length; i++) {
370
+ if (siblings[i] === node) { idx = i; break; }
371
+ }
372
+ path.unshift(idx);
373
+ }
374
+ node = node.parentNode;
375
+ }
376
+ return path;
377
+ }
378
+
379
+ function applyMark(id, annType, selData) {
380
+ try {
381
+ var startNode = resolveNodePath(selData.startContainerPath);
382
+ var endNode = resolveNodePath(selData.endContainerPath);
383
+ if (!startNode || !endNode) return;
384
+
385
+ var range = document.createRange();
386
+ range.setStart(startNode, selData.startOffset);
387
+ range.setEnd(endNode, selData.endOffset);
388
+ wrapRangeInMarks(range, id, annType);
389
+ } catch (ex) { /* range may be stale */ }
390
+ }
391
+
392
+ function wrapRangeInMarks(range, id, annType) {
393
+ var walker = document.createTreeWalker(
394
+ range.commonAncestorContainer.nodeType === 1 ? range.commonAncestorContainer : range.commonAncestorContainer.parentNode,
395
+ NodeFilter.SHOW_TEXT,
396
+ null
397
+ );
398
+
399
+ var textNodes = [];
400
+ while (walker.nextNode()) {
401
+ if (range.intersectsNode(walker.currentNode)) {
402
+ textNodes.push(walker.currentNode);
403
+ }
404
+ }
405
+
406
+ for (var i = 0; i < textNodes.length; i++) {
407
+ var tn = textNodes[i];
408
+ var start = (tn === range.startContainer) ? range.startOffset : 0;
409
+ var end = (tn === range.endContainer) ? range.endOffset : tn.length;
410
+ if (start >= end) continue;
411
+
412
+ var markRange = document.createRange();
413
+ markRange.setStart(tn, start);
414
+ markRange.setEnd(tn, end);
415
+
416
+ var mark = document.createElement('mark');
417
+ mark.className = 'annotation-highlight ' + annType;
418
+ mark.setAttribute('data-bind-id', id);
419
+ markRange.surroundContents(mark);
420
+ }
421
+
422
+ var rect = document.querySelector('[data-bind-id="' + id + '"]');
423
+ if (rect) {
424
+ var r = rect.getBoundingClientRect();
425
+ parent.postMessage({
426
+ type: PREFIX + 'mark-created',
427
+ id: id,
428
+ rect: { top: r.top, left: r.left, width: r.width, height: r.height }
429
+ }, '*');
430
+ }
431
+ }
432
+
433
+ function findTextAndMark(id, originalText, annType) {
434
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
435
+ var buffer = '';
436
+ var nodes = [];
437
+ while (walker.nextNode()) {
438
+ nodes.push({ node: walker.currentNode, start: buffer.length });
439
+ buffer += walker.currentNode.textContent;
440
+ }
441
+ var idx = buffer.indexOf(originalText);
442
+ if (idx === -1) return false;
443
+
444
+ var endIdx = idx + originalText.length;
445
+ var slices = [];
446
+ for (var i = 0; i < nodes.length; i++) {
447
+ var entry = nodes[i];
448
+ var nodeEnd = entry.start + entry.node.length;
449
+ if (nodeEnd <= idx) continue;
450
+ if (entry.start >= endIdx) break;
451
+
452
+ var start = Math.max(0, idx - entry.start);
453
+ var end = Math.min(entry.node.length, endIdx - entry.start);
454
+ if (start >= end) continue;
455
+ slices.push({ node: entry.node, start: start, end: end });
456
+ }
457
+ for (var j = slices.length - 1; j >= 0; j--) {
458
+ try {
459
+ var s = slices[j];
460
+ var markRange = document.createRange();
461
+ markRange.setStart(s.node, s.start);
462
+ markRange.setEnd(s.node, s.end);
463
+
464
+ var mark = document.createElement('mark');
465
+ mark.className = 'annotation-highlight ' + annType;
466
+ mark.setAttribute('data-bind-id', id);
467
+ markRange.surroundContents(mark);
468
+ } catch (ex) { /* node may have been mutated by a prior wrap */ }
469
+ }
470
+ return slices.length > 0;
471
+ }
472
+
473
+ function removeMark(id) {
474
+ var marks = document.querySelectorAll('[data-bind-id="' + id + '"]');
475
+ for (var i = marks.length - 1; i >= 0; i--) unwrapMark(marks[i]);
476
+ }
477
+
478
+ function unwrapMark(mark) {
479
+ var parent = mark.parentNode;
480
+ while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
481
+ parent.removeChild(mark);
482
+ parent.normalize();
483
+ }
484
+
485
+ function resolveNodePath(path) {
486
+ var node = document.body;
487
+ for (var i = 0; i < path.length; i++) {
488
+ if (!node.childNodes[path[i]]) return null;
489
+ node = node.childNodes[path[i]];
490
+ }
491
+ return node;
492
+ }
493
+
494
+ function onReady() {
495
+ if (typeof ResizeObserver !== 'undefined' && document.body) {
496
+ new ResizeObserver(postResize).observe(document.body);
497
+ }
498
+ parent.postMessage({ type: PREFIX + 'ready' }, '*');
499
+ }
500
+ if (document.readyState === 'loading') {
501
+ document.addEventListener('DOMContentLoaded', onReady);
502
+ } else {
503
+ onReady();
504
+ }
505
+ })();`;
@@ -0,0 +1 @@
1
+ export { HtmlViewer, type HtmlViewerProps } from "./HtmlViewer";