vite-plugin-specter 0.5.0 → 0.6.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 +15 -0
- package/dist/client.js +198 -47
- package/dist/extension-chrome/content.js +198 -47
- package/dist/extension-chrome/manifest.json +1 -1
- package/dist/extension-firefox/content.js +198 -47
- package/dist/extension-firefox/manifest.json +1 -1
- package/dist/index.cjs +198 -47
- package/dist/index.js +198 -47
- package/extension/content.js +198 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ function getClientScript(options) {
|
|
|
33
33
|
// \u2500\u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
34
34
|
var fiActive = false;
|
|
35
35
|
var measureMode = false;
|
|
36
|
+
var commentMode = false; // C: hide props/measure overlays for design review \u2014 Specs still capture them
|
|
36
37
|
var pinEl = null;
|
|
37
38
|
var pinHighlight = null;
|
|
38
39
|
var lastHovered = null;
|
|
@@ -198,7 +199,7 @@ function getClientScript(options) {
|
|
|
198
199
|
hoverFx(listBtn, { background: 'rgba(255,255,255,0.32)' }, { background: 'rgba(255,255,255,0.16)' });
|
|
199
200
|
|
|
200
201
|
var clearBtn = document.createElement('span');
|
|
201
|
-
clearBtn.textContent = '\u2715 Delete all
|
|
202
|
+
clearBtn.textContent = '\u2715 Delete all';
|
|
202
203
|
clearBtn.title = 'Delete all annotations';
|
|
203
204
|
Object.assign(clearBtn.style, {
|
|
204
205
|
display: 'none',
|
|
@@ -230,7 +231,18 @@ function getClientScript(options) {
|
|
|
230
231
|
chevron.addEventListener('click', function (e) { e.stopPropagation(); moveSide(); });
|
|
231
232
|
hoverFx(chevron, { opacity: '1', transform: 'scale(1.2)' }, { opacity: '0.8', transform: 'scale(1)' });
|
|
232
233
|
|
|
234
|
+
// Sync status in the control bar (only when a bridge is configured): spinner while
|
|
235
|
+
// syncing, green when synced, red on error \u2014 so you always know what's staged.
|
|
236
|
+
var pillSync = document.createElement('span');
|
|
237
|
+
Object.assign(pillSync.style, {
|
|
238
|
+
display: 'none', width: '8px', height: '8px', borderRadius: '999px',
|
|
239
|
+
background: '#6B7280', flexShrink: '0', boxSizing: 'border-box',
|
|
240
|
+
});
|
|
241
|
+
pillSync.title = 'Sync status \u2014 click to re-sync';
|
|
242
|
+
pillSync.addEventListener('click', function (e) { e.stopPropagation(); if (BRIDGE) doSync(); });
|
|
243
|
+
|
|
233
244
|
pill.appendChild(iconBtn);
|
|
245
|
+
pill.appendChild(pillSync);
|
|
234
246
|
pill.appendChild(pillText);
|
|
235
247
|
pill.appendChild(listBtn);
|
|
236
248
|
pill.appendChild(clearBtn);
|
|
@@ -238,6 +250,30 @@ function getClientScript(options) {
|
|
|
238
250
|
pillWrap.appendChild(pill);
|
|
239
251
|
document.body.appendChild(pillWrap);
|
|
240
252
|
|
|
253
|
+
// Keyframes for the sync spinner (injected once).
|
|
254
|
+
var spinStyle = document.createElement('style');
|
|
255
|
+
spinStyle.textContent = '@keyframes __specterSpin{to{transform:rotate(360deg)}}';
|
|
256
|
+
markUI(spinStyle);
|
|
257
|
+
document.head.appendChild(spinStyle);
|
|
258
|
+
|
|
259
|
+
// Shared dot styling for the control-bar AND side-panel sync indicators, so they
|
|
260
|
+
// always match: spinner while syncing, green when synced, red on error.
|
|
261
|
+
function applyDotState(el, state) {
|
|
262
|
+
el.style.boxSizing = 'border-box';
|
|
263
|
+
if (state === 'syncing') {
|
|
264
|
+
Object.assign(el.style, { width: '10px', height: '10px', background: 'transparent', border: '2px solid rgba(255,255,255,0.35)', borderTopColor: '#fff', animation: '__specterSpin 0.6s linear infinite' });
|
|
265
|
+
} else if (state === 'synced') {
|
|
266
|
+
Object.assign(el.style, { width: '8px', height: '8px', background: GREEN, border: 'none', animation: 'none' });
|
|
267
|
+
} else { // offline / error
|
|
268
|
+
Object.assign(el.style, { width: '8px', height: '8px', background: '#F26D6D', border: 'none', animation: 'none' });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function setPillSync(state) {
|
|
272
|
+
if (!BRIDGE) { pillSync.style.display = 'none'; return; }
|
|
273
|
+
pillSync.style.display = 'inline-block';
|
|
274
|
+
applyDotState(pillSync, state);
|
|
275
|
+
}
|
|
276
|
+
|
|
241
277
|
pillWrap.addEventListener('mouseenter', function () {
|
|
242
278
|
clearMeasureOverlay();
|
|
243
279
|
clearMeasureTargetHL();
|
|
@@ -267,6 +303,12 @@ function getClientScript(options) {
|
|
|
267
303
|
}
|
|
268
304
|
}
|
|
269
305
|
|
|
306
|
+
// The mode is shown at all times (persistent prefix), so you always know whether
|
|
307
|
+
// hovering shows properties, measurements, or nothing (Comment).
|
|
308
|
+
function modeLabel() {
|
|
309
|
+
return commentMode ? 'Comment' : (measureMode ? 'Measure' : 'Properties');
|
|
310
|
+
}
|
|
311
|
+
|
|
270
312
|
function expandPill(text) {
|
|
271
313
|
pill.style.maxWidth = '820px';
|
|
272
314
|
pillText.style.display = 'inline';
|
|
@@ -274,19 +316,17 @@ function getClientScript(options) {
|
|
|
274
316
|
listBtn.style.display = specs.length > 0 ? 'inline-flex' : 'none';
|
|
275
317
|
clearBtn.style.display = specs.length > 0 ? 'inline' : 'none';
|
|
276
318
|
pillExpanded = true;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} else if (measureMode) {
|
|
281
|
-
pillText.textContent = 'Measure \xB7 hover distances \xB7 P mark \xB7 M pin \xB7 Cmd+C copy \xB7 Option toggle';
|
|
282
|
-
} else {
|
|
283
|
-
pillText.textContent = 'Properties \xB7 P mark / annotate \xB7 Cmd+C copy \xB7 Option measure';
|
|
284
|
-
}
|
|
319
|
+
// Just the mode (+ the action buttons when Specs exist). Shortcuts live in the
|
|
320
|
+
// side panel now, so the pill stays short.
|
|
321
|
+
pillText.textContent = text || modeLabel();
|
|
285
322
|
}
|
|
286
323
|
|
|
324
|
+
// "Collapsed" = the compact resting state: mode label only, no hints/buttons.
|
|
325
|
+
// Still shows the mode so it's visible at all times.
|
|
287
326
|
function collapsePill() {
|
|
288
|
-
|
|
289
|
-
pillText.style.display = '
|
|
327
|
+
pillText.textContent = modeLabel();
|
|
328
|
+
pillText.style.display = 'inline';
|
|
329
|
+
pill.style.maxWidth = '220px';
|
|
290
330
|
chevron.style.display = 'none';
|
|
291
331
|
listBtn.style.display = 'none';
|
|
292
332
|
clearBtn.style.display = 'none';
|
|
@@ -295,7 +335,7 @@ function getClientScript(options) {
|
|
|
295
335
|
|
|
296
336
|
function flashMode() {
|
|
297
337
|
if (pillExpanded && pillWrap.matches(':hover')) return;
|
|
298
|
-
var text = measureMode ? '
|
|
338
|
+
var text = commentMode ? 'Comment mode' : (measureMode ? 'Measure mode' : 'Properties mode');
|
|
299
339
|
expandPill(text);
|
|
300
340
|
clearTimeout(flashTimer);
|
|
301
341
|
flashTimer = setTimeout(function () {
|
|
@@ -316,11 +356,13 @@ function getClientScript(options) {
|
|
|
316
356
|
function activate() {
|
|
317
357
|
fiActive = true;
|
|
318
358
|
measureMode = false;
|
|
359
|
+
commentMode = false;
|
|
319
360
|
pillWrap.style.display = 'block';
|
|
320
361
|
document.body.style.cursor = 'crosshair';
|
|
321
362
|
updatePill();
|
|
322
363
|
startLoop();
|
|
323
364
|
reflowSpecs();
|
|
365
|
+
if (BRIDGE) doSync(); // resolve the control-bar sync dot (green if reachable, red if not)
|
|
324
366
|
}
|
|
325
367
|
|
|
326
368
|
function deactivate() {
|
|
@@ -604,24 +646,37 @@ function getClientScript(options) {
|
|
|
604
646
|
return ['[Specter]', head, 'find: ' + resolveLocator(data.el), props.join(' \xB7 ')].join('\\n');
|
|
605
647
|
}
|
|
606
648
|
|
|
649
|
+
// Group Specs on the SAME element so their identical properties aren't emitted
|
|
650
|
+
// twice \u2014 several comments on one thing share ONE property block, each with its
|
|
651
|
+
// own change note. Measure Specs always stand alone (each is a distinct reading).
|
|
652
|
+
function groupSpecs() {
|
|
653
|
+
var groups = [];
|
|
654
|
+
for (var i = 0; i < specs.length; i++) {
|
|
655
|
+
var s = specs[i], g = null;
|
|
656
|
+
if (s.kind === 'element' && s.el) {
|
|
657
|
+
for (var j = 0; j < groups.length; j++) { if (groups[j].kind === 'element' && groups[j].el === s.el) { g = groups[j]; break; } }
|
|
658
|
+
}
|
|
659
|
+
if (g) g.specs.push(s);
|
|
660
|
+
else groups.push({ kind: s.kind, el: s.el, specs: [s] });
|
|
661
|
+
}
|
|
662
|
+
return groups;
|
|
663
|
+
}
|
|
664
|
+
|
|
607
665
|
// Copy every Spec \u2014 using each Spec's body snapshotted at mark time, so Specs
|
|
608
666
|
// whose element is currently hidden (e.g. inside a closed modal) still copy.
|
|
667
|
+
// Same-element Specs collapse into one block (properties once, notes stacked).
|
|
609
668
|
function buildSpecsCopyText() {
|
|
610
|
-
var
|
|
611
|
-
|
|
612
|
-
|
|
669
|
+
var groups = groupSpecs();
|
|
670
|
+
var n = groups.length;
|
|
671
|
+
return groups.map(function (g, i) {
|
|
672
|
+
var tag = g.kind === 'measure' ? 'Specter Measure' : 'Specter';
|
|
613
673
|
var header = n > 1 ? '[' + tag + ' ' + (i + 1) + '/' + n + ']' : '[' + tag + ']';
|
|
614
|
-
|
|
615
|
-
|
|
674
|
+
var notes = g.specs.filter(function (s) { return s.note; }).map(function (s) { return '\u270F\uFE0F CHANGE: ' + s.note; });
|
|
675
|
+
if (notes.length) header += '\\n' + notes.join('\\n');
|
|
676
|
+
return g.specs[0].body.replace(/^\\[Specter[^\\]]*\\]/, function () { return header; });
|
|
616
677
|
}).join('\\n\\n---\\n\\n');
|
|
617
678
|
}
|
|
618
679
|
|
|
619
|
-
function linesToHTML(str) {
|
|
620
|
-
return str.split('\\n').map(function (l) {
|
|
621
|
-
return '<div style="margin-bottom:4px;color:#fff">' + esc(l) + '</div>';
|
|
622
|
-
}).join('');
|
|
623
|
-
}
|
|
624
|
-
|
|
625
680
|
// \u2500\u2500\u2500 Hover outline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
626
681
|
function setHoverOutline(el) {
|
|
627
682
|
if (outlinedEl === el) return;
|
|
@@ -653,12 +708,14 @@ function getClientScript(options) {
|
|
|
653
708
|
}
|
|
654
709
|
|
|
655
710
|
function reRenderTooltip() {
|
|
711
|
+
if (commentMode) { hideTooltip(); return; }
|
|
656
712
|
if (measureMode) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
713
|
+
// Redraw the overlay on scroll/resize; readout box stays hidden (see hover handler).
|
|
714
|
+
if (pinEl && pinEl !== lastHovered) measureBetween(pinEl, lastHovered); else measureToNeighbor(lastHovered);
|
|
715
|
+
hideTooltip();
|
|
716
|
+
return;
|
|
661
717
|
}
|
|
718
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(lastHovered));
|
|
662
719
|
positionTooltip(lastMouse.x, lastMouse.y);
|
|
663
720
|
}
|
|
664
721
|
|
|
@@ -814,7 +871,7 @@ function getClientScript(options) {
|
|
|
814
871
|
|
|
815
872
|
function updateBadgeContent(spec) {
|
|
816
873
|
spec.num.textContent = String(specs.indexOf(spec) + 1);
|
|
817
|
-
if (spec.note) spec.noteSpan.textContent =
|
|
874
|
+
if (spec.note) spec.noteSpan.textContent = spec.note;
|
|
818
875
|
else spec.noteSpan.textContent = spec.kind === 'measure' ? '\u2B21 measure' : '';
|
|
819
876
|
}
|
|
820
877
|
|
|
@@ -859,10 +916,6 @@ function getClientScript(options) {
|
|
|
859
916
|
updateBadgeContent(spec);
|
|
860
917
|
}
|
|
861
918
|
|
|
862
|
-
function findElementSpec(el) {
|
|
863
|
-
for (var i = 0; i < specs.length; i++) if (specs[i].kind === 'element' && specs[i].el === el) return specs[i];
|
|
864
|
-
return null;
|
|
865
|
-
}
|
|
866
919
|
|
|
867
920
|
// If the element sits inside a dialog/modal/drawer/menu, produce a hint for how to
|
|
868
921
|
// bring it back when it's later hidden \u2014 a declarative opener (aria-controls /
|
|
@@ -901,7 +954,9 @@ function getClientScript(options) {
|
|
|
901
954
|
if (pinEl && lastHovered && lastHovered !== pinEl) { el = pinEl; body = buildMeasureCopyText(pinEl, lastHovered); }
|
|
902
955
|
else { el = anchorEl; body = buildNeighborCopyText(anchorEl); }
|
|
903
956
|
} else {
|
|
904
|
-
|
|
957
|
+
// Multiple Specs per element are allowed in every mode \u2014 clustering fans the badges
|
|
958
|
+
// out and same-element output collapses to one property block. Mode only changes
|
|
959
|
+
// what's shown on screen, never whether you can add an annotation.
|
|
905
960
|
body = buildLLMClipboard(buildInfo(anchorEl));
|
|
906
961
|
}
|
|
907
962
|
clearHoverOutline();
|
|
@@ -1139,14 +1194,74 @@ function getClientScript(options) {
|
|
|
1139
1194
|
}
|
|
1140
1195
|
|
|
1141
1196
|
var panelList = document.createElement('div');
|
|
1142
|
-
|
|
1197
|
+
// overscroll-behavior:contain stops the panel's scroll from chaining into the page.
|
|
1198
|
+
Object.assign(panelList.style, { flex: '1', overflowY: 'auto', overflowX: 'hidden', overscrollBehavior: 'contain' });
|
|
1199
|
+
|
|
1200
|
+
// Keyboard shortcuts reference \u2014 collapsible, collapsed by default, lives here (not
|
|
1201
|
+
// in the pill) so the pill stays short.
|
|
1202
|
+
var panelKeys = document.createElement('div');
|
|
1203
|
+
Object.assign(panelKeys.style, {
|
|
1204
|
+
flexShrink: '0', fontSize: '11px', lineHeight: '18px', color: LABEL,
|
|
1205
|
+
borderTop: '1px solid rgba(255,255,255,0.08)',
|
|
1206
|
+
});
|
|
1207
|
+
(function () {
|
|
1208
|
+
var rows = [
|
|
1209
|
+
['P', 'mark / comment the hovered element'],
|
|
1210
|
+
['C', 'toggle Comment mode (hide properties)'],
|
|
1211
|
+
['\\u2325 Option', 'toggle Measure mode'],
|
|
1212
|
+
['M', 'pin an element to measure from'],
|
|
1213
|
+
['Cmd/Ctrl+C', 'copy all Specs'],
|
|
1214
|
+
['L', 'toggle this panel'],
|
|
1215
|
+
];
|
|
1216
|
+
var head = document.createElement('div');
|
|
1217
|
+
Object.assign(head.style, { display: 'flex', alignItems: 'center', gap: '6px', color: '#8A8D96', fontWeight: '700', letterSpacing: '0.04em', textTransform: 'uppercase', fontSize: '11px', padding: '10px 16px', cursor: 'pointer', userSelect: 'none' });
|
|
1218
|
+
var caret = document.createElement('span');
|
|
1219
|
+
caret.textContent = '\\u203A'; // \u203A
|
|
1220
|
+
Object.assign(caret.style, { display: 'inline-block', fontSize: '16px', lineHeight: '1', transition: 'transform 0.15s ease', transform: 'rotate(0deg)' });
|
|
1221
|
+
var headText = document.createElement('span');
|
|
1222
|
+
headText.textContent = 'Keyboard shortcuts';
|
|
1223
|
+
head.appendChild(caret);
|
|
1224
|
+
head.appendChild(headText);
|
|
1225
|
+
var body = document.createElement('div');
|
|
1226
|
+
Object.assign(body.style, { display: 'none', padding: '0 16px 12px' }); // collapsed by default
|
|
1227
|
+
rows.forEach(function (r) {
|
|
1228
|
+
var row = document.createElement('div');
|
|
1229
|
+
Object.assign(row.style, { display: 'flex', gap: '8px', marginBottom: '2px' });
|
|
1230
|
+
var k = document.createElement('span');
|
|
1231
|
+
k.textContent = r[0];
|
|
1232
|
+
Object.assign(k.style, { color: '#E0A3F5', fontWeight: '700', minWidth: '78px', flexShrink: '0' });
|
|
1233
|
+
var d = document.createElement('span');
|
|
1234
|
+
d.textContent = r[1];
|
|
1235
|
+
row.appendChild(k); row.appendChild(d);
|
|
1236
|
+
body.appendChild(row);
|
|
1237
|
+
});
|
|
1238
|
+
var open = false;
|
|
1239
|
+
head.addEventListener('click', function (e) {
|
|
1240
|
+
e.stopPropagation();
|
|
1241
|
+
open = !open;
|
|
1242
|
+
body.style.display = open ? 'block' : 'none';
|
|
1243
|
+
caret.style.transform = open ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
1244
|
+
});
|
|
1245
|
+
panelKeys.appendChild(head);
|
|
1246
|
+
panelKeys.appendChild(body);
|
|
1247
|
+
})();
|
|
1143
1248
|
|
|
1144
1249
|
panelWrap.appendChild(panelHead);
|
|
1145
1250
|
panelWrap.appendChild(panelTools);
|
|
1146
1251
|
panelWrap.appendChild(panelHint);
|
|
1147
1252
|
panelWrap.appendChild(panelList);
|
|
1253
|
+
panelWrap.appendChild(panelKeys);
|
|
1148
1254
|
document.body.appendChild(panelWrap);
|
|
1149
1255
|
|
|
1256
|
+
// Panel scroll and page scroll are mutually exclusive: wheel over the scrollable list
|
|
1257
|
+
// scrolls it natively (overscroll-behavior:contain stops it chaining at the bounds);
|
|
1258
|
+
// wheel over any non-scrolling part of the panel never scrolls the page behind it.
|
|
1259
|
+
panelWrap.addEventListener('wheel', function (e) {
|
|
1260
|
+
var canScroll = panelList.scrollHeight > panelList.clientHeight;
|
|
1261
|
+
if (panelList.contains(e.target) && canScroll) return; // let the list scroll natively
|
|
1262
|
+
e.preventDefault();
|
|
1263
|
+
}, { passive: false });
|
|
1264
|
+
|
|
1150
1265
|
// \u2500\u2500 Auto-sync: mirror the browser's current Specs to the local bridge \u2500\u2500
|
|
1151
1266
|
// Debounced so rapid edits/typing collapse into one POST. The bridge replaces
|
|
1152
1267
|
// its snapshot for this URL, so it always reflects what's in the panel \u2014 no
|
|
@@ -1154,24 +1269,33 @@ function getClientScript(options) {
|
|
|
1154
1269
|
var syncTimer = null;
|
|
1155
1270
|
function setSyncState(s) {
|
|
1156
1271
|
if (!BRIDGE) return;
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1272
|
+
setPillSync(s); // control-bar dot
|
|
1273
|
+
applyDotState(dot, s); // side-panel dot \u2014 same spinner/green/red
|
|
1274
|
+
if (s === 'syncing') { dotLabel.textContent = 'Syncing\u2026'; }
|
|
1275
|
+
else if (s === 'synced') { dotLabel.textContent = specs.length ? (specs.length + (specs.length === 1 ? ' Spec synced' : ' Specs synced')) : 'Synced'; }
|
|
1276
|
+
else { dotLabel.textContent = 'Bridge offline'; }
|
|
1160
1277
|
}
|
|
1161
1278
|
function doSync() {
|
|
1162
1279
|
if (!BRIDGE) return;
|
|
1163
1280
|
setSyncState('syncing');
|
|
1281
|
+
// Keep the spinner up for at least 1s even on an instant localhost sync, so the
|
|
1282
|
+
// feedback is actually perceptible instead of flashing by.
|
|
1283
|
+
var started = Date.now();
|
|
1284
|
+
var settle = function (state) {
|
|
1285
|
+
var wait = Math.max(0, 1000 - (Date.now() - started));
|
|
1286
|
+
setTimeout(function () { setSyncState(state); }, wait);
|
|
1287
|
+
};
|
|
1164
1288
|
var payload = {
|
|
1165
1289
|
url: location.href,
|
|
1166
1290
|
text: buildSpecsCopyText(),
|
|
1167
|
-
specs:
|
|
1168
|
-
return { num: i + 1, note: s.note
|
|
1291
|
+
specs: groupSpecs().map(function (g, i) {
|
|
1292
|
+
return { num: i + 1, note: g.specs.map(function (s) { return s.note; }).filter(Boolean).join('\\n'), kind: g.kind, body: g.specs[0].body };
|
|
1169
1293
|
}),
|
|
1170
1294
|
};
|
|
1171
1295
|
fetch(BRIDGE + '/specs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })
|
|
1172
1296
|
.then(function (r) { if (!r.ok) throw 0; return r.json(); })
|
|
1173
|
-
.then(function () {
|
|
1174
|
-
.catch(function () {
|
|
1297
|
+
.then(function () { settle('synced'); })
|
|
1298
|
+
.catch(function () { settle('offline'); });
|
|
1175
1299
|
}
|
|
1176
1300
|
function scheduleSync() {
|
|
1177
1301
|
if (!BRIDGE) return;
|
|
@@ -1315,7 +1439,7 @@ function getClientScript(options) {
|
|
|
1315
1439
|
Object.assign(hbar.style, { display: 'flex', alignItems: 'center', gap: '7px', marginTop: '2px', flexWrap: 'wrap' });
|
|
1316
1440
|
var tag = document.createElement('span');
|
|
1317
1441
|
tag.textContent = 'HIDDEN';
|
|
1318
|
-
Object.assign(tag.style, { fontSize: '
|
|
1442
|
+
Object.assign(tag.style, { fontSize: '11px', fontWeight: '700', letterSpacing: '0.05em', color: '#3A2A05', background: '#F59E0B', borderRadius: '4px', padding: '2px 6px', flexShrink: '0' });
|
|
1319
1443
|
var hint = document.createElement('span');
|
|
1320
1444
|
hint.textContent = spec.locate || 'Not on the page right now';
|
|
1321
1445
|
Object.assign(hint.style, { fontSize: '11px', color: '#C9CBD2', overflow: 'hidden', textOverflow: 'ellipsis' });
|
|
@@ -1579,9 +1703,9 @@ function getClientScript(options) {
|
|
|
1579
1703
|
position: 'absolute',
|
|
1580
1704
|
background: RED,
|
|
1581
1705
|
color: '#fff',
|
|
1582
|
-
fontSize: '
|
|
1706
|
+
fontSize: '12px',
|
|
1583
1707
|
fontFamily: MONO,
|
|
1584
|
-
padding: '
|
|
1708
|
+
padding: '2px 5px',
|
|
1585
1709
|
borderRadius: '3px',
|
|
1586
1710
|
pointerEvents: 'none',
|
|
1587
1711
|
whiteSpace: 'nowrap',
|
|
@@ -1781,12 +1905,24 @@ function getClientScript(options) {
|
|
|
1781
1905
|
|
|
1782
1906
|
lastHovered = target;
|
|
1783
1907
|
|
|
1908
|
+
// Comment mode: outline only (so you know what you're commenting on), no
|
|
1909
|
+
// properties/measure overlay on screen. The Spec still captures everything.
|
|
1910
|
+
if (commentMode) {
|
|
1911
|
+
clearMeasureOverlay();
|
|
1912
|
+
clearMeasureTargetHL();
|
|
1913
|
+
setHoverOutline(target);
|
|
1914
|
+
hideTooltip();
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1784
1918
|
if (measureMode) {
|
|
1785
1919
|
clearHoverOutline();
|
|
1786
1920
|
showMeasureTargetHL(target);
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1921
|
+
// Draw the on-screen measurement overlay (the px badges + lines) but keep
|
|
1922
|
+
// the dark readout box hidden \u2014 it occludes the very measurements it reports.
|
|
1923
|
+
// The full readout still copies with Cmd+C. (Properties mode keeps its box.)
|
|
1924
|
+
if (pinEl && pinEl !== target) measureBetween(pinEl, target); else measureToNeighbor(target);
|
|
1925
|
+
hideTooltip();
|
|
1790
1926
|
if (pinEl) updatePinHL();
|
|
1791
1927
|
} else {
|
|
1792
1928
|
clearMeasureOverlay();
|
|
@@ -1868,6 +2004,21 @@ function getClientScript(options) {
|
|
|
1868
2004
|
return;
|
|
1869
2005
|
}
|
|
1870
2006
|
|
|
2007
|
+
// C \u2014 toggle Comment mode (outline only; props/measure hidden but still captured).
|
|
2008
|
+
// Plain c only \u2014 Cmd/Ctrl+C copy was handled above and returned.
|
|
2009
|
+
if ((e.key === 'c' || e.key === 'C') && !e.metaKey && !e.ctrlKey) {
|
|
2010
|
+
e.preventDefault();
|
|
2011
|
+
commentMode = !commentMode;
|
|
2012
|
+
if (commentMode && measureMode) { measureMode = false; clearPin(); }
|
|
2013
|
+
clearMeasureOverlay();
|
|
2014
|
+
clearMeasureTargetHL();
|
|
2015
|
+
clearHoverOutline();
|
|
2016
|
+
hideTooltip();
|
|
2017
|
+
if (lastHovered && commentMode) setHoverOutline(lastHovered);
|
|
2018
|
+
flashMode();
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1871
2022
|
// Esc only HIDES the plugin \u2014 Specs persist and return on reactivate.
|
|
1872
2023
|
if (e.key === 'Escape') {
|
|
1873
2024
|
if (panelOpen) { hidePanel(); return; }
|