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.cjs
CHANGED
|
@@ -60,6 +60,7 @@ function getClientScript(options) {
|
|
|
60
60
|
// \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
|
|
61
61
|
var fiActive = false;
|
|
62
62
|
var measureMode = false;
|
|
63
|
+
var commentMode = false; // C: hide props/measure overlays for design review \u2014 Specs still capture them
|
|
63
64
|
var pinEl = null;
|
|
64
65
|
var pinHighlight = null;
|
|
65
66
|
var lastHovered = null;
|
|
@@ -225,7 +226,7 @@ function getClientScript(options) {
|
|
|
225
226
|
hoverFx(listBtn, { background: 'rgba(255,255,255,0.32)' }, { background: 'rgba(255,255,255,0.16)' });
|
|
226
227
|
|
|
227
228
|
var clearBtn = document.createElement('span');
|
|
228
|
-
clearBtn.textContent = '\u2715 Delete all
|
|
229
|
+
clearBtn.textContent = '\u2715 Delete all';
|
|
229
230
|
clearBtn.title = 'Delete all annotations';
|
|
230
231
|
Object.assign(clearBtn.style, {
|
|
231
232
|
display: 'none',
|
|
@@ -257,7 +258,18 @@ function getClientScript(options) {
|
|
|
257
258
|
chevron.addEventListener('click', function (e) { e.stopPropagation(); moveSide(); });
|
|
258
259
|
hoverFx(chevron, { opacity: '1', transform: 'scale(1.2)' }, { opacity: '0.8', transform: 'scale(1)' });
|
|
259
260
|
|
|
261
|
+
// Sync status in the control bar (only when a bridge is configured): spinner while
|
|
262
|
+
// syncing, green when synced, red on error \u2014 so you always know what's staged.
|
|
263
|
+
var pillSync = document.createElement('span');
|
|
264
|
+
Object.assign(pillSync.style, {
|
|
265
|
+
display: 'none', width: '8px', height: '8px', borderRadius: '999px',
|
|
266
|
+
background: '#6B7280', flexShrink: '0', boxSizing: 'border-box',
|
|
267
|
+
});
|
|
268
|
+
pillSync.title = 'Sync status \u2014 click to re-sync';
|
|
269
|
+
pillSync.addEventListener('click', function (e) { e.stopPropagation(); if (BRIDGE) doSync(); });
|
|
270
|
+
|
|
260
271
|
pill.appendChild(iconBtn);
|
|
272
|
+
pill.appendChild(pillSync);
|
|
261
273
|
pill.appendChild(pillText);
|
|
262
274
|
pill.appendChild(listBtn);
|
|
263
275
|
pill.appendChild(clearBtn);
|
|
@@ -265,6 +277,30 @@ function getClientScript(options) {
|
|
|
265
277
|
pillWrap.appendChild(pill);
|
|
266
278
|
document.body.appendChild(pillWrap);
|
|
267
279
|
|
|
280
|
+
// Keyframes for the sync spinner (injected once).
|
|
281
|
+
var spinStyle = document.createElement('style');
|
|
282
|
+
spinStyle.textContent = '@keyframes __specterSpin{to{transform:rotate(360deg)}}';
|
|
283
|
+
markUI(spinStyle);
|
|
284
|
+
document.head.appendChild(spinStyle);
|
|
285
|
+
|
|
286
|
+
// Shared dot styling for the control-bar AND side-panel sync indicators, so they
|
|
287
|
+
// always match: spinner while syncing, green when synced, red on error.
|
|
288
|
+
function applyDotState(el, state) {
|
|
289
|
+
el.style.boxSizing = 'border-box';
|
|
290
|
+
if (state === 'syncing') {
|
|
291
|
+
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' });
|
|
292
|
+
} else if (state === 'synced') {
|
|
293
|
+
Object.assign(el.style, { width: '8px', height: '8px', background: GREEN, border: 'none', animation: 'none' });
|
|
294
|
+
} else { // offline / error
|
|
295
|
+
Object.assign(el.style, { width: '8px', height: '8px', background: '#F26D6D', border: 'none', animation: 'none' });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function setPillSync(state) {
|
|
299
|
+
if (!BRIDGE) { pillSync.style.display = 'none'; return; }
|
|
300
|
+
pillSync.style.display = 'inline-block';
|
|
301
|
+
applyDotState(pillSync, state);
|
|
302
|
+
}
|
|
303
|
+
|
|
268
304
|
pillWrap.addEventListener('mouseenter', function () {
|
|
269
305
|
clearMeasureOverlay();
|
|
270
306
|
clearMeasureTargetHL();
|
|
@@ -294,6 +330,12 @@ function getClientScript(options) {
|
|
|
294
330
|
}
|
|
295
331
|
}
|
|
296
332
|
|
|
333
|
+
// The mode is shown at all times (persistent prefix), so you always know whether
|
|
334
|
+
// hovering shows properties, measurements, or nothing (Comment).
|
|
335
|
+
function modeLabel() {
|
|
336
|
+
return commentMode ? 'Comment' : (measureMode ? 'Measure' : 'Properties');
|
|
337
|
+
}
|
|
338
|
+
|
|
297
339
|
function expandPill(text) {
|
|
298
340
|
pill.style.maxWidth = '820px';
|
|
299
341
|
pillText.style.display = 'inline';
|
|
@@ -301,19 +343,17 @@ function getClientScript(options) {
|
|
|
301
343
|
listBtn.style.display = specs.length > 0 ? 'inline-flex' : 'none';
|
|
302
344
|
clearBtn.style.display = specs.length > 0 ? 'inline' : 'none';
|
|
303
345
|
pillExpanded = true;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
} else if (measureMode) {
|
|
308
|
-
pillText.textContent = 'Measure \xB7 hover distances \xB7 P mark \xB7 M pin \xB7 Cmd+C copy \xB7 Option toggle';
|
|
309
|
-
} else {
|
|
310
|
-
pillText.textContent = 'Properties \xB7 P mark / annotate \xB7 Cmd+C copy \xB7 Option measure';
|
|
311
|
-
}
|
|
346
|
+
// Just the mode (+ the action buttons when Specs exist). Shortcuts live in the
|
|
347
|
+
// side panel now, so the pill stays short.
|
|
348
|
+
pillText.textContent = text || modeLabel();
|
|
312
349
|
}
|
|
313
350
|
|
|
351
|
+
// "Collapsed" = the compact resting state: mode label only, no hints/buttons.
|
|
352
|
+
// Still shows the mode so it's visible at all times.
|
|
314
353
|
function collapsePill() {
|
|
315
|
-
|
|
316
|
-
pillText.style.display = '
|
|
354
|
+
pillText.textContent = modeLabel();
|
|
355
|
+
pillText.style.display = 'inline';
|
|
356
|
+
pill.style.maxWidth = '220px';
|
|
317
357
|
chevron.style.display = 'none';
|
|
318
358
|
listBtn.style.display = 'none';
|
|
319
359
|
clearBtn.style.display = 'none';
|
|
@@ -322,7 +362,7 @@ function getClientScript(options) {
|
|
|
322
362
|
|
|
323
363
|
function flashMode() {
|
|
324
364
|
if (pillExpanded && pillWrap.matches(':hover')) return;
|
|
325
|
-
var text = measureMode ? '
|
|
365
|
+
var text = commentMode ? 'Comment mode' : (measureMode ? 'Measure mode' : 'Properties mode');
|
|
326
366
|
expandPill(text);
|
|
327
367
|
clearTimeout(flashTimer);
|
|
328
368
|
flashTimer = setTimeout(function () {
|
|
@@ -343,11 +383,13 @@ function getClientScript(options) {
|
|
|
343
383
|
function activate() {
|
|
344
384
|
fiActive = true;
|
|
345
385
|
measureMode = false;
|
|
386
|
+
commentMode = false;
|
|
346
387
|
pillWrap.style.display = 'block';
|
|
347
388
|
document.body.style.cursor = 'crosshair';
|
|
348
389
|
updatePill();
|
|
349
390
|
startLoop();
|
|
350
391
|
reflowSpecs();
|
|
392
|
+
if (BRIDGE) doSync(); // resolve the control-bar sync dot (green if reachable, red if not)
|
|
351
393
|
}
|
|
352
394
|
|
|
353
395
|
function deactivate() {
|
|
@@ -631,24 +673,37 @@ function getClientScript(options) {
|
|
|
631
673
|
return ['[Specter]', head, 'find: ' + resolveLocator(data.el), props.join(' \xB7 ')].join('\\n');
|
|
632
674
|
}
|
|
633
675
|
|
|
676
|
+
// Group Specs on the SAME element so their identical properties aren't emitted
|
|
677
|
+
// twice \u2014 several comments on one thing share ONE property block, each with its
|
|
678
|
+
// own change note. Measure Specs always stand alone (each is a distinct reading).
|
|
679
|
+
function groupSpecs() {
|
|
680
|
+
var groups = [];
|
|
681
|
+
for (var i = 0; i < specs.length; i++) {
|
|
682
|
+
var s = specs[i], g = null;
|
|
683
|
+
if (s.kind === 'element' && s.el) {
|
|
684
|
+
for (var j = 0; j < groups.length; j++) { if (groups[j].kind === 'element' && groups[j].el === s.el) { g = groups[j]; break; } }
|
|
685
|
+
}
|
|
686
|
+
if (g) g.specs.push(s);
|
|
687
|
+
else groups.push({ kind: s.kind, el: s.el, specs: [s] });
|
|
688
|
+
}
|
|
689
|
+
return groups;
|
|
690
|
+
}
|
|
691
|
+
|
|
634
692
|
// Copy every Spec \u2014 using each Spec's body snapshotted at mark time, so Specs
|
|
635
693
|
// whose element is currently hidden (e.g. inside a closed modal) still copy.
|
|
694
|
+
// Same-element Specs collapse into one block (properties once, notes stacked).
|
|
636
695
|
function buildSpecsCopyText() {
|
|
637
|
-
var
|
|
638
|
-
|
|
639
|
-
|
|
696
|
+
var groups = groupSpecs();
|
|
697
|
+
var n = groups.length;
|
|
698
|
+
return groups.map(function (g, i) {
|
|
699
|
+
var tag = g.kind === 'measure' ? 'Specter Measure' : 'Specter';
|
|
640
700
|
var header = n > 1 ? '[' + tag + ' ' + (i + 1) + '/' + n + ']' : '[' + tag + ']';
|
|
641
|
-
|
|
642
|
-
|
|
701
|
+
var notes = g.specs.filter(function (s) { return s.note; }).map(function (s) { return '\u270F\uFE0F CHANGE: ' + s.note; });
|
|
702
|
+
if (notes.length) header += '\\n' + notes.join('\\n');
|
|
703
|
+
return g.specs[0].body.replace(/^\\[Specter[^\\]]*\\]/, function () { return header; });
|
|
643
704
|
}).join('\\n\\n---\\n\\n');
|
|
644
705
|
}
|
|
645
706
|
|
|
646
|
-
function linesToHTML(str) {
|
|
647
|
-
return str.split('\\n').map(function (l) {
|
|
648
|
-
return '<div style="margin-bottom:4px;color:#fff">' + esc(l) + '</div>';
|
|
649
|
-
}).join('');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
707
|
// \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
|
|
653
708
|
function setHoverOutline(el) {
|
|
654
709
|
if (outlinedEl === el) return;
|
|
@@ -680,12 +735,14 @@ function getClientScript(options) {
|
|
|
680
735
|
}
|
|
681
736
|
|
|
682
737
|
function reRenderTooltip() {
|
|
738
|
+
if (commentMode) { hideTooltip(); return; }
|
|
683
739
|
if (measureMode) {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
740
|
+
// Redraw the overlay on scroll/resize; readout box stays hidden (see hover handler).
|
|
741
|
+
if (pinEl && pinEl !== lastHovered) measureBetween(pinEl, lastHovered); else measureToNeighbor(lastHovered);
|
|
742
|
+
hideTooltip();
|
|
743
|
+
return;
|
|
688
744
|
}
|
|
745
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(lastHovered));
|
|
689
746
|
positionTooltip(lastMouse.x, lastMouse.y);
|
|
690
747
|
}
|
|
691
748
|
|
|
@@ -841,7 +898,7 @@ function getClientScript(options) {
|
|
|
841
898
|
|
|
842
899
|
function updateBadgeContent(spec) {
|
|
843
900
|
spec.num.textContent = String(specs.indexOf(spec) + 1);
|
|
844
|
-
if (spec.note) spec.noteSpan.textContent =
|
|
901
|
+
if (spec.note) spec.noteSpan.textContent = spec.note;
|
|
845
902
|
else spec.noteSpan.textContent = spec.kind === 'measure' ? '\u2B21 measure' : '';
|
|
846
903
|
}
|
|
847
904
|
|
|
@@ -886,10 +943,6 @@ function getClientScript(options) {
|
|
|
886
943
|
updateBadgeContent(spec);
|
|
887
944
|
}
|
|
888
945
|
|
|
889
|
-
function findElementSpec(el) {
|
|
890
|
-
for (var i = 0; i < specs.length; i++) if (specs[i].kind === 'element' && specs[i].el === el) return specs[i];
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
946
|
|
|
894
947
|
// If the element sits inside a dialog/modal/drawer/menu, produce a hint for how to
|
|
895
948
|
// bring it back when it's later hidden \u2014 a declarative opener (aria-controls /
|
|
@@ -928,7 +981,9 @@ function getClientScript(options) {
|
|
|
928
981
|
if (pinEl && lastHovered && lastHovered !== pinEl) { el = pinEl; body = buildMeasureCopyText(pinEl, lastHovered); }
|
|
929
982
|
else { el = anchorEl; body = buildNeighborCopyText(anchorEl); }
|
|
930
983
|
} else {
|
|
931
|
-
|
|
984
|
+
// Multiple Specs per element are allowed in every mode \u2014 clustering fans the badges
|
|
985
|
+
// out and same-element output collapses to one property block. Mode only changes
|
|
986
|
+
// what's shown on screen, never whether you can add an annotation.
|
|
932
987
|
body = buildLLMClipboard(buildInfo(anchorEl));
|
|
933
988
|
}
|
|
934
989
|
clearHoverOutline();
|
|
@@ -1166,14 +1221,74 @@ function getClientScript(options) {
|
|
|
1166
1221
|
}
|
|
1167
1222
|
|
|
1168
1223
|
var panelList = document.createElement('div');
|
|
1169
|
-
|
|
1224
|
+
// overscroll-behavior:contain stops the panel's scroll from chaining into the page.
|
|
1225
|
+
Object.assign(panelList.style, { flex: '1', overflowY: 'auto', overflowX: 'hidden', overscrollBehavior: 'contain' });
|
|
1226
|
+
|
|
1227
|
+
// Keyboard shortcuts reference \u2014 collapsible, collapsed by default, lives here (not
|
|
1228
|
+
// in the pill) so the pill stays short.
|
|
1229
|
+
var panelKeys = document.createElement('div');
|
|
1230
|
+
Object.assign(panelKeys.style, {
|
|
1231
|
+
flexShrink: '0', fontSize: '11px', lineHeight: '18px', color: LABEL,
|
|
1232
|
+
borderTop: '1px solid rgba(255,255,255,0.08)',
|
|
1233
|
+
});
|
|
1234
|
+
(function () {
|
|
1235
|
+
var rows = [
|
|
1236
|
+
['P', 'mark / comment the hovered element'],
|
|
1237
|
+
['C', 'toggle Comment mode (hide properties)'],
|
|
1238
|
+
['\\u2325 Option', 'toggle Measure mode'],
|
|
1239
|
+
['M', 'pin an element to measure from'],
|
|
1240
|
+
['Cmd/Ctrl+C', 'copy all Specs'],
|
|
1241
|
+
['L', 'toggle this panel'],
|
|
1242
|
+
];
|
|
1243
|
+
var head = document.createElement('div');
|
|
1244
|
+
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' });
|
|
1245
|
+
var caret = document.createElement('span');
|
|
1246
|
+
caret.textContent = '\\u203A'; // \u203A
|
|
1247
|
+
Object.assign(caret.style, { display: 'inline-block', fontSize: '16px', lineHeight: '1', transition: 'transform 0.15s ease', transform: 'rotate(0deg)' });
|
|
1248
|
+
var headText = document.createElement('span');
|
|
1249
|
+
headText.textContent = 'Keyboard shortcuts';
|
|
1250
|
+
head.appendChild(caret);
|
|
1251
|
+
head.appendChild(headText);
|
|
1252
|
+
var body = document.createElement('div');
|
|
1253
|
+
Object.assign(body.style, { display: 'none', padding: '0 16px 12px' }); // collapsed by default
|
|
1254
|
+
rows.forEach(function (r) {
|
|
1255
|
+
var row = document.createElement('div');
|
|
1256
|
+
Object.assign(row.style, { display: 'flex', gap: '8px', marginBottom: '2px' });
|
|
1257
|
+
var k = document.createElement('span');
|
|
1258
|
+
k.textContent = r[0];
|
|
1259
|
+
Object.assign(k.style, { color: '#E0A3F5', fontWeight: '700', minWidth: '78px', flexShrink: '0' });
|
|
1260
|
+
var d = document.createElement('span');
|
|
1261
|
+
d.textContent = r[1];
|
|
1262
|
+
row.appendChild(k); row.appendChild(d);
|
|
1263
|
+
body.appendChild(row);
|
|
1264
|
+
});
|
|
1265
|
+
var open = false;
|
|
1266
|
+
head.addEventListener('click', function (e) {
|
|
1267
|
+
e.stopPropagation();
|
|
1268
|
+
open = !open;
|
|
1269
|
+
body.style.display = open ? 'block' : 'none';
|
|
1270
|
+
caret.style.transform = open ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
1271
|
+
});
|
|
1272
|
+
panelKeys.appendChild(head);
|
|
1273
|
+
panelKeys.appendChild(body);
|
|
1274
|
+
})();
|
|
1170
1275
|
|
|
1171
1276
|
panelWrap.appendChild(panelHead);
|
|
1172
1277
|
panelWrap.appendChild(panelTools);
|
|
1173
1278
|
panelWrap.appendChild(panelHint);
|
|
1174
1279
|
panelWrap.appendChild(panelList);
|
|
1280
|
+
panelWrap.appendChild(panelKeys);
|
|
1175
1281
|
document.body.appendChild(panelWrap);
|
|
1176
1282
|
|
|
1283
|
+
// Panel scroll and page scroll are mutually exclusive: wheel over the scrollable list
|
|
1284
|
+
// scrolls it natively (overscroll-behavior:contain stops it chaining at the bounds);
|
|
1285
|
+
// wheel over any non-scrolling part of the panel never scrolls the page behind it.
|
|
1286
|
+
panelWrap.addEventListener('wheel', function (e) {
|
|
1287
|
+
var canScroll = panelList.scrollHeight > panelList.clientHeight;
|
|
1288
|
+
if (panelList.contains(e.target) && canScroll) return; // let the list scroll natively
|
|
1289
|
+
e.preventDefault();
|
|
1290
|
+
}, { passive: false });
|
|
1291
|
+
|
|
1177
1292
|
// \u2500\u2500 Auto-sync: mirror the browser's current Specs to the local bridge \u2500\u2500
|
|
1178
1293
|
// Debounced so rapid edits/typing collapse into one POST. The bridge replaces
|
|
1179
1294
|
// its snapshot for this URL, so it always reflects what's in the panel \u2014 no
|
|
@@ -1181,24 +1296,33 @@ function getClientScript(options) {
|
|
|
1181
1296
|
var syncTimer = null;
|
|
1182
1297
|
function setSyncState(s) {
|
|
1183
1298
|
if (!BRIDGE) return;
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1299
|
+
setPillSync(s); // control-bar dot
|
|
1300
|
+
applyDotState(dot, s); // side-panel dot \u2014 same spinner/green/red
|
|
1301
|
+
if (s === 'syncing') { dotLabel.textContent = 'Syncing\u2026'; }
|
|
1302
|
+
else if (s === 'synced') { dotLabel.textContent = specs.length ? (specs.length + (specs.length === 1 ? ' Spec synced' : ' Specs synced')) : 'Synced'; }
|
|
1303
|
+
else { dotLabel.textContent = 'Bridge offline'; }
|
|
1187
1304
|
}
|
|
1188
1305
|
function doSync() {
|
|
1189
1306
|
if (!BRIDGE) return;
|
|
1190
1307
|
setSyncState('syncing');
|
|
1308
|
+
// Keep the spinner up for at least 1s even on an instant localhost sync, so the
|
|
1309
|
+
// feedback is actually perceptible instead of flashing by.
|
|
1310
|
+
var started = Date.now();
|
|
1311
|
+
var settle = function (state) {
|
|
1312
|
+
var wait = Math.max(0, 1000 - (Date.now() - started));
|
|
1313
|
+
setTimeout(function () { setSyncState(state); }, wait);
|
|
1314
|
+
};
|
|
1191
1315
|
var payload = {
|
|
1192
1316
|
url: location.href,
|
|
1193
1317
|
text: buildSpecsCopyText(),
|
|
1194
|
-
specs:
|
|
1195
|
-
return { num: i + 1, note: s.note
|
|
1318
|
+
specs: groupSpecs().map(function (g, i) {
|
|
1319
|
+
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 };
|
|
1196
1320
|
}),
|
|
1197
1321
|
};
|
|
1198
1322
|
fetch(BRIDGE + '/specs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })
|
|
1199
1323
|
.then(function (r) { if (!r.ok) throw 0; return r.json(); })
|
|
1200
|
-
.then(function () {
|
|
1201
|
-
.catch(function () {
|
|
1324
|
+
.then(function () { settle('synced'); })
|
|
1325
|
+
.catch(function () { settle('offline'); });
|
|
1202
1326
|
}
|
|
1203
1327
|
function scheduleSync() {
|
|
1204
1328
|
if (!BRIDGE) return;
|
|
@@ -1342,7 +1466,7 @@ function getClientScript(options) {
|
|
|
1342
1466
|
Object.assign(hbar.style, { display: 'flex', alignItems: 'center', gap: '7px', marginTop: '2px', flexWrap: 'wrap' });
|
|
1343
1467
|
var tag = document.createElement('span');
|
|
1344
1468
|
tag.textContent = 'HIDDEN';
|
|
1345
|
-
Object.assign(tag.style, { fontSize: '
|
|
1469
|
+
Object.assign(tag.style, { fontSize: '11px', fontWeight: '700', letterSpacing: '0.05em', color: '#3A2A05', background: '#F59E0B', borderRadius: '4px', padding: '2px 6px', flexShrink: '0' });
|
|
1346
1470
|
var hint = document.createElement('span');
|
|
1347
1471
|
hint.textContent = spec.locate || 'Not on the page right now';
|
|
1348
1472
|
Object.assign(hint.style, { fontSize: '11px', color: '#C9CBD2', overflow: 'hidden', textOverflow: 'ellipsis' });
|
|
@@ -1606,9 +1730,9 @@ function getClientScript(options) {
|
|
|
1606
1730
|
position: 'absolute',
|
|
1607
1731
|
background: RED,
|
|
1608
1732
|
color: '#fff',
|
|
1609
|
-
fontSize: '
|
|
1733
|
+
fontSize: '12px',
|
|
1610
1734
|
fontFamily: MONO,
|
|
1611
|
-
padding: '
|
|
1735
|
+
padding: '2px 5px',
|
|
1612
1736
|
borderRadius: '3px',
|
|
1613
1737
|
pointerEvents: 'none',
|
|
1614
1738
|
whiteSpace: 'nowrap',
|
|
@@ -1808,12 +1932,24 @@ function getClientScript(options) {
|
|
|
1808
1932
|
|
|
1809
1933
|
lastHovered = target;
|
|
1810
1934
|
|
|
1935
|
+
// Comment mode: outline only (so you know what you're commenting on), no
|
|
1936
|
+
// properties/measure overlay on screen. The Spec still captures everything.
|
|
1937
|
+
if (commentMode) {
|
|
1938
|
+
clearMeasureOverlay();
|
|
1939
|
+
clearMeasureTargetHL();
|
|
1940
|
+
setHoverOutline(target);
|
|
1941
|
+
hideTooltip();
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1811
1945
|
if (measureMode) {
|
|
1812
1946
|
clearHoverOutline();
|
|
1813
1947
|
showMeasureTargetHL(target);
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1948
|
+
// Draw the on-screen measurement overlay (the px badges + lines) but keep
|
|
1949
|
+
// the dark readout box hidden \u2014 it occludes the very measurements it reports.
|
|
1950
|
+
// The full readout still copies with Cmd+C. (Properties mode keeps its box.)
|
|
1951
|
+
if (pinEl && pinEl !== target) measureBetween(pinEl, target); else measureToNeighbor(target);
|
|
1952
|
+
hideTooltip();
|
|
1817
1953
|
if (pinEl) updatePinHL();
|
|
1818
1954
|
} else {
|
|
1819
1955
|
clearMeasureOverlay();
|
|
@@ -1895,6 +2031,21 @@ function getClientScript(options) {
|
|
|
1895
2031
|
return;
|
|
1896
2032
|
}
|
|
1897
2033
|
|
|
2034
|
+
// C \u2014 toggle Comment mode (outline only; props/measure hidden but still captured).
|
|
2035
|
+
// Plain c only \u2014 Cmd/Ctrl+C copy was handled above and returned.
|
|
2036
|
+
if ((e.key === 'c' || e.key === 'C') && !e.metaKey && !e.ctrlKey) {
|
|
2037
|
+
e.preventDefault();
|
|
2038
|
+
commentMode = !commentMode;
|
|
2039
|
+
if (commentMode && measureMode) { measureMode = false; clearPin(); }
|
|
2040
|
+
clearMeasureOverlay();
|
|
2041
|
+
clearMeasureTargetHL();
|
|
2042
|
+
clearHoverOutline();
|
|
2043
|
+
hideTooltip();
|
|
2044
|
+
if (lastHovered && commentMode) setHoverOutline(lastHovered);
|
|
2045
|
+
flashMode();
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1898
2049
|
// Esc only HIDES the plugin \u2014 Specs persist and return on reactivate.
|
|
1899
2050
|
if (e.key === 'Escape') {
|
|
1900
2051
|
if (panelOpen) { hidePanel(); return; }
|