@plannotator/ui 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/components/ActionMenu.tsx +6 -1
- package/components/AgentsTab.tsx +8 -9
- package/components/AnalysisLayerToggle.tsx +48 -0
- package/components/AnnotationPanel.tsx +62 -32
- package/components/AnnotationToolbar.tsx +28 -8
- package/components/AnnotationToolstrip.tsx +9 -0
- package/components/CommentPopover.tsx +212 -46
- package/components/ConfirmDialog.tsx +42 -28
- package/components/InlineMarkdown.tsx +3 -2
- package/components/KeyboardShortcuts.tsx +9 -0
- package/components/Landing.tsx +1 -1
- package/components/LookAndFeelAnnouncementDialog.tsx +147 -178
- package/components/MarkdownEditor/embedPicker.ts +349 -0
- package/components/MarkdownEditor.tsx +12 -0
- package/components/ModeToggle.tsx +2 -1
- package/components/PermissionModeSetup.tsx +24 -5
- package/components/PinpointOverlay.tsx +11 -6
- package/components/PlanHeaderMenu.tsx +140 -1
- package/components/SearchableSelect.tsx +2 -0
- package/components/Settings.tsx +136 -10
- package/components/SkillReferenceMenu.tsx +9 -0
- package/components/StickyHeaderLane.tsx +9 -2
- package/components/TableOfContents.tsx +9 -4
- package/components/TextShimmer.tsx +8 -5
- package/components/ThemeProvider.tsx +43 -1
- package/components/ThemeTab.tsx +52 -1
- package/components/Tooltip.tsx +3 -1
- package/components/Viewer.tsx +19 -5
- package/components/VimTargetReticle.tsx +12 -4
- package/components/ai/DocumentAIChatPanel.tsx +1 -0
- package/components/core/button.tsx +14 -6
- package/components/html-viewer/HtmlViewer.tsx +208 -39
- package/components/html-viewer/bridge-script.ts +319 -65
- package/components/html-viewer/composerYield.ts +1 -51
- package/components/html-viewer/useHtmlAnnotation.ts +146 -57
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -0
- package/components/sidebar/FileBrowser.tsx +17 -5
- package/components/sidebar/SidebarContainer.tsx +124 -28
- package/components/ui/button.tsx +10 -8
- package/components/ui/dialog.tsx +35 -25
- package/config/index.ts +6 -1
- package/config/reviewView.ts +42 -9
- package/config/settings.ts +141 -0
- package/hooks/useAIProviderConfig.ts +8 -7
- package/hooks/useActiveSection.ts +6 -4
- package/hooks/useAgentJobs.ts +3 -0
- package/hooks/useAnnotationHighlighter.ts +20 -0
- package/hooks/useIsMobile.ts +37 -0
- package/hooks/useLinkedDoc.ts +7 -0
- package/hooks/useScrollViewport.ts +74 -0
- package/hooks/useViewportEnvironment.ts +350 -0
- package/package.json +3 -2
- package/shortcuts/index.ts +3 -0
- package/shortcuts/plan-review/annotationMode.shortcuts.ts +91 -0
- package/shortcuts/plan-review/documentView.shortcuts.ts +26 -0
- package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +24 -0
- package/styles.css +1 -1
- package/theme.css +229 -0
- package/types.ts +34 -0
- package/utils/annotateAgentTerminal.ts +36 -5
- package/utils/blockTargeting.ts +6 -3
- package/utils/composerYield.ts +45 -0
- package/utils/htmlChrome.ts +20 -16
- package/utils/lookAndFeelAnnouncement.ts +12 -8
- package/utils/markdownExtensions.ts +57 -0
- package/utils/parser.ts +37 -2
- package/utils/vimNavigation.ts +4 -1
- package/utils/vimScroll.ts +9 -4
- package/utils/wideMode.ts +20 -0
- package/components/PlanAIAnnouncementDialog.tsx +0 -187
- package/components/VimModeAnnouncementDialog.tsx +0 -557
- package/utils/planAIAnnouncement.ts +0 -17
- package/utils/vimModeAnnouncement.ts +0 -23
|
@@ -213,19 +213,83 @@ body[data-plannotator-vim-focus-owner]:focus {
|
|
|
213
213
|
export const BRIDGE_SCRIPT = `(function() {
|
|
214
214
|
var PREFIX = 'plannotator-bridge-';
|
|
215
215
|
|
|
216
|
+
// --- Live mode (proxied local app) ---
|
|
217
|
+
// Srcdoc sessions carry no config: LIVE stays null and every branch below is
|
|
218
|
+
// inert, keeping srcdoc behavior byte-for-byte identical. The proxy injects
|
|
219
|
+
// this script into EVERY HTML response, so a frame gate deactivates the
|
|
220
|
+
// bridge when the proxied page is opened directly (not framed) and inside
|
|
221
|
+
// nested same-origin subframes: only the frame whose parent IS the editor
|
|
222
|
+
// may run.
|
|
223
|
+
var LIVE = window.__plannotatorLiveConfig || null;
|
|
224
|
+
if (LIVE && (window === window.parent || window.parent !== window.top)) return;
|
|
225
|
+
// The server cannot know which origin form (localhost or 127.0.0.1) the
|
|
226
|
+
// editor tab was opened on, so live outbound messages are posted once per
|
|
227
|
+
// listed editor origin: the browser delivers only the post whose
|
|
228
|
+
// targetOrigin matches the parent document and silently drops the rest,
|
|
229
|
+
// so exactly one copy arrives. Inbound accepts any listed origin. Srcdoc
|
|
230
|
+
// keeps targetOrigin '*' and no token.
|
|
231
|
+
function isEditorOrigin(origin) {
|
|
232
|
+
if (!LIVE) return true;
|
|
233
|
+
var list = LIVE.editorOrigins || [];
|
|
234
|
+
for (var i = 0; i < list.length; i++) { if (list[i] === origin) return true; }
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
function postToParent(msg) {
|
|
238
|
+
if (LIVE) {
|
|
239
|
+
msg.token = LIVE.token;
|
|
240
|
+
var origins = LIVE.editorOrigins || [];
|
|
241
|
+
for (var o = 0; o < origins.length; o++) parent.postMessage(msg, origins[o]);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
parent.postMessage(msg, '*');
|
|
245
|
+
}
|
|
246
|
+
// Page identity for multi-page live sessions: annotations are stamped with
|
|
247
|
+
// the page they were made on, and restore filters to the current page.
|
|
248
|
+
function currentPageUrl() {
|
|
249
|
+
return (location.pathname + location.search).slice(0, 2048);
|
|
250
|
+
}
|
|
251
|
+
if (LIVE) {
|
|
252
|
+
// SPA navigation: report history changes so the parent can re-filter the
|
|
253
|
+
// restored set. Coalesced with a microtask flag so a pushState burst posts
|
|
254
|
+
// once. Full reloads need nothing: the proxy re-injects and the fresh
|
|
255
|
+
// document posts ready again.
|
|
256
|
+
var pageChangeQueued = false;
|
|
257
|
+
var postPageChange = function() {
|
|
258
|
+
if (pageChangeQueued) return;
|
|
259
|
+
pageChangeQueued = true;
|
|
260
|
+
Promise.resolve().then(function() {
|
|
261
|
+
pageChangeQueued = false;
|
|
262
|
+
postToParent({ type: PREFIX + 'page-change', pageUrl: currentPageUrl() });
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
var wrapHistory = function(name) {
|
|
266
|
+
var original = history[name];
|
|
267
|
+
if (typeof original !== 'function') return;
|
|
268
|
+
history[name] = function() {
|
|
269
|
+
var result = original.apply(this, arguments);
|
|
270
|
+
postPageChange();
|
|
271
|
+
return result;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
wrapHistory('pushState');
|
|
275
|
+
wrapHistory('replaceState');
|
|
276
|
+
window.addEventListener('popstate', postPageChange);
|
|
277
|
+
}
|
|
278
|
+
|
|
216
279
|
// --- Theme ---
|
|
217
280
|
// The author owns this document. Unless it opted in to host theming
|
|
218
281
|
// (hostTheme), only viewer-namespaced --pn-* properties may be written to its
|
|
219
282
|
// root, and its class list is never touched.
|
|
220
283
|
window.addEventListener('message', function(e) {
|
|
221
284
|
if (e.source !== parent) return;
|
|
285
|
+
if (LIVE && (!isEditorOrigin(e.origin) || !e.data || e.data.token !== LIVE.token)) return;
|
|
222
286
|
if (!e.data) return;
|
|
223
287
|
if (e.data.type === PREFIX + 'set-vim-help') {
|
|
224
288
|
vimHelpOpen = !!e.data.open;
|
|
225
|
-
|
|
289
|
+
postToParent({
|
|
226
290
|
type: PREFIX + 'vim-help',
|
|
227
291
|
open: vimHelpOpen
|
|
228
|
-
}
|
|
292
|
+
});
|
|
229
293
|
return;
|
|
230
294
|
}
|
|
231
295
|
if (e.data.type !== PREFIX + 'theme') return;
|
|
@@ -246,11 +310,12 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
246
310
|
// --- Resize ---
|
|
247
311
|
var lastHeight = 0;
|
|
248
312
|
function postResize() {
|
|
313
|
+
if (LIVE) return; // live surfaces render full-viewport; the parent ignores height
|
|
249
314
|
if (!document.body) return;
|
|
250
315
|
var h = document.body.scrollHeight;
|
|
251
316
|
if (h !== lastHeight) {
|
|
252
317
|
lastHeight = h;
|
|
253
|
-
|
|
318
|
+
postToParent({ type: PREFIX + 'resize', height: h });
|
|
254
319
|
}
|
|
255
320
|
}
|
|
256
321
|
window.addEventListener('load', postResize);
|
|
@@ -274,7 +339,28 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
274
339
|
var pendingMultiTargets = []; // { key, el, anchor, label, text, box }
|
|
275
340
|
var multiTargetSeq = 0;
|
|
276
341
|
var MAX_MULTI_TARGETS = 16;
|
|
277
|
-
|
|
342
|
+
// Live mode clamps the INPUT METHOD to pinpoint (click = element). Text
|
|
343
|
+
// drag-selection is a separate, always-on channel — see the mouseup handler
|
|
344
|
+
// — so the clamp only decides what a plain click does, never whether text
|
|
345
|
+
// can be selected and commented.
|
|
346
|
+
var currentInputMethod = LIVE ? 'pinpoint' : 'drag'; // 'drag' = text selection, 'pinpoint' = click an element
|
|
347
|
+
// Interact/Annotate mode. While INACTIVE the bridge keeps clicks native: no
|
|
348
|
+
// pinpoint capture, no hover outline, no [data-annotate] click, no
|
|
349
|
+
// committed-highlight click interception — clicks, forms, and SPA
|
|
350
|
+
// navigation reach the page untouched. Text drag-selection commenting stays
|
|
351
|
+
// LIVE in both modes (a real drag opens the comment toolbar even in
|
|
352
|
+
// Interact), committed overlay artifacts stay visible in both modes, and
|
|
353
|
+
// marker buttons keep their own clicks. BOTH surface kinds start ARMED —
|
|
354
|
+
// Esc (or the header pen) drops to Interact.
|
|
355
|
+
var annotateModeActive = true;
|
|
356
|
+
function updatePinpointCursor() {
|
|
357
|
+
if (!document.body) return;
|
|
358
|
+
if (annotateModeActive && currentInputMethod === 'pinpoint') {
|
|
359
|
+
document.body.setAttribute('data-plannotator-pinpoint-cursor', '');
|
|
360
|
+
} else {
|
|
361
|
+
document.body.removeAttribute('data-plannotator-pinpoint-cursor');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
278
364
|
var pinpointHover = null;
|
|
279
365
|
var vimEnabled = false;
|
|
280
366
|
var vimHudEnabled = false;
|
|
@@ -295,10 +381,9 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
295
381
|
// and immediately clear it. This flag suppresses that one trailing clear.
|
|
296
382
|
var skipNextClear = false;
|
|
297
383
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
});
|
|
384
|
+
// Drag-selection commenting is handled by the merged capture-phase mouseup
|
|
385
|
+
// listener below (after the drag-yield state it reads) — it is ALWAYS live:
|
|
386
|
+
// both surfaces, armed or Interact, drag or pinpoint input method.
|
|
302
387
|
|
|
303
388
|
// The page fully controls element text, so everything posted as a selection
|
|
304
389
|
// is bounded here before it crosses the bridge (the parent enforces the same
|
|
@@ -322,7 +407,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
322
407
|
// Trailing clear from a plain-click element annotation — consume it once.
|
|
323
408
|
if (skipNextClear) { skipNextClear = false; return; }
|
|
324
409
|
if (pendingSelection) {
|
|
325
|
-
|
|
410
|
+
postToParent({ type: PREFIX + 'selection-clear' });
|
|
326
411
|
pendingSelection = null;
|
|
327
412
|
pendingRange = null;
|
|
328
413
|
clearMultiTargets();
|
|
@@ -350,7 +435,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
350
435
|
endOffset: range.endOffset
|
|
351
436
|
};
|
|
352
437
|
|
|
353
|
-
|
|
438
|
+
postToParent({
|
|
354
439
|
type: PREFIX + 'selection',
|
|
355
440
|
text: text,
|
|
356
441
|
modeOverride: modeOverride || undefined,
|
|
@@ -359,7 +444,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
359
444
|
targetKey: (extras && extras.targetKey) || undefined,
|
|
360
445
|
targetLabel: (extras && extras.targetLabel) || undefined,
|
|
361
446
|
rect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
|
|
362
|
-
}
|
|
447
|
+
});
|
|
363
448
|
renderAnnotationOverlay(); // draft selection highlight (overlay-projected)
|
|
364
449
|
return true;
|
|
365
450
|
}
|
|
@@ -382,17 +467,17 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
382
467
|
// Drag selections keep the existing close-on-scroll-out behavior.
|
|
383
468
|
if (pendingPinViaPinpoint || pendingMultiTargets.length > 0) return;
|
|
384
469
|
// Selection scrolled out of view — close the toolbar (matches markdown).
|
|
385
|
-
|
|
470
|
+
postToParent({ type: PREFIX + 'selection-clear' });
|
|
386
471
|
pendingSelection = null;
|
|
387
472
|
pendingRange = null;
|
|
388
473
|
clearPendingPin();
|
|
389
474
|
renderAnnotationOverlay();
|
|
390
475
|
return;
|
|
391
476
|
}
|
|
392
|
-
|
|
477
|
+
postToParent({
|
|
393
478
|
type: PREFIX + 'selection-rect',
|
|
394
479
|
rect: { top: r.top, left: r.left, width: r.width, height: r.height }
|
|
395
|
-
}
|
|
480
|
+
});
|
|
396
481
|
}
|
|
397
482
|
window.addEventListener('scroll', function() {
|
|
398
483
|
if (!pendingSelection) return;
|
|
@@ -402,6 +487,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
402
487
|
// --- Mark Creation ---
|
|
403
488
|
window.addEventListener('message', function(e) {
|
|
404
489
|
if (e.source !== parent) return;
|
|
490
|
+
if (LIVE && (!isEditorOrigin(e.origin) || !e.data || e.data.token !== LIVE.token)) return;
|
|
405
491
|
if (!e.data || !e.data.type) return;
|
|
406
492
|
var type = e.data.type;
|
|
407
493
|
|
|
@@ -483,11 +569,11 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
483
569
|
e.data.anchor,
|
|
484
570
|
e.data.additionalAnchors
|
|
485
571
|
);
|
|
486
|
-
|
|
572
|
+
postToParent({
|
|
487
573
|
type: PREFIX + 'mark-applied',
|
|
488
574
|
id: e.data.id,
|
|
489
575
|
success: found
|
|
490
|
-
}
|
|
576
|
+
});
|
|
491
577
|
}
|
|
492
578
|
|
|
493
579
|
else if (type === PREFIX + 'remove-mark') {
|
|
@@ -578,18 +664,44 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
578
664
|
}
|
|
579
665
|
|
|
580
666
|
else if (type === PREFIX + 'set-input-method') {
|
|
581
|
-
|
|
667
|
+
// Live mode clamps the input method to pinpoint (what a plain click
|
|
668
|
+
// does); text drag-selection commenting stays live regardless.
|
|
669
|
+
currentInputMethod = (LIVE || e.data.method === 'pinpoint') ? 'pinpoint' : 'drag';
|
|
582
670
|
if (currentInputMethod === 'pinpoint') {
|
|
583
671
|
clearHoverHighlight(); // pinpoint owns clicks; drop the select affordance (and any pending hit test)
|
|
584
|
-
if (document.body) document.body.setAttribute('data-plannotator-pinpoint-cursor', '');
|
|
585
672
|
} else {
|
|
586
|
-
if (document.body) document.body.removeAttribute('data-plannotator-pinpoint-cursor');
|
|
587
673
|
clearPinpointHover();
|
|
588
674
|
}
|
|
675
|
+
updatePinpointCursor(); // cursor affordance is mode-gated: never in Interact
|
|
589
676
|
if (vimEnabled) updateVimUi();
|
|
590
677
|
}
|
|
591
678
|
|
|
679
|
+
else if (type === PREFIX + 'set-annotate-mode') {
|
|
680
|
+
var nextAnnotateActive = e.data.active === true;
|
|
681
|
+
if (nextAnnotateActive !== annotateModeActive) {
|
|
682
|
+
annotateModeActive = nextAnnotateActive;
|
|
683
|
+
if (!annotateModeActive) {
|
|
684
|
+
// Disarm tears down every PENDING affordance; committed markers and
|
|
685
|
+
// highlights stay visible, and marker buttons keep their clicks.
|
|
686
|
+
if (pendingSelection) postToParent({ type: PREFIX + 'selection-clear' });
|
|
687
|
+
pendingSelection = null;
|
|
688
|
+
pendingRange = null;
|
|
689
|
+
skipNextClear = false;
|
|
690
|
+
clearMultiTargets();
|
|
691
|
+
clearPendingPin();
|
|
692
|
+
try { window.getSelection().removeAllRanges(); } catch (ex) {}
|
|
693
|
+
clearPinpointHover();
|
|
694
|
+
clearHoverHighlight();
|
|
695
|
+
renderAnnotationOverlay();
|
|
696
|
+
}
|
|
697
|
+
updatePinpointCursor();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
592
701
|
else if (type === PREFIX + 'set-vim-mode') {
|
|
702
|
+
// Vim is off in live mode: it writes classes onto author elements and
|
|
703
|
+
// captures keys the app needs.
|
|
704
|
+
if (LIVE) return;
|
|
593
705
|
var wasVimEnabled = vimEnabled;
|
|
594
706
|
var wasVimHudEnabled = vimHudEnabled;
|
|
595
707
|
vimEnabled = e.data.enabled === true;
|
|
@@ -1096,6 +1208,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1096
1208
|
document.addEventListener('mousemove', function(e) {
|
|
1097
1209
|
lastPointer = { x: e.clientX, y: e.clientY };
|
|
1098
1210
|
updateDragYield(e);
|
|
1211
|
+
if (!annotateModeActive) return; // Interact mode: no hover affordances
|
|
1099
1212
|
if (currentInputMethod !== 'pinpoint') {
|
|
1100
1213
|
// Click-to-select hover affordance (drag mode owns highlight clicks):
|
|
1101
1214
|
// cheap cached-rect hit test, rAF-throttled, cleared while a draft or
|
|
@@ -1108,6 +1221,9 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1108
1221
|
return;
|
|
1109
1222
|
}
|
|
1110
1223
|
if (vimEnabled && vimPhase !== 'inactive') return;
|
|
1224
|
+
// Mid-drag (text selection in progress) the element hover box is noise:
|
|
1225
|
+
// the drag owns the surface until mouseup resolves it.
|
|
1226
|
+
if (dragYieldActive) { clearPinpointHover(); return; }
|
|
1111
1227
|
// Hit-test at the pointer (e.target only backstops engines without
|
|
1112
1228
|
// elementFromPoint) so the same code path serves the scroll re-hit-test.
|
|
1113
1229
|
updatePinpointHover(e.clientX, e.clientY, e.target);
|
|
@@ -1137,7 +1253,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1137
1253
|
renderAnnotationOverlay();
|
|
1138
1254
|
// Scroll under a stationary pointer moves the committed rects: re-run
|
|
1139
1255
|
// the cached-rect hover test so the affordance tracks reality.
|
|
1140
|
-
if (currentInputMethod !== 'pinpoint' && lastPointer && !pendingPinEl && !pendingSelection) {
|
|
1256
|
+
if (annotateModeActive && currentInputMethod !== 'pinpoint' && lastPointer && !pendingPinEl && !pendingSelection) {
|
|
1141
1257
|
setHoverHighlight(committedHighlightIdAtCached(lastPointer.x, lastPointer.y));
|
|
1142
1258
|
}
|
|
1143
1259
|
positionMultiTargetBoxes();
|
|
@@ -1145,7 +1261,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1145
1261
|
positionPinpointBox(pendingPinEl);
|
|
1146
1262
|
return;
|
|
1147
1263
|
}
|
|
1148
|
-
if (currentInputMethod !== 'pinpoint') return;
|
|
1264
|
+
if (!annotateModeActive || currentInputMethod !== 'pinpoint') return;
|
|
1149
1265
|
if (vimEnabled && vimPhase !== 'inactive') return;
|
|
1150
1266
|
if (lastPointer) {
|
|
1151
1267
|
updatePinpointHover(lastPointer.x, lastPointer.y, pinpointHover);
|
|
@@ -1259,15 +1375,21 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1259
1375
|
}
|
|
1260
1376
|
}
|
|
1261
1377
|
|
|
1262
|
-
// Drag-selection marker yield: while a text drag is in progress
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
//
|
|
1267
|
-
//
|
|
1268
|
-
//
|
|
1378
|
+
// Drag-selection marker yield: while a text drag is in progress, placed
|
|
1379
|
+
// markers drop pointer input (the same data-pn-hittest CSS the hit-test
|
|
1380
|
+
// yield uses) so a 25px bubble sitting over the text cannot capture the
|
|
1381
|
+
// selection mid-drag. Armed only by a >4px move with the primary button
|
|
1382
|
+
// held from a non-overlay mousedown, so marker clicks (mousedown ON the
|
|
1383
|
+
// marker) and plain click-to-select (no drag) are untouched; disarmed on
|
|
1384
|
+
// mouseup or when the button is seen released. The same >4px arming is
|
|
1385
|
+
// what tells the mouseup handler below that a REAL drag happened, which
|
|
1386
|
+
// is how drag-selection commenting stays live in pinpoint/armed mode
|
|
1387
|
+
// without a plain pinpoint click re-posting its own selection.
|
|
1269
1388
|
var dragYieldStart = null;
|
|
1270
1389
|
var dragYieldActive = false;
|
|
1390
|
+
// True between a drag's terminating mouseup and the next mousedown: the
|
|
1391
|
+
// click event that follows a completed drag must not pinpoint-annotate.
|
|
1392
|
+
var dragEndedClick = false;
|
|
1271
1393
|
function endDragYield() {
|
|
1272
1394
|
dragYieldStart = null;
|
|
1273
1395
|
if (dragYieldActive) {
|
|
@@ -1292,13 +1414,37 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1292
1414
|
}
|
|
1293
1415
|
}
|
|
1294
1416
|
document.addEventListener('mousedown', function(e) {
|
|
1295
|
-
|
|
1417
|
+
dragEndedClick = false;
|
|
1296
1418
|
if (e.button !== 0) return;
|
|
1297
1419
|
if (isViewerOverlayNode(e.target)) return;
|
|
1298
1420
|
dragYieldStart = { x: e.clientX, y: e.clientY };
|
|
1299
1421
|
}, true);
|
|
1300
1422
|
document.addEventListener('mouseup', function() {
|
|
1423
|
+
// Text drag-selection commenting is ALWAYS live: both surfaces, armed or
|
|
1424
|
+
// Interact. In armed pinpoint a plain click belongs to the pinpoint
|
|
1425
|
+
// handler — only a drag that actually PRODUCED a text selection owns its
|
|
1426
|
+
// trailing click, so annotateElement's own selection is never re-posted
|
|
1427
|
+
// by its trailing mouseup. The >4px drift alone is NOT enough to arm the
|
|
1428
|
+
// click suppression: a drifted click (common on trackpads) that selected
|
|
1429
|
+
// nothing must stay a click, or armed pinpoint would silently swallow it
|
|
1430
|
+
// while the unprevented click leaks through to the page. Browsers clear
|
|
1431
|
+
// a prior selection on the mousedown that starts the drag, so the
|
|
1432
|
+
// selection observed here is the one THIS drag produced. Everywhere else
|
|
1433
|
+
// the classic always-schedule behavior stays: handleSelection only acts
|
|
1434
|
+
// on a real selection, posts the clear that dismisses a stale draft, and
|
|
1435
|
+
// never preventDefaults — a plain click is never swallowed.
|
|
1436
|
+
var dragged = dragYieldActive;
|
|
1301
1437
|
endDragYield();
|
|
1438
|
+
var draggedSelection = false;
|
|
1439
|
+
if (dragged) {
|
|
1440
|
+
try {
|
|
1441
|
+
var s = window.getSelection();
|
|
1442
|
+
draggedSelection = !!(s && !s.isCollapsed && s.rangeCount && (s.toString() || '').trim());
|
|
1443
|
+
} catch (ex) {}
|
|
1444
|
+
}
|
|
1445
|
+
dragEndedClick = draggedSelection;
|
|
1446
|
+
if (annotateModeActive && currentInputMethod === 'pinpoint' && !draggedSelection) return;
|
|
1447
|
+
setTimeout(handleSelection, 10);
|
|
1302
1448
|
}, true);
|
|
1303
1449
|
|
|
1304
1450
|
// One record per committed annotation. Its targets are live projections;
|
|
@@ -1362,7 +1508,11 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1362
1508
|
var key = JSON.stringify(combined);
|
|
1363
1509
|
if (key === lastUnanchoredKey) return;
|
|
1364
1510
|
lastUnanchoredKey = key;
|
|
1365
|
-
|
|
1511
|
+
// postToParent, not a raw '*' post: live sessions stamp the session
|
|
1512
|
+
// token and post only to the listed editor origins, and the parent
|
|
1513
|
+
// drops untokened live messages — a raw post would silently disable
|
|
1514
|
+
// unanchored reporting exactly where restores fail most (live pages).
|
|
1515
|
+
postToParent({ type: PREFIX + 'unanchored', ids: combined });
|
|
1366
1516
|
}
|
|
1367
1517
|
|
|
1368
1518
|
function validNormalizedPoint(p) {
|
|
@@ -1502,9 +1652,47 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
1502
1652
|
}
|
|
1503
1653
|
}
|
|
1504
1654
|
}
|
|
1655
|
+
if (!record.targets.length && LIVE) {
|
|
1656
|
+
// Live pages render late (lazy routes, data-dependent trees): a
|
|
1657
|
+
// restore that resolves nothing keeps its record, seeded with
|
|
1658
|
+
// unresolved placeholder targets built from the durable params, so
|
|
1659
|
+
// the mutation-driven reconcile re-acquires them when the elements
|
|
1660
|
+
// appear. Srcdoc documents are static (nothing would ever
|
|
1661
|
+
// re-resolve), so the record is dropped below exactly as before.
|
|
1662
|
+
if (anchor) {
|
|
1663
|
+
record.targets.push({
|
|
1664
|
+
kind: 'element',
|
|
1665
|
+
element: null,
|
|
1666
|
+
anchor: anchor,
|
|
1667
|
+
point: normalizedPointOf(anchor, null)
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
if (originalText) {
|
|
1671
|
+
record.targets.push({
|
|
1672
|
+
kind: 'range',
|
|
1673
|
+
range: null,
|
|
1674
|
+
text: originalText,
|
|
1675
|
+
markerless: !!anchor
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
if (additionalAnchors && additionalAnchors.length) {
|
|
1679
|
+
var lateCount = Math.min(additionalAnchors.length, MAX_MULTI_TARGETS);
|
|
1680
|
+
for (var lateIndex = 0; lateIndex < lateCount; lateIndex++) {
|
|
1681
|
+
if (!additionalAnchors[lateIndex]) continue;
|
|
1682
|
+
record.targets.push({
|
|
1683
|
+
kind: 'element',
|
|
1684
|
+
element: null,
|
|
1685
|
+
anchor: additionalAnchors[lateIndex],
|
|
1686
|
+
point: normalizedPointOf(additionalAnchors[lateIndex], null)
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1505
1691
|
if (!record.targets.length) {
|
|
1506
1692
|
// The record is removed (nothing to retry), so the per-pass dead scan
|
|
1507
1693
|
// cannot see this id: track it separately for the unanchored report.
|
|
1694
|
+
// Live sessions only reach here when the durable params seeded no
|
|
1695
|
+
// placeholder targets at all (nothing will ever re-resolve).
|
|
1508
1696
|
removeAnnRecord(id);
|
|
1509
1697
|
restoreFailedIds.add(id);
|
|
1510
1698
|
}
|
|
@@ -2067,7 +2255,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2067
2255
|
btn.addEventListener('click', function(clickEvent) {
|
|
2068
2256
|
clickEvent.preventDefault();
|
|
2069
2257
|
clickEvent.stopPropagation();
|
|
2070
|
-
|
|
2258
|
+
postToParent({ type: PREFIX + 'mark-click', id: annId });
|
|
2071
2259
|
});
|
|
2072
2260
|
overlayNodes.add(btn);
|
|
2073
2261
|
return btn;
|
|
@@ -2608,7 +2796,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2608
2796
|
clearPendingPin();
|
|
2609
2797
|
try { window.getSelection().removeAllRanges(); } catch (ex) {}
|
|
2610
2798
|
renderAnnotationOverlay();
|
|
2611
|
-
if (echo)
|
|
2799
|
+
if (echo) postToParent({ type: PREFIX + 'multi-target-removed', key: key });
|
|
2612
2800
|
return;
|
|
2613
2801
|
}
|
|
2614
2802
|
var next = pendingMultiTargets.shift();
|
|
@@ -2628,14 +2816,14 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2628
2816
|
mainBox.classList.remove('pn-pin-enter');
|
|
2629
2817
|
if (pendingPinEl && pendingPinEl.isConnected) positionPinpointBox(pendingPinEl);
|
|
2630
2818
|
renderAnnotationOverlay();
|
|
2631
|
-
if (echo)
|
|
2819
|
+
if (echo) postToParent({ type: PREFIX + 'multi-target-removed', key: key });
|
|
2632
2820
|
return;
|
|
2633
2821
|
}
|
|
2634
2822
|
for (var i = 0; i < pendingMultiTargets.length; i++) {
|
|
2635
2823
|
if (pendingMultiTargets[i].key === key) {
|
|
2636
2824
|
destroyMultiTargetBox(pendingMultiTargets[i].box);
|
|
2637
2825
|
pendingMultiTargets.splice(i, 1);
|
|
2638
|
-
if (echo)
|
|
2826
|
+
if (echo) postToParent({ type: PREFIX + 'multi-target-removed', key: key });
|
|
2639
2827
|
return;
|
|
2640
2828
|
}
|
|
2641
2829
|
}
|
|
@@ -2678,13 +2866,13 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2678
2866
|
var key = makeTargetKey();
|
|
2679
2867
|
var box = createMultiTargetBox(el);
|
|
2680
2868
|
pendingMultiTargets.push({ key: key, el: el, anchor: anchor, label: label, text: text, point: point, box: box });
|
|
2681
|
-
|
|
2869
|
+
postToParent({
|
|
2682
2870
|
type: PREFIX + 'multi-target-added',
|
|
2683
2871
|
key: key,
|
|
2684
2872
|
label: label,
|
|
2685
2873
|
text: text,
|
|
2686
2874
|
anchor: anchor || undefined
|
|
2687
|
-
}
|
|
2875
|
+
});
|
|
2688
2876
|
}
|
|
2689
2877
|
|
|
2690
2878
|
/** Chip hover in the composer: flash the corresponding pinned outline. */
|
|
@@ -2748,12 +2936,12 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2748
2936
|
pointerRelayRaf = requestAnimationFrame(function() {
|
|
2749
2937
|
pointerRelayRaf = 0;
|
|
2750
2938
|
if (!pendingPinEl || !pointerRelayPos) return;
|
|
2751
|
-
|
|
2939
|
+
postToParent({
|
|
2752
2940
|
type: PREFIX + 'pointer',
|
|
2753
2941
|
x: pointerRelayPos.x,
|
|
2754
2942
|
y: pointerRelayPos.y,
|
|
2755
2943
|
shift: pointerRelayPos.shift
|
|
2756
|
-
}
|
|
2944
|
+
});
|
|
2757
2945
|
});
|
|
2758
2946
|
}
|
|
2759
2947
|
|
|
@@ -2823,22 +3011,31 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2823
3011
|
pendingSelection = { element: true };
|
|
2824
3012
|
pendingRange = null;
|
|
2825
3013
|
skipNextClear = true; // don't let this click's mouseup clear the toolbar we just opened
|
|
2826
|
-
|
|
3014
|
+
postToParent({ type: PREFIX + 'selection', text: elText,
|
|
2827
3015
|
modeOverride: modeOverride || undefined,
|
|
2828
3016
|
anchor: pendingPinAnchor || undefined,
|
|
2829
3017
|
pinpoint: !!viaPinpoint || undefined,
|
|
2830
3018
|
targetKey: pendingPinKey || undefined,
|
|
2831
3019
|
targetLabel: pendingPinLabel || undefined,
|
|
2832
|
-
rect: { top: r.top, left: r.left, width: r.width, height: r.height } }
|
|
3020
|
+
rect: { top: r.top, left: r.left, width: r.width, height: r.height } });
|
|
2833
3021
|
return true;
|
|
2834
3022
|
}
|
|
2835
3023
|
|
|
2836
3024
|
document.addEventListener('click', function(e) {
|
|
2837
|
-
if (currentInputMethod !== 'pinpoint') return;
|
|
3025
|
+
if (!annotateModeActive || currentInputMethod !== 'pinpoint') return;
|
|
2838
3026
|
// Real placed markers (and any other viewer overlay) own their clicks —
|
|
2839
3027
|
// checked by IDENTITY, not selector, so a page element spoofing
|
|
2840
3028
|
// [data-plannotator-marker] stays an ordinary annotatable target.
|
|
2841
3029
|
if (isViewerOverlayNode(e.target)) return;
|
|
3030
|
+
// A drag that ended in this click owns the surface: the drag-selection
|
|
3031
|
+
// pass is about to post the selected text, and pinpoint-annotating the
|
|
3032
|
+
// element under the pointer would clobber it (annotateElement rewrites
|
|
3033
|
+
// the selection). Armed at mouseup only when the drag actually produced
|
|
3034
|
+
// a selection — a drifted click arms nothing and pins normally below —
|
|
3035
|
+
// and mousedown clears a prior pin's leftover selection, so stale
|
|
3036
|
+
// selection state never blocks the next plain re-pin click. One-shot:
|
|
3037
|
+
// only the drag's own trailing click is suppressed.
|
|
3038
|
+
if (dragEndedClick) { dragEndedClick = false; return; }
|
|
2842
3039
|
// Shift-click while an ARMED pinpoint draft is open: toggle the element
|
|
2843
3040
|
// in/out of the SAME draft comment instead of replacing the selection.
|
|
2844
3041
|
// Unarmed drafts (modes the parent does not mirror, e.g. quickLabel)
|
|
@@ -2865,24 +3062,51 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2865
3062
|
annotateElement(el, undefined, true, { x: e.clientX, y: e.clientY });
|
|
2866
3063
|
}, true);
|
|
2867
3064
|
|
|
2868
|
-
// Escape
|
|
2869
|
-
//
|
|
3065
|
+
// Escape ladder (outside vim, which has its own): a pending draft closes
|
|
3066
|
+
// first, then the hover outline clears, then Esc EXITS Annotate back to
|
|
3067
|
+
// Interact — the parent owns the mode, so the final rung only posts
|
|
3068
|
+
// annotate-exit and waits for set-annotate-mode to come back down.
|
|
3069
|
+
function closePendingDraft() {
|
|
3070
|
+
postToParent({ type: PREFIX + 'selection-clear' });
|
|
3071
|
+
pendingSelection = null;
|
|
3072
|
+
pendingRange = null;
|
|
3073
|
+
skipNextClear = false;
|
|
3074
|
+
clearMultiTargets();
|
|
3075
|
+
clearPendingPin();
|
|
3076
|
+
window.getSelection().removeAllRanges();
|
|
3077
|
+
renderAnnotationOverlay();
|
|
3078
|
+
}
|
|
2870
3079
|
document.addEventListener('keydown', function(e) {
|
|
2871
3080
|
if (e.key !== 'Escape' || vimEnabled) return;
|
|
3081
|
+
if (!annotateModeActive) {
|
|
3082
|
+
// Interact mode: Esc belongs to the page — except an open drag-comment
|
|
3083
|
+
// draft (drag-selection stays live in Interact) still closes first.
|
|
3084
|
+
if (pendingSelection) closePendingDraft();
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
2872
3087
|
if (pendingSelection) {
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
} else if (currentInputMethod === 'pinpoint') {
|
|
2882
|
-
clearPinpointHover();
|
|
3088
|
+
closePendingDraft();
|
|
3089
|
+
} else {
|
|
3090
|
+
// Hover-clear is NOT a rung: the hover outline is a pointer
|
|
3091
|
+
// affordance, not a state the user perceives as a step, so clearing
|
|
3092
|
+
// it and exiting to Interact happen on the SAME press. Only an open
|
|
3093
|
+
// draft earns its own press.
|
|
3094
|
+
if (currentInputMethod === 'pinpoint' && pinpointHover) clearPinpointHover();
|
|
3095
|
+
postToParent({ type: PREFIX + 'annotate-exit' });
|
|
2883
3096
|
}
|
|
2884
3097
|
});
|
|
2885
3098
|
|
|
3099
|
+
// Mod+Shift+A toggles Interact/Annotate from inside the iframe (the parent
|
|
3100
|
+
// registers the same chord, but focus usually lives in here on live apps).
|
|
3101
|
+
// Capture phase so the page cannot swallow the reserved chord; the parent
|
|
3102
|
+
// answers with set-annotate-mode.
|
|
3103
|
+
document.addEventListener('keydown', function(e) {
|
|
3104
|
+
if (!(e.metaKey || e.ctrlKey) || !e.shiftKey || e.altKey) return;
|
|
3105
|
+
if (e.key !== 'a' && e.key !== 'A') return;
|
|
3106
|
+
e.preventDefault();
|
|
3107
|
+
postToParent({ type: PREFIX + 'annotate-toggle' });
|
|
3108
|
+
}, true);
|
|
3109
|
+
|
|
2886
3110
|
// Author opt-in: a plain click on any element tagged [data-annotate] pops the
|
|
2887
3111
|
// toolbar — no pinpoint mode. Lets an HTML doc (e.g. a flow graph) wire its own
|
|
2888
3112
|
// nodes to Plannotator's toolbar. Bubble phase so the page's own click handlers
|
|
@@ -2890,6 +3114,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2890
3114
|
// a committed highlight selects the annotation instead (the pre-overlay
|
|
2891
3115
|
// handler deferred to '.annotation-highlight' the same way).
|
|
2892
3116
|
document.addEventListener('click', function(e) {
|
|
3117
|
+
if (!annotateModeActive) return; // Interact mode: [data-annotate] stays a page element
|
|
2893
3118
|
if (currentInputMethod === 'pinpoint') return; // pinpoint handler covers this
|
|
2894
3119
|
if (isViewerOverlayNode(e.target)) return; // placed markers own their clicks
|
|
2895
3120
|
var t = e.target && e.target.closest && e.target.closest('[data-annotate]');
|
|
@@ -2933,6 +3158,10 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2933
3158
|
}
|
|
2934
3159
|
|
|
2935
3160
|
document.addEventListener('click', function(e) {
|
|
3161
|
+
// Interact mode: highlight rects are pointer-transparent projections, so a
|
|
3162
|
+
// page click landing on one goes to the page. Markers (real buttons) stay
|
|
3163
|
+
// the affordance for opening a committed comment in Interact.
|
|
3164
|
+
if (!annotateModeActive) return;
|
|
2936
3165
|
if (e.shiftKey) return; // shift belongs to multi-select
|
|
2937
3166
|
if (isViewerOverlayNode(e.target)) return; // markers own their clicks
|
|
2938
3167
|
if (pendingPinEl) return; // an open pinpoint draft owns the surface
|
|
@@ -2941,7 +3170,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
2941
3170
|
var hitId = committedHighlightAt(e.clientX, e.clientY);
|
|
2942
3171
|
if (!hitId) return;
|
|
2943
3172
|
e.stopPropagation();
|
|
2944
|
-
|
|
3173
|
+
postToParent({ type: PREFIX + 'mark-click', id: hitId });
|
|
2945
3174
|
});
|
|
2946
3175
|
|
|
2947
3176
|
// --- Optional Vim navigation ---
|
|
@@ -3655,10 +3884,10 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
3655
3884
|
if (!vimEnabled) return;
|
|
3656
3885
|
if (vimHudEnabled && vimLastPostedPhase !== vimPhase) {
|
|
3657
3886
|
vimLastPostedPhase = vimPhase;
|
|
3658
|
-
|
|
3887
|
+
postToParent({
|
|
3659
3888
|
type: PREFIX + 'vim-state',
|
|
3660
3889
|
phase: vimPhase
|
|
3661
|
-
}
|
|
3890
|
+
});
|
|
3662
3891
|
}
|
|
3663
3892
|
var badge = document.querySelector('[data-plannotator-vim-badge]');
|
|
3664
3893
|
if (!vimHudEnabled && !badge) badge = getVimBadgeEl();
|
|
@@ -3720,10 +3949,10 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
3720
3949
|
|
|
3721
3950
|
function toggleVimHelp() {
|
|
3722
3951
|
vimHelpOpen = !vimHelpOpen;
|
|
3723
|
-
|
|
3952
|
+
postToParent({
|
|
3724
3953
|
type: PREFIX + 'vim-help',
|
|
3725
3954
|
open: vimHelpOpen
|
|
3726
|
-
}
|
|
3955
|
+
});
|
|
3727
3956
|
}
|
|
3728
3957
|
|
|
3729
3958
|
function clearVimUi() {
|
|
@@ -3743,10 +3972,10 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
3743
3972
|
|
|
3744
3973
|
function copyVimText(text) {
|
|
3745
3974
|
if (!text) return;
|
|
3746
|
-
|
|
3975
|
+
postToParent({
|
|
3747
3976
|
type: PREFIX + 'vim-copy',
|
|
3748
3977
|
text: text
|
|
3749
|
-
}
|
|
3978
|
+
});
|
|
3750
3979
|
}
|
|
3751
3980
|
|
|
3752
3981
|
function vimActionMode(key) {
|
|
@@ -3808,7 +4037,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
3808
4037
|
handled = true;
|
|
3809
4038
|
} else if (key === 'Escape') {
|
|
3810
4039
|
if (pendingSelection) {
|
|
3811
|
-
|
|
4040
|
+
postToParent({ type: PREFIX + 'selection-clear' });
|
|
3812
4041
|
pendingSelection = null;
|
|
3813
4042
|
pendingRange = null;
|
|
3814
4043
|
restoreVimSemanticTarget();
|
|
@@ -4025,12 +4254,12 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
4025
4254
|
vimLastActionId = vimActionId;
|
|
4026
4255
|
vimLastActionContext = vimCommandContext;
|
|
4027
4256
|
updateVimReticle();
|
|
4028
|
-
|
|
4257
|
+
postToParent({
|
|
4029
4258
|
type: PREFIX + 'vim-command',
|
|
4030
4259
|
actionId: vimActionId,
|
|
4031
4260
|
key: hudKey,
|
|
4032
4261
|
context: vimCommandContext
|
|
4033
|
-
}
|
|
4262
|
+
});
|
|
4034
4263
|
}
|
|
4035
4264
|
e.preventDefault();
|
|
4036
4265
|
e.stopImmediatePropagation();
|
|
@@ -4064,7 +4293,7 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
4064
4293
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
4065
4294
|
if (!e.key || e.key.length !== 1) return; // single printable char only
|
|
4066
4295
|
e.preventDefault();
|
|
4067
|
-
|
|
4296
|
+
postToParent({ type: PREFIX + 'keytype', key: e.key });
|
|
4068
4297
|
// Hand keyboard focus back to the parent window so the comment textarea can
|
|
4069
4298
|
// take it. Blurring the <iframe> from the parent isn't enough — the inner
|
|
4070
4299
|
// document keeps focus — so the iframe must relinquish it. parent.focus() is
|
|
@@ -4305,7 +4534,13 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
4305
4534
|
}).observe(document.body);
|
|
4306
4535
|
}
|
|
4307
4536
|
watchPageMutations();
|
|
4308
|
-
|
|
4537
|
+
// Armed is the default on both surfaces, and live sessions default to
|
|
4538
|
+
// pinpoint: show the cursor affordance immediately instead of waiting for
|
|
4539
|
+
// the parent's first set-input-method/set-annotate-mode round trip.
|
|
4540
|
+
updatePinpointCursor();
|
|
4541
|
+
var readyMsg = { type: PREFIX + 'ready' };
|
|
4542
|
+
if (LIVE) readyMsg.pageUrl = currentPageUrl();
|
|
4543
|
+
postToParent(readyMsg);
|
|
4309
4544
|
}
|
|
4310
4545
|
if (document.readyState === 'loading') {
|
|
4311
4546
|
document.addEventListener('DOMContentLoaded', onReady);
|
|
@@ -4333,3 +4568,22 @@ export const BRIDGE_SCRIPT = `(function() {
|
|
|
4333
4568
|
}
|
|
4334
4569
|
};
|
|
4335
4570
|
})();`;
|
|
4571
|
+
|
|
4572
|
+
/**
|
|
4573
|
+
* Live-mode bootstrap, prepended to BRIDGE_SCRIPT by the annotate server when
|
|
4574
|
+
* composing the proxy-served bridge body. Reads the JSON config prelude
|
|
4575
|
+
* (window.__plannotatorLiveConfig) and installs the annotation CSS that srcdoc
|
|
4576
|
+
* mode splices as a <style> tag. Runs before the bridge IIFE and before its
|
|
4577
|
+
* MutationObserver exists, so this write never feeds the reconcile loop.
|
|
4578
|
+
* Same escaping rules as BRIDGE_SCRIPT: a dependency-free string constant.
|
|
4579
|
+
*/
|
|
4580
|
+
export const LIVE_BRIDGE_BOOTSTRAP = `(function() {
|
|
4581
|
+
var config = window.__plannotatorLiveConfig;
|
|
4582
|
+
if (!config || typeof config.css !== 'string') return;
|
|
4583
|
+
try {
|
|
4584
|
+
var style = document.createElement('style');
|
|
4585
|
+
style.setAttribute('data-plannotator-live-css', '');
|
|
4586
|
+
style.appendChild(document.createTextNode(config.css));
|
|
4587
|
+
(document.head || document.documentElement).appendChild(style);
|
|
4588
|
+
} catch (ex) {}
|
|
4589
|
+
})();`;
|