@erdemtuna/doc-review 0.11.0 → 0.12.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/lib/chrome-client.js +41 -35
- package/lib/chrome.html +3 -0
- package/lib/contracts/frame.js +6 -0
- package/lib/contracts/index.js +1 -1
- package/lib/frame-controller.js +189 -17
- package/lib/frame-host.js +10 -0
- package/lib/paths.js +3 -3
- package/lib/recovery-controller.js +9 -0
- package/lib/review-palette.js +97 -0
- package/lib/sdk.js +214 -60
- package/lib/ui/chrome.css +356 -162
- package/lib/ui/chrome.js +17 -17
- package/package.json +1 -1
package/lib/sdk.js
CHANGED
|
@@ -56,6 +56,9 @@ let disposed = false;
|
|
|
56
56
|
let viewObserver = null;
|
|
57
57
|
let viewChangeTimer = null;
|
|
58
58
|
let lastObservedView = null;
|
|
59
|
+
let reviewTheme = null;
|
|
60
|
+
let themeRevision = 0;
|
|
61
|
+
let themeWarnings = 0;
|
|
59
62
|
const blockTargets = new Map();
|
|
60
63
|
const blockMarkers = new Map();
|
|
61
64
|
/** True when the page's own scripts rewrote the DOM before any user edit. */
|
|
@@ -70,22 +73,115 @@ const shadow = host.attachShadow({ mode: "open" });
|
|
|
70
73
|
shadow.innerHTML = `
|
|
71
74
|
<style>
|
|
72
75
|
:host { all: initial; }
|
|
76
|
+
:host(:not([data-review-theme])) { visibility: hidden !important; }
|
|
77
|
+
:host(:not([data-review-theme])) * { visibility: hidden !important; pointer-events: none !important; }
|
|
78
|
+
/* REVIEW_THEME_SHADOW_START */
|
|
79
|
+
:host([data-review-theme="light"]) {
|
|
80
|
+
color-scheme: light;
|
|
81
|
+
--review-background: #F5F2EA;
|
|
82
|
+
--review-foreground: #292E2B;
|
|
83
|
+
--review-card: #FFFDF7;
|
|
84
|
+
--review-card-foreground: #292E2B;
|
|
85
|
+
--review-popover: #FFFDF7;
|
|
86
|
+
--review-popover-foreground: #292E2B;
|
|
87
|
+
--review-primary: #17685F;
|
|
88
|
+
--review-primary-foreground: #FFFDF7;
|
|
89
|
+
--review-secondary: #ECEDE5;
|
|
90
|
+
--review-secondary-foreground: #626B65;
|
|
91
|
+
--review-muted: #ECEDE5;
|
|
92
|
+
--review-muted-foreground: #626B65;
|
|
93
|
+
--review-accent: #E1EEEA;
|
|
94
|
+
--review-accent-foreground: #17685F;
|
|
95
|
+
--review-destructive: #B43C48;
|
|
96
|
+
--review-destructive-foreground: #FFFDF7;
|
|
97
|
+
--review-border: #D8D8CC;
|
|
98
|
+
--review-input: #7C877D;
|
|
99
|
+
--review-ring: #17685F;
|
|
100
|
+
--review-review-added: #EFF3E2;
|
|
101
|
+
--review-review-added-foreground: #4D6B24;
|
|
102
|
+
--review-review-added-border: #82945E;
|
|
103
|
+
--review-review-removed: #FCECEF;
|
|
104
|
+
--review-review-removed-foreground: #B43C48;
|
|
105
|
+
--review-review-removed-border: #C88791;
|
|
106
|
+
--review-review-modified: #FFF1D9;
|
|
107
|
+
--review-review-modified-foreground: #915B13;
|
|
108
|
+
--review-review-modified-border: #B99157;
|
|
109
|
+
--review-review-count-added: #4D6B24;
|
|
110
|
+
--review-review-count-removed: #B43C48;
|
|
111
|
+
--review-review-count-modified: #915B13;
|
|
112
|
+
--review-review-insert: #DCE7C5;
|
|
113
|
+
--review-review-delete: #FAE2E7;
|
|
114
|
+
--review-review-overlay: #191F1D80;
|
|
115
|
+
--review-review-shadow-color: #191F1D26;
|
|
116
|
+
--review-annotation-foreground: #75470E;
|
|
117
|
+
--review-annotation-background: #FFF1D9;
|
|
118
|
+
--review-annotation-active: #F5D6A3;
|
|
119
|
+
--review-annotation-border: #915B13;
|
|
120
|
+
--review-annotation-tint: #915B131A;
|
|
121
|
+
--review-annotation-active-tint: #915B132E;
|
|
122
|
+
--review-halo-light: #FFFDF7;
|
|
123
|
+
--review-halo-dark: #191F1D;
|
|
124
|
+
}
|
|
125
|
+
:host([data-review-theme="dark"]) {
|
|
126
|
+
color-scheme: dark;
|
|
127
|
+
--review-background: #191F1D;
|
|
128
|
+
--review-foreground: #EEEFE6;
|
|
129
|
+
--review-card: #222A26;
|
|
130
|
+
--review-card-foreground: #EEEFE6;
|
|
131
|
+
--review-popover: #222A26;
|
|
132
|
+
--review-popover-foreground: #EEEFE6;
|
|
133
|
+
--review-primary: #85C7B8;
|
|
134
|
+
--review-primary-foreground: #102F29;
|
|
135
|
+
--review-secondary: #303A33;
|
|
136
|
+
--review-secondary-foreground: #AFB9AF;
|
|
137
|
+
--review-muted: #303A33;
|
|
138
|
+
--review-muted-foreground: #AFB9AF;
|
|
139
|
+
--review-accent: #293F37;
|
|
140
|
+
--review-accent-foreground: #85C7B8;
|
|
141
|
+
--review-destructive: #F2A2AB;
|
|
142
|
+
--review-destructive-foreground: #40272C;
|
|
143
|
+
--review-border: #3C4841;
|
|
144
|
+
--review-input: #77877B;
|
|
145
|
+
--review-ring: #85C7B8;
|
|
146
|
+
--review-review-added: #2B3522;
|
|
147
|
+
--review-review-added-foreground: #BBCD87;
|
|
148
|
+
--review-review-added-border: #6F8347;
|
|
149
|
+
--review-review-removed: #40272C;
|
|
150
|
+
--review-review-removed-foreground: #F2A2AB;
|
|
151
|
+
--review-review-removed-border: #A96572;
|
|
152
|
+
--review-review-modified: #3D3020;
|
|
153
|
+
--review-review-modified-foreground: #E7BB72;
|
|
154
|
+
--review-review-modified-border: #9C7A46;
|
|
155
|
+
--review-review-count-added: #BBCD87;
|
|
156
|
+
--review-review-count-removed: #F2A2AB;
|
|
157
|
+
--review-review-count-modified: #E7BB72;
|
|
158
|
+
--review-review-insert: #3E4D2D;
|
|
159
|
+
--review-review-delete: #633944;
|
|
160
|
+
--review-review-overlay: #191F1DB3;
|
|
161
|
+
--review-review-shadow-color: #00000066;
|
|
162
|
+
--review-annotation-foreground: #F1CC8E;
|
|
163
|
+
--review-annotation-background: #3D3020;
|
|
164
|
+
--review-annotation-active: #594325;
|
|
165
|
+
--review-annotation-border: #E7BB72;
|
|
166
|
+
--review-annotation-tint: #E7BB721A;
|
|
167
|
+
--review-annotation-active-tint: #E7BB722E;
|
|
168
|
+
--review-halo-light: #FFFDF7;
|
|
169
|
+
--review-halo-dark: #191F1D;
|
|
170
|
+
}
|
|
171
|
+
/* REVIEW_THEME_SHADOW_END */
|
|
73
172
|
.box { position: fixed; pointer-events: none; z-index: 2147483646; border-radius: 3px; display: none; }
|
|
74
|
-
.outline { border: 1px dashed
|
|
173
|
+
.outline { border: 1px dashed var(--review-input); animation: outline-in 90ms ease-out; }
|
|
75
174
|
@keyframes outline-in { from { opacity: .2; } to { opacity: 1; } }
|
|
76
|
-
.active { border: 2px solid
|
|
175
|
+
.active { border: 2px solid var(--review-annotation-border); }
|
|
77
176
|
.block-marker { position: fixed; pointer-events: none; z-index: 2147483644;
|
|
78
|
-
box-sizing: border-box; border-left: 3px solid
|
|
79
|
-
.block-marker[data-active="true"] { border-left-width: 6px; background:
|
|
80
|
-
.block-marker[data-dark="true"] { border-left-color: #ffcb66; background: #ffcb6617; }
|
|
81
|
-
.block-marker[data-dark="true"][data-active="true"] { background: #ffcb6630; }
|
|
177
|
+
box-sizing: border-box; border-left: 3px solid var(--review-annotation-border); background: var(--review-annotation-tint); }
|
|
178
|
+
.block-marker[data-active="true"] { border-left-width: 6px; background: var(--review-annotation-active-tint); }
|
|
82
179
|
.block-badge { position: fixed; z-index: 2147483647; pointer-events: auto;
|
|
83
180
|
height: 24px; min-width: 32px; box-sizing: border-box; padding: 2px 6px;
|
|
84
|
-
border: 1px solid
|
|
181
|
+
border: 1px solid var(--review-annotation-border); border-radius: 5px; background: var(--review-annotation-background); color: var(--review-annotation-foreground);
|
|
85
182
|
font: 600 12px/18px system-ui, sans-serif; cursor: pointer; }
|
|
86
|
-
.block-badge[data-dark="true"] { background: #493517; color: #ffe0a0; border-color: #ffcb66; }
|
|
87
183
|
.block-badge[aria-pressed="true"] { border-width: 2px; text-decoration: underline; }
|
|
88
|
-
.block-badge:focus-visible { outline: 3px solid
|
|
184
|
+
.block-badge:focus-visible { outline: 3px solid var(--review-ring); outline-offset: 2px; }
|
|
89
185
|
.chips {
|
|
90
186
|
position: fixed; z-index: 2147483647; display: none; gap: 4px;
|
|
91
187
|
pointer-events: auto;
|
|
@@ -93,60 +189,66 @@ shadow.innerHTML = `
|
|
|
93
189
|
.chip {
|
|
94
190
|
width: 23px; height: 23px; display: flex;
|
|
95
191
|
align-items: center; justify-content: center; padding: 0;
|
|
96
|
-
border: 1px solid
|
|
192
|
+
border: 1px solid var(--review-input); border-radius: 50%; background: var(--review-card); color: var(--review-muted-foreground);
|
|
97
193
|
font: 11px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
98
|
-
cursor: pointer;
|
|
194
|
+
cursor: pointer;
|
|
99
195
|
}
|
|
100
|
-
.chip:hover { background:
|
|
101
|
-
.chip.danger { color:
|
|
102
|
-
.chip.danger:hover { background:
|
|
103
|
-
.chips .chip, .mover {
|
|
104
|
-
|
|
196
|
+
.chip:hover { background: var(--review-accent); color: var(--review-accent-foreground); border-color: var(--review-primary); }
|
|
197
|
+
.chip.danger { color: var(--review-destructive); }
|
|
198
|
+
.chip.danger:hover { background: var(--review-destructive); color: var(--review-destructive-foreground); border-color: var(--review-destructive); }
|
|
199
|
+
.chips .chip, .mover { transition: background-color 100ms ease; }
|
|
200
|
+
button:disabled { background: var(--review-muted); color: var(--review-muted-foreground); cursor: not-allowed; }
|
|
105
201
|
.grip {
|
|
106
202
|
position: fixed; z-index: 2147483647; width: 13px; height: 13px;
|
|
107
|
-
display: none; border: 1px solid
|
|
108
|
-
background:
|
|
109
|
-
box-shadow: 0 1px 4px rgba(27,26,22,.2);
|
|
203
|
+
display: none; border: 1px solid var(--review-primary); border-radius: 3px;
|
|
204
|
+
background: var(--review-card); cursor: nwse-resize; pointer-events: auto;
|
|
110
205
|
}
|
|
111
206
|
.hint {
|
|
112
207
|
position: fixed; z-index: 2147483647; display: none; padding: 3px 7px;
|
|
113
|
-
border-radius: 5px; background:
|
|
208
|
+
border-radius: 5px; background: var(--review-card); color: var(--review-foreground); white-space: nowrap;
|
|
114
209
|
font: 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
115
210
|
pointer-events: none;
|
|
116
211
|
}
|
|
117
212
|
.linkbox {
|
|
118
213
|
position: fixed; z-index: 2147483647; display: none; gap: 4px; align-items: center;
|
|
119
|
-
padding: 5px; border: 1px solid
|
|
120
|
-
|
|
214
|
+
padding: 5px; border: 1px solid var(--review-input); border-radius: 9px; background: var(--review-card);
|
|
215
|
+
pointer-events: auto;
|
|
121
216
|
}
|
|
122
217
|
.linkbox input {
|
|
123
218
|
width: 224px; padding: 4px 7px; border: none; outline: none; background: none;
|
|
124
|
-
font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color:
|
|
219
|
+
font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--review-foreground);
|
|
125
220
|
}
|
|
126
|
-
.linkbox input::placeholder { color:
|
|
221
|
+
.linkbox input::placeholder { color: var(--review-muted-foreground); opacity: 1; }
|
|
127
222
|
.mover {
|
|
128
223
|
position: fixed; z-index: 2147483647; width: 18px; height: 24px;
|
|
129
224
|
display: none; align-items: center; justify-content: center;
|
|
130
|
-
border: 1px solid
|
|
225
|
+
border: 1px solid var(--review-input); border-radius: 6px; background: var(--review-card); color: var(--review-muted-foreground);
|
|
131
226
|
font: 12px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
132
227
|
cursor: grab; pointer-events: auto; user-select: none;
|
|
133
|
-
box-shadow: 0 2px 8px rgba(27,26,22,.14);
|
|
134
228
|
}
|
|
135
|
-
.mover:hover { color:
|
|
229
|
+
.mover:hover { color: var(--review-accent-foreground); background: var(--review-accent); border-color: var(--review-primary); }
|
|
136
230
|
.mover:active { cursor: grabbing; }
|
|
137
231
|
.dropline {
|
|
138
232
|
position: fixed; z-index: 2147483646; height: 0; display: none;
|
|
139
|
-
border-top: 2px solid
|
|
233
|
+
border-top: 2px solid var(--review-primary); border-radius: 1px; pointer-events: none;
|
|
140
234
|
}
|
|
141
235
|
.comment-action {
|
|
142
236
|
position: fixed; z-index: 2147483647; width: 30px; height: 30px;
|
|
143
237
|
display: none; align-items: center; justify-content: center; padding: 0;
|
|
144
|
-
border: 1px solid
|
|
145
|
-
cursor: pointer; pointer-events: auto;
|
|
238
|
+
border: 1px solid var(--review-primary); border-radius: 999px; background: var(--review-primary); color: var(--review-primary-foreground);
|
|
239
|
+
cursor: pointer; pointer-events: auto;
|
|
240
|
+
}
|
|
241
|
+
.comment-action:hover { background: var(--review-accent); color: var(--review-accent-foreground); }
|
|
242
|
+
button:focus-visible, input:focus-visible, .mover:focus-visible, .grip:focus-visible {
|
|
243
|
+
outline: 3px solid var(--review-ring); outline-offset: 2px;
|
|
244
|
+
}
|
|
245
|
+
.box, .block-marker, .block-badge, .chip, .grip, .hint, .linkbox, .mover, .dropline, .comment-action {
|
|
246
|
+
box-shadow: 0 0 0 1px var(--review-halo-light), 0 0 0 2px var(--review-halo-dark), 0 3px 10px var(--review-review-shadow-color);
|
|
146
247
|
}
|
|
147
|
-
.comment-action:hover, .comment-action:focus-visible { background: #34312b; outline: 3px solid rgba(90,99,216,.32); }
|
|
148
248
|
.selection-cues { position: fixed; inset: 0; pointer-events: none; z-index: 2147483645; }
|
|
149
|
-
.selection-cue { position: fixed; border-radius: 2px; background:
|
|
249
|
+
.selection-cue { position: fixed; border-radius: 2px; background: var(--review-annotation-tint);
|
|
250
|
+
box-shadow: inset 0 -2px var(--review-annotation-border), 0 1px var(--review-halo-light), 0 2px var(--review-halo-dark); }
|
|
251
|
+
::selection { background: var(--review-annotation-active); color: var(--review-annotation-foreground); }
|
|
150
252
|
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
|
|
151
253
|
</style>
|
|
152
254
|
<div class="box outline" id="outline"></div>
|
|
@@ -187,15 +289,6 @@ const mountOverlay = () => {
|
|
|
187
289
|
els.commentAction = shadow.getElementById("commentAction");
|
|
188
290
|
els.blockAnnotations = shadow.getElementById("blockAnnotations");
|
|
189
291
|
};
|
|
190
|
-
function darkSurface(element) {
|
|
191
|
-
for (let probe = element; probe; probe = probe.parentElement) {
|
|
192
|
-
const color = getComputedStyle(probe).backgroundColor.match(/[\d.]+/g)?.map(Number);
|
|
193
|
-
if (color?.length >= 3 && (color.length < 4 || color[3] >= .5)) {
|
|
194
|
-
return color[0] * .2126 + color[1] * .7152 + color[2] * .0722 < 128;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return matchMedia("(prefers-color-scheme: dark)").matches;
|
|
198
|
-
}
|
|
199
292
|
function positionBlockBadge(badge, rect) {
|
|
200
293
|
const width = badge.offsetWidth || 36;
|
|
201
294
|
const height = 24;
|
|
@@ -259,9 +352,7 @@ function renderBlockAnnotations() {
|
|
|
259
352
|
}
|
|
260
353
|
const { marker, badge } = entry;
|
|
261
354
|
const active = ids.includes(activeCommentId);
|
|
262
|
-
const dark = darkSurface(element);
|
|
263
355
|
marker.dataset.active = String(active);
|
|
264
|
-
marker.dataset.dark = badge.dataset.dark = String(dark);
|
|
265
356
|
badge.setAttribute("aria-pressed", String(active));
|
|
266
357
|
const label = commentTargetFor(element)?.label || element.tagName.toLowerCase();
|
|
267
358
|
const name = `${ids.length} block comment${ids.length === 1 ? "" : "s"} on ${label}${ids.length > 1 ? "; activate to open next comment" : ""}`;
|
|
@@ -618,7 +709,7 @@ function geometryWatchSignature() {
|
|
|
618
709
|
blocks: [...groupCommentTargets(blockTargets).keys()].map((element) => {
|
|
619
710
|
const rect = element.getBoundingClientRect();
|
|
620
711
|
const clip = effectiveClipRect({ kind: "element", element });
|
|
621
|
-
return [rect.left, rect.top, rect.width, rect.height, clip
|
|
712
|
+
return [rect.left, rect.top, rect.width, rect.height, clip];
|
|
622
713
|
}),
|
|
623
714
|
});
|
|
624
715
|
}
|
|
@@ -1282,7 +1373,7 @@ function setElementTarget(container) {
|
|
|
1282
1373
|
}
|
|
1283
1374
|
function openPendingCompose() {
|
|
1284
1375
|
hoverIntent?.cancel();
|
|
1285
|
-
if (composing || disposed)
|
|
1376
|
+
if (composing || disposed || !themeRevision)
|
|
1286
1377
|
return false;
|
|
1287
1378
|
const selection = document.getSelection();
|
|
1288
1379
|
if (selection && !selection.isCollapsed && selection.rangeCount) {
|
|
@@ -1485,7 +1576,6 @@ function activate(id, scroll) {
|
|
|
1485
1576
|
for (const mark of marks)
|
|
1486
1577
|
mark.classList.add("eh-active");
|
|
1487
1578
|
place(els.activeBox, target);
|
|
1488
|
-
els.activeBox.style.borderColor = target && darkSurface(target) ? "#ffcb66" : "#a96100";
|
|
1489
1579
|
renderBlockAnnotations();
|
|
1490
1580
|
const rects = marks.length
|
|
1491
1581
|
? marks.map((mark) => rectData(mark.getBoundingClientRect()))
|
|
@@ -1531,18 +1621,54 @@ function activate(id, scroll) {
|
|
|
1531
1621
|
}
|
|
1532
1622
|
}
|
|
1533
1623
|
// -------------------------------------------------------------------- boot
|
|
1624
|
+
/* REVIEW_THEME_DOCUMENT_START */
|
|
1625
|
+
const REVIEW_DOCUMENT_COLORS = {
|
|
1626
|
+
light: `
|
|
1627
|
+
mark[${MARK_ATTR}] { background: #FFF1D9; color: #75470E; text-decoration: underline #915B13; }
|
|
1628
|
+
mark[${MARK_ATTR}]:hover, mark[${MARK_ATTR}].eh-active { background: #F5D6A3; }
|
|
1629
|
+
mark[${MARK_ATTR}].eh-active { outline: 2px solid #915B13; outline-offset: 1px; }
|
|
1630
|
+
::selection { background: #F5D6A3; color: #75470E; }
|
|
1631
|
+
`,
|
|
1632
|
+
dark: `
|
|
1633
|
+
mark[${MARK_ATTR}] { background: #3D3020; color: #F1CC8E; text-decoration: underline #E7BB72; }
|
|
1634
|
+
mark[${MARK_ATTR}]:hover, mark[${MARK_ATTR}].eh-active { background: #594325; }
|
|
1635
|
+
mark[${MARK_ATTR}].eh-active { outline: 2px solid #E7BB72; outline-offset: 1px; }
|
|
1636
|
+
::selection { background: #594325; color: #F1CC8E; }
|
|
1637
|
+
`,
|
|
1638
|
+
};
|
|
1639
|
+
/* REVIEW_THEME_DOCUMENT_END */
|
|
1534
1640
|
function boot() {
|
|
1535
1641
|
mountOverlay();
|
|
1536
1642
|
const style = document.createElement("style");
|
|
1537
1643
|
style.setAttribute("data-eh-sdk", "");
|
|
1538
|
-
|
|
1539
|
-
mark[${MARK_ATTR}] { background:
|
|
1540
|
-
mark[${MARK_ATTR}]:hover { background: rgba(243,176,0,.5); }
|
|
1541
|
-
mark[${MARK_ATTR}].eh-active { background: rgba(255,180,0,.55); }
|
|
1644
|
+
const documentStyle = `
|
|
1645
|
+
mark[${MARK_ATTR}] { background: transparent; border-radius: 2px; color: inherit; cursor: pointer; }
|
|
1542
1646
|
body[contenteditable]:focus { outline: none; }
|
|
1543
|
-
::selection { background: rgba(245,196,0,.42); }
|
|
1544
1647
|
`;
|
|
1648
|
+
style.textContent = documentStyle;
|
|
1545
1649
|
document.head.appendChild(style);
|
|
1650
|
+
const applyReviewTheme = (message) => {
|
|
1651
|
+
const allowedFields = ["type", "theme", "themeRevision", "capability", "generation", "pageKey"];
|
|
1652
|
+
const malformed = !["light", "dark"].includes(message.theme) ||
|
|
1653
|
+
!Number.isSafeInteger(message.themeRevision) || message.themeRevision <= 0 ||
|
|
1654
|
+
Object.keys(message).some((field) => !allowedFields.includes(field));
|
|
1655
|
+
const conflicting = message.themeRevision === themeRevision && message.theme !== reviewTheme;
|
|
1656
|
+
if (malformed || conflicting) {
|
|
1657
|
+
if (themeWarnings++ < 3)
|
|
1658
|
+
console.warn("[doc-review-frame] Rejected invalid theme command.");
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (message.themeRevision < themeRevision)
|
|
1662
|
+
return;
|
|
1663
|
+
if (message.themeRevision > themeRevision) {
|
|
1664
|
+
// Only review-owned nodes change: keep the document, controls, focus and mode intact.
|
|
1665
|
+
style.textContent = documentStyle + REVIEW_DOCUMENT_COLORS[message.theme];
|
|
1666
|
+
host.dataset.reviewTheme = message.theme;
|
|
1667
|
+
reviewTheme = message.theme;
|
|
1668
|
+
themeRevision = message.themeRevision;
|
|
1669
|
+
}
|
|
1670
|
+
post("eh:themeApplied", { theme: reviewTheme, themeRevision });
|
|
1671
|
+
};
|
|
1546
1672
|
modeController = keepBodyInReviewMode(document.body, "view");
|
|
1547
1673
|
baseline = serialize();
|
|
1548
1674
|
bootSnapshot = baseline;
|
|
@@ -1581,7 +1707,7 @@ function boot() {
|
|
|
1581
1707
|
const modified = event.metaKey || event.ctrlKey;
|
|
1582
1708
|
const href = navigationHref(event.target);
|
|
1583
1709
|
const mark = event.target.closest && event.target.closest(`mark[${MARK_ATTR}]`);
|
|
1584
|
-
if (mark) {
|
|
1710
|
+
if (mark && themeRevision) {
|
|
1585
1711
|
event.preventDefault();
|
|
1586
1712
|
post("eh:activate", { id: mark.getAttribute(MARK_ATTR) });
|
|
1587
1713
|
return;
|
|
@@ -1701,7 +1827,7 @@ function boot() {
|
|
|
1701
1827
|
clearPending();
|
|
1702
1828
|
};
|
|
1703
1829
|
hoverIntent = createHoverIntent((event) => {
|
|
1704
|
-
if (disposed || pointerSelecting || composing || composeOpen || selectionIsActive())
|
|
1830
|
+
if (disposed || !themeRevision || pointerSelecting || composing || composeOpen || selectionIsActive())
|
|
1705
1831
|
return;
|
|
1706
1832
|
if (!event) {
|
|
1707
1833
|
clearHover();
|
|
@@ -1734,7 +1860,7 @@ function boot() {
|
|
|
1734
1860
|
showHint(reviewMode === "edit" && interactive ? "⌘-click to open" : reviewMode === "edit" && draggable ? "Drag to move" : "", event.clientX, event.clientY);
|
|
1735
1861
|
});
|
|
1736
1862
|
const updateHover = (event) => {
|
|
1737
|
-
if (disposed || resizing || moving || pointerSelecting || composing || composeOpen ||
|
|
1863
|
+
if (disposed || !themeRevision || resizing || moving || pointerSelecting || composing || composeOpen ||
|
|
1738
1864
|
commentOpenRequestGeneration || selectionIsActive()) {
|
|
1739
1865
|
hoverIntent.cancel();
|
|
1740
1866
|
return;
|
|
@@ -1987,6 +2113,30 @@ function boot() {
|
|
|
1987
2113
|
document.body.focus({ preventScroll: true });
|
|
1988
2114
|
}
|
|
1989
2115
|
}
|
|
2116
|
+
function positionLinkbox() {
|
|
2117
|
+
if (!linkState)
|
|
2118
|
+
return false;
|
|
2119
|
+
const { range, anchor, blockEl, startNode, endNode } = linkState;
|
|
2120
|
+
if (!startNode.isConnected || !endNode.isConnected || (anchor && !anchor.isConnected) ||
|
|
2121
|
+
(blockEl && !blockEl.isConnected) || !document.body.contains(range.commonAncestorContainer)) {
|
|
2122
|
+
closeLinkbox(false);
|
|
2123
|
+
return false;
|
|
2124
|
+
}
|
|
2125
|
+
const rects = visibleRects([...range.getClientRects()].map(rectData), {
|
|
2126
|
+
kind: "element", element: anchor || blockEl || startNode.parentElement,
|
|
2127
|
+
});
|
|
2128
|
+
if (!rects.length) {
|
|
2129
|
+
closeLinkbox(false);
|
|
2130
|
+
return false;
|
|
2131
|
+
}
|
|
2132
|
+
const r = rects[0];
|
|
2133
|
+
const width = els.linkbox.offsetWidth || 320;
|
|
2134
|
+
const height = els.linkbox.offsetHeight || 36;
|
|
2135
|
+
const below = r.bottom + height + 8 < window.innerHeight;
|
|
2136
|
+
els.linkbox.style.left = `${Math.max(8, Math.min(r.left, window.innerWidth - width - 8))}px`;
|
|
2137
|
+
els.linkbox.style.top = `${Math.max(8, Math.min(below ? r.bottom + 8 : r.top - height - 8, window.innerHeight - height - 8))}px`;
|
|
2138
|
+
return true;
|
|
2139
|
+
}
|
|
1990
2140
|
function openLinkbox() {
|
|
1991
2141
|
const sel = document.getSelection();
|
|
1992
2142
|
if (!sel || !sel.rangeCount)
|
|
@@ -2010,18 +2160,18 @@ function boot() {
|
|
|
2010
2160
|
anchor,
|
|
2011
2161
|
blockEl: target ? target.el : null,
|
|
2012
2162
|
label: target ? target.label : "Document body",
|
|
2163
|
+
startNode: range.startContainer,
|
|
2164
|
+
endNode: range.endContainer,
|
|
2013
2165
|
};
|
|
2014
2166
|
if (linkState.blockEl)
|
|
2015
2167
|
captureOriginal(linkState.blockEl);
|
|
2016
2168
|
// The compose card and the link popup fight over the same selection.
|
|
2017
2169
|
clearPending();
|
|
2018
2170
|
post("eh:dismiss", {});
|
|
2019
|
-
const r = range.getBoundingClientRect();
|
|
2020
|
-
const below = r.bottom + 44 < window.innerHeight;
|
|
2021
2171
|
els.linkbox.style.display = "flex";
|
|
2022
|
-
els.linkbox.style.left = `${Math.max(8, Math.min(r.left, window.innerWidth - 320))}px`;
|
|
2023
|
-
els.linkbox.style.top = `${below ? r.bottom + 8 : Math.max(8, r.top - 44)}px`;
|
|
2024
2172
|
els.linkRemove.style.display = anchor ? "" : "none";
|
|
2173
|
+
if (!positionLinkbox())
|
|
2174
|
+
return false;
|
|
2025
2175
|
els.linkInput.value = anchor ? anchor.getAttribute("href") || "" : "";
|
|
2026
2176
|
els.linkInput.focus();
|
|
2027
2177
|
els.linkInput.select();
|
|
@@ -2476,9 +2626,10 @@ function boot() {
|
|
|
2476
2626
|
scheduleTargetGeometry();
|
|
2477
2627
|
if (activeCommentId)
|
|
2478
2628
|
activate(activeCommentId, false);
|
|
2479
|
-
//
|
|
2629
|
+
// Recovery notices can resize the frame. Re-anchor without discarding the
|
|
2630
|
+
// open editor's draft or focus; only a missing/invisible target invalidates it.
|
|
2480
2631
|
if (linkState)
|
|
2481
|
-
|
|
2632
|
+
positionLinkbox();
|
|
2482
2633
|
};
|
|
2483
2634
|
// getBoundingClientRect on every scroll event forces layout mid-scroll;
|
|
2484
2635
|
// one reposition per frame is plenty.
|
|
@@ -2514,6 +2665,9 @@ function boot() {
|
|
|
2514
2665
|
if (!matchesFrameMessage(msg))
|
|
2515
2666
|
return;
|
|
2516
2667
|
switch (msg.type) {
|
|
2668
|
+
case "eh:setTheme":
|
|
2669
|
+
applyReviewTheme(msg);
|
|
2670
|
+
break;
|
|
2517
2671
|
case "eh:anchors":
|
|
2518
2672
|
reanchor(msg.comments || []);
|
|
2519
2673
|
break;
|