vite-plugin-specter 0.3.3 → 0.4.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 +22 -4
- package/dist/client.js +254 -29
- package/dist/extension-chrome/background.js +4 -0
- package/dist/extension-chrome/content.js +1211 -0
- package/dist/extension-chrome/icons/icon128.png +0 -0
- package/dist/extension-chrome/icons/icon16.png +0 -0
- package/dist/extension-chrome/icons/icon32.png +0 -0
- package/dist/extension-chrome/icons/icon48.png +0 -0
- package/dist/extension-chrome/manifest.json +38 -0
- package/dist/extension-firefox/content.js +254 -29
- package/dist/extension-firefox/manifest.json +1 -1
- package/dist/index.cjs +254 -29
- package/dist/index.js +254 -29
- package/extension/background.js +1 -5
- package/extension/content.js +254 -29
- package/extension/icons/specter.png +0 -0
- package/extension/manifest.firefox.json +13 -5
- package/extension/manifest.json +8 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ function getClientScript(options) {
|
|
|
13
13
|
var LABEL = '#8B8D94';
|
|
14
14
|
var MONO = "'JetBrains Mono', 'SF Mono', 'Fira Code', monospace";
|
|
15
15
|
var ZAP = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>';
|
|
16
|
+
var PENCIL = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path><path d="m15 5 4 4"></path></svg>';
|
|
16
17
|
var ACTIVATE = ${JSON.stringify(activateShortcut)};
|
|
17
18
|
|
|
18
19
|
// \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
|
|
@@ -32,6 +33,9 @@ function getClientScript(options) {
|
|
|
32
33
|
var lastMouse = { x: 0, y: 0 };
|
|
33
34
|
var selectedEls = [];
|
|
34
35
|
var selectedBadges = [];
|
|
36
|
+
var noteMap = new Map();
|
|
37
|
+
var noteInputEl = null;
|
|
38
|
+
var noteTargetEl = null;
|
|
35
39
|
|
|
36
40
|
// \u2500\u2500\u2500 Tooltip \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
|
|
37
41
|
var tooltip = document.createElement('div');
|
|
@@ -191,11 +195,11 @@ function getClientScript(options) {
|
|
|
191
195
|
pillExpanded = true;
|
|
192
196
|
if (text) { pillText.textContent = text; return; }
|
|
193
197
|
if (selectedEls.length > 0) {
|
|
194
|
-
pillText.textContent = selectedEls.length + ' selected \xB7 Cmd+C copy all \xB7 Esc clear';
|
|
198
|
+
pillText.textContent = selectedEls.length + ' selected \xB7 N annotate \xB7 Cmd+C copy all \xB7 Esc clear';
|
|
195
199
|
} else if (measureMode) {
|
|
196
200
|
pillText.textContent = 'Measure \xB7 hover distances \xB7 M pin \xB7 Cmd+C copy \xB7 Option toggle';
|
|
197
201
|
} else {
|
|
198
|
-
pillText.textContent = 'Properties \xB7 hover
|
|
202
|
+
pillText.textContent = 'Properties \xB7 hover \xB7 P pick \xB7 N annotate \xB7 Cmd+C copy \xB7 Option measure';
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
|
|
@@ -276,17 +280,11 @@ function getClientScript(options) {
|
|
|
276
280
|
return { hex: toHex(m[1], m[2], m[3]), alpha: m[4] !== undefined ? parseFloat(m[4]) : 1 };
|
|
277
281
|
}
|
|
278
282
|
|
|
279
|
-
function colorLabel(str) {
|
|
280
|
-
var c = parseColor(str);
|
|
281
|
-
if (!c) return null;
|
|
282
|
-
if (c.alpha < 1) return c.hex + ' (' + Math.round(c.alpha * 100) + '% opacity)';
|
|
283
|
-
return c.hex;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
283
|
function colorObj(str) {
|
|
287
284
|
var c = parseColor(str);
|
|
288
285
|
if (!c) return null;
|
|
289
|
-
|
|
286
|
+
var label = c.alpha < 1 ? c.hex + ' (' + Math.round(c.alpha * 100) + '% opacity)' : c.hex;
|
|
287
|
+
return { raw: str, hex: c.hex, alpha: c.alpha, label: label };
|
|
290
288
|
}
|
|
291
289
|
|
|
292
290
|
function edge(t, r, b, l) {
|
|
@@ -321,6 +319,55 @@ function getClientScript(options) {
|
|
|
321
319
|
return null;
|
|
322
320
|
}
|
|
323
321
|
|
|
322
|
+
// Collapse a rule's declarations to their shortest lossless form: drop
|
|
323
|
+
// 'initial' (= default, no signal) and always-'normal' noise, and fold
|
|
324
|
+
// side/corner longhands back into shorthands (margin/padding/radius/gap/transition).
|
|
325
|
+
function cleanDecls(style) {
|
|
326
|
+
var m = {}, order = [];
|
|
327
|
+
for (var i = 0; i < style.length; i++) {
|
|
328
|
+
var p = style[i];
|
|
329
|
+
var v = style.getPropertyValue(p);
|
|
330
|
+
if (!v) continue;
|
|
331
|
+
if (v === 'initial') continue;
|
|
332
|
+
if (p === 'transition-behavior') continue;
|
|
333
|
+
if (!(p in m)) order.push(p);
|
|
334
|
+
m[p] = v;
|
|
335
|
+
}
|
|
336
|
+
var used = {}, collapses = {};
|
|
337
|
+
function four(name, t, r, b, l) {
|
|
338
|
+
if (m[t] === undefined || m[r] === undefined || m[b] === undefined || m[l] === undefined) return;
|
|
339
|
+
used[t] = used[r] = used[b] = used[l] = 1;
|
|
340
|
+
var a = m[t], c = m[r], d = m[b], e = m[l], sh;
|
|
341
|
+
if (a === c && c === d && d === e) sh = a;
|
|
342
|
+
else if (a === d && c === e) sh = a + ' ' + c;
|
|
343
|
+
else if (c === e) sh = a + ' ' + c + ' ' + d;
|
|
344
|
+
else sh = a + ' ' + c + ' ' + d + ' ' + e;
|
|
345
|
+
collapses[t] = name + ': ' + sh;
|
|
346
|
+
}
|
|
347
|
+
four('margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left');
|
|
348
|
+
four('padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left');
|
|
349
|
+
four('border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius');
|
|
350
|
+
if (m['row-gap'] !== undefined && m['column-gap'] !== undefined) {
|
|
351
|
+
var g = m['row-gap'] === m['column-gap'] ? m['row-gap'] : m['row-gap'] + ' ' + m['column-gap'];
|
|
352
|
+
collapses['row-gap'] = 'gap: ' + g; used['row-gap'] = used['column-gap'] = 1;
|
|
353
|
+
}
|
|
354
|
+
if (m['transition-property'] !== undefined) {
|
|
355
|
+
var tr = 'transition: ' + m['transition-property'];
|
|
356
|
+
if (m['transition-duration'] !== undefined) tr += ' ' + m['transition-duration'];
|
|
357
|
+
if (m['transition-timing-function'] !== undefined) tr += ' ' + m['transition-timing-function'];
|
|
358
|
+
if (m['transition-delay'] !== undefined && m['transition-delay'] !== '0s') tr += ' ' + m['transition-delay'];
|
|
359
|
+
collapses['transition-property'] = tr;
|
|
360
|
+
used['transition-property'] = used['transition-duration'] = used['transition-timing-function'] = used['transition-delay'] = 1;
|
|
361
|
+
}
|
|
362
|
+
var out = [];
|
|
363
|
+
order.forEach(function (p) {
|
|
364
|
+
if (collapses[p]) { out.push(' ' + collapses[p] + ';'); return; }
|
|
365
|
+
if (used[p]) return;
|
|
366
|
+
out.push(' ' + p + ': ' + m[p] + ';');
|
|
367
|
+
});
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
324
371
|
function getMatchingRules(el) {
|
|
325
372
|
var results = [];
|
|
326
373
|
try {
|
|
@@ -337,12 +384,7 @@ function getClientScript(options) {
|
|
|
337
384
|
if (/^[*,]|^:root|^html|^body$|^::before|^::after/.test(sel.trim())) continue;
|
|
338
385
|
try {
|
|
339
386
|
if (el.matches(sel)) {
|
|
340
|
-
var props =
|
|
341
|
-
for (var i = 0; i < rule.style.length; i++) {
|
|
342
|
-
var prop = rule.style[i];
|
|
343
|
-
var val = rule.style.getPropertyValue(prop);
|
|
344
|
-
if (val) props.push(' ' + prop + ': ' + val + ';');
|
|
345
|
-
}
|
|
387
|
+
var props = cleanDecls(rule.style);
|
|
346
388
|
if (props.length) results.push(sel + ' {\\n' + props.join('\\n') + '\\n}');
|
|
347
389
|
}
|
|
348
390
|
} catch (e) {}
|
|
@@ -462,9 +504,13 @@ function getClientScript(options) {
|
|
|
462
504
|
}
|
|
463
505
|
|
|
464
506
|
function buildMultiSelectCopyText() {
|
|
507
|
+
var n = selectedEls.length;
|
|
465
508
|
return selectedEls.map(function (el, i) {
|
|
466
509
|
var body = buildLLMClipboard(buildInfo(el));
|
|
467
|
-
|
|
510
|
+
var note = noteMap.get(el);
|
|
511
|
+
var header = n > 1 ? '[Specter ' + (i + 1) + '/' + n + ']' : '[Specter]';
|
|
512
|
+
if (note) header += '\\n\u270F\uFE0F CHANGE: ' + note;
|
|
513
|
+
return body.replace('[Specter]', header);
|
|
468
514
|
}).join('\\n\\n' + Array(41).join('\u2500') + '\\n\\n');
|
|
469
515
|
}
|
|
470
516
|
|
|
@@ -488,6 +534,7 @@ function getClientScript(options) {
|
|
|
488
534
|
function clearHoverOutline() {
|
|
489
535
|
if (outlinedEl) {
|
|
490
536
|
outlinedEl.style.outline = prevOutline;
|
|
537
|
+
outlinedEl.style.outlineOffset = '';
|
|
491
538
|
outlinedEl = null;
|
|
492
539
|
}
|
|
493
540
|
}
|
|
@@ -514,16 +561,25 @@ function getClientScript(options) {
|
|
|
514
561
|
}
|
|
515
562
|
|
|
516
563
|
// \u2500\u2500\u2500 Multi-select \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
|
|
517
|
-
function makeBadge(el, index) {
|
|
564
|
+
function makeBadge(el, index, note) {
|
|
518
565
|
var rect = el.getBoundingClientRect();
|
|
519
|
-
var
|
|
520
|
-
|
|
521
|
-
Object.assign(badge.style, {
|
|
566
|
+
var wrap = document.createElement('div');
|
|
567
|
+
Object.assign(wrap.style, {
|
|
522
568
|
position: 'fixed',
|
|
523
569
|
left: (rect.left - 4) + 'px',
|
|
524
570
|
top: (rect.top - 4) + 'px',
|
|
571
|
+
zIndex: '2147483644',
|
|
572
|
+
pointerEvents: 'none',
|
|
573
|
+
display: 'flex',
|
|
574
|
+
alignItems: 'flex-start',
|
|
575
|
+
gap: '4px',
|
|
576
|
+
});
|
|
577
|
+
var badge = document.createElement('div');
|
|
578
|
+
badge.textContent = String(index + 1);
|
|
579
|
+
Object.assign(badge.style, {
|
|
525
580
|
width: '20px',
|
|
526
581
|
height: '20px',
|
|
582
|
+
flexShrink: '0',
|
|
527
583
|
background: PURPLE,
|
|
528
584
|
color: '#fff',
|
|
529
585
|
fontSize: '11px',
|
|
@@ -533,19 +589,38 @@ function getClientScript(options) {
|
|
|
533
589
|
display: 'flex',
|
|
534
590
|
alignItems: 'center',
|
|
535
591
|
justifyContent: 'center',
|
|
536
|
-
zIndex: '2147483644',
|
|
537
|
-
pointerEvents: 'none',
|
|
538
592
|
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
|
|
539
593
|
});
|
|
540
|
-
|
|
541
|
-
|
|
594
|
+
wrap.appendChild(badge);
|
|
595
|
+
if (note) {
|
|
596
|
+
var chip = document.createElement('div');
|
|
597
|
+
chip.textContent = '\u270F\uFE0F ' + (note.length > 32 ? note.slice(0, 32) + '\u2026' : note);
|
|
598
|
+
Object.assign(chip.style, {
|
|
599
|
+
maxWidth: '240px',
|
|
600
|
+
background: TIP_BG,
|
|
601
|
+
color: '#fff',
|
|
602
|
+
fontSize: '10px',
|
|
603
|
+
lineHeight: '16px',
|
|
604
|
+
fontFamily: MONO,
|
|
605
|
+
padding: '2px 6px',
|
|
606
|
+
borderRadius: '4px',
|
|
607
|
+
whiteSpace: 'nowrap',
|
|
608
|
+
overflow: 'hidden',
|
|
609
|
+
textOverflow: 'ellipsis',
|
|
610
|
+
border: '1px solid ' + PURPLE,
|
|
611
|
+
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
|
|
612
|
+
});
|
|
613
|
+
wrap.appendChild(chip);
|
|
614
|
+
}
|
|
615
|
+
document.body.appendChild(wrap);
|
|
616
|
+
return wrap;
|
|
542
617
|
}
|
|
543
618
|
|
|
544
619
|
function rebuildBadges() {
|
|
545
620
|
selectedBadges.forEach(function (b) { b.remove(); });
|
|
546
621
|
selectedBadges.length = 0;
|
|
547
622
|
selectedEls.forEach(function (el, i) {
|
|
548
|
-
selectedBadges.push(makeBadge(el, i));
|
|
623
|
+
selectedBadges.push(makeBadge(el, i, noteMap.get(el)));
|
|
549
624
|
});
|
|
550
625
|
}
|
|
551
626
|
|
|
@@ -554,6 +629,7 @@ function getClientScript(options) {
|
|
|
554
629
|
if (idx >= 0) {
|
|
555
630
|
selectedEls.splice(idx, 1);
|
|
556
631
|
el.style.outline = '';
|
|
632
|
+
noteMap.delete(el);
|
|
557
633
|
} else {
|
|
558
634
|
selectedEls.push(el);
|
|
559
635
|
el.style.outline = '2px solid ' + PURPLE;
|
|
@@ -564,10 +640,12 @@ function getClientScript(options) {
|
|
|
564
640
|
}
|
|
565
641
|
|
|
566
642
|
function clearSelection() {
|
|
567
|
-
|
|
643
|
+
closeNoteInput();
|
|
644
|
+
selectedEls.forEach(function (el) { el.style.outline = ''; el.style.outlineOffset = ''; });
|
|
568
645
|
selectedEls.length = 0;
|
|
569
646
|
selectedBadges.forEach(function (b) { b.remove(); });
|
|
570
647
|
selectedBadges.length = 0;
|
|
648
|
+
noteMap.clear();
|
|
571
649
|
}
|
|
572
650
|
|
|
573
651
|
function updatePillForSelection() {
|
|
@@ -575,6 +653,142 @@ function getClientScript(options) {
|
|
|
575
653
|
else if (!pillWrap.matches(':hover')) collapsePill();
|
|
576
654
|
}
|
|
577
655
|
|
|
656
|
+
// \u2500\u2500\u2500 Annotations (inline change notes) \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
|
|
657
|
+
function openNoteInput(el) {
|
|
658
|
+
closeNoteInput();
|
|
659
|
+
if (selectedEls.indexOf(el) < 0) {
|
|
660
|
+
clearHoverOutline();
|
|
661
|
+
hideTooltip();
|
|
662
|
+
selectedEls.push(el);
|
|
663
|
+
el.style.outline = '2px solid ' + PURPLE;
|
|
664
|
+
el.style.outlineOffset = '-1px';
|
|
665
|
+
}
|
|
666
|
+
noteTargetEl = el;
|
|
667
|
+
var rect = el.getBoundingClientRect();
|
|
668
|
+
|
|
669
|
+
var box = document.createElement('div');
|
|
670
|
+
Object.assign(box.style, {
|
|
671
|
+
position: 'fixed',
|
|
672
|
+
zIndex: '2147483647',
|
|
673
|
+
display: 'inline-flex',
|
|
674
|
+
alignItems: 'flex-start',
|
|
675
|
+
gap: '8px',
|
|
676
|
+
boxSizing: 'border-box',
|
|
677
|
+
background: TIP_BG,
|
|
678
|
+
border: '1px solid ' + PURPLE,
|
|
679
|
+
borderRadius: '8px',
|
|
680
|
+
padding: '11px 12px',
|
|
681
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.35)',
|
|
682
|
+
fontFamily: MONO,
|
|
683
|
+
});
|
|
684
|
+
var pen = document.createElement('span');
|
|
685
|
+
pen.innerHTML = PENCIL;
|
|
686
|
+
Object.assign(pen.style, {
|
|
687
|
+
display: 'flex',
|
|
688
|
+
alignItems: 'center',
|
|
689
|
+
flexShrink: '0',
|
|
690
|
+
marginTop: '2px',
|
|
691
|
+
color: '#E0A3F5',
|
|
692
|
+
});
|
|
693
|
+
var input = document.createElement('textarea');
|
|
694
|
+
input.rows = 1;
|
|
695
|
+
input.placeholder = 'Describe the change\u2026 (Enter to save)';
|
|
696
|
+
input.value = noteMap.get(el) || '';
|
|
697
|
+
Object.assign(input.style, {
|
|
698
|
+
flexShrink: '0',
|
|
699
|
+
background: 'transparent',
|
|
700
|
+
border: 'none',
|
|
701
|
+
outline: 'none',
|
|
702
|
+
resize: 'none',
|
|
703
|
+
overflow: 'hidden',
|
|
704
|
+
color: '#fff',
|
|
705
|
+
fontFamily: MONO,
|
|
706
|
+
fontSize: '12px',
|
|
707
|
+
lineHeight: '18px',
|
|
708
|
+
padding: '0',
|
|
709
|
+
margin: '0',
|
|
710
|
+
whiteSpace: 'pre-wrap',
|
|
711
|
+
overflowWrap: 'anywhere',
|
|
712
|
+
});
|
|
713
|
+
// Hidden mirror to measure single-line text width so the field grows as you type.
|
|
714
|
+
var meas = document.createElement('span');
|
|
715
|
+
Object.assign(meas.style, {
|
|
716
|
+
position: 'absolute',
|
|
717
|
+
visibility: 'hidden',
|
|
718
|
+
whiteSpace: 'pre',
|
|
719
|
+
pointerEvents: 'none',
|
|
720
|
+
fontFamily: MONO,
|
|
721
|
+
fontSize: '12px',
|
|
722
|
+
left: '-9999px',
|
|
723
|
+
top: '0',
|
|
724
|
+
});
|
|
725
|
+
box.appendChild(pen);
|
|
726
|
+
box.appendChild(input);
|
|
727
|
+
box.appendChild(meas);
|
|
728
|
+
document.body.appendChild(box);
|
|
729
|
+
noteInputEl = box;
|
|
730
|
+
|
|
731
|
+
var margin = 8;
|
|
732
|
+
function textW(t) { meas.textContent = t; return meas.offsetWidth; }
|
|
733
|
+
|
|
734
|
+
// Anchor to the element; the box is repositioned around this as it grows.
|
|
735
|
+
var anchorL = rect.left, anchorT = rect.top, anchorB = rect.bottom;
|
|
736
|
+
|
|
737
|
+
function sizeAndPosition() {
|
|
738
|
+
var vw = window.innerWidth, vh = window.innerHeight;
|
|
739
|
+
var maxBoxW = Math.min(560, vw - margin * 2);
|
|
740
|
+
var maxTextW = Math.max(120, maxBoxW - 55); // room for icon + gaps + padding
|
|
741
|
+
var placeholderW = textW(input.placeholder);
|
|
742
|
+
var minTextW = Math.min(placeholderW, maxTextW);
|
|
743
|
+
var maxTaH = Math.min(14 * 18, Math.max(18, (vh - margin * 2) - 24)); // cap ~14 lines, never past viewport
|
|
744
|
+
|
|
745
|
+
// Width: grow with content up to the max, then wrapping takes over.
|
|
746
|
+
var single = textW(input.value || input.placeholder) + 3;
|
|
747
|
+
input.style.width = Math.min(Math.max(single, minTextW), maxTextW) + 'px';
|
|
748
|
+
// Height: fit wrapped content, scroll only in the extreme case.
|
|
749
|
+
input.style.height = 'auto';
|
|
750
|
+
var h = input.scrollHeight;
|
|
751
|
+
if (h > maxTaH) { input.style.height = maxTaH + 'px'; input.style.overflowY = 'auto'; }
|
|
752
|
+
else { input.style.height = h + 'px'; input.style.overflowY = 'hidden'; }
|
|
753
|
+
|
|
754
|
+
// Keep the whole box on screen.
|
|
755
|
+
var bw = box.offsetWidth, bh = box.offsetHeight;
|
|
756
|
+
var left = Math.min(Math.max(anchorL, margin), Math.max(margin, vw - bw - margin));
|
|
757
|
+
var top = anchorT - bh - 8; // prefer sitting above the element
|
|
758
|
+
if (top < margin) top = anchorB + 8; // otherwise below it
|
|
759
|
+
if (top + bh > vh - margin) top = Math.max(margin, vh - bh - margin);
|
|
760
|
+
box.style.left = left + 'px';
|
|
761
|
+
box.style.top = top + 'px';
|
|
762
|
+
}
|
|
763
|
+
sizeAndPosition();
|
|
764
|
+
|
|
765
|
+
input.addEventListener('input', sizeAndPosition);
|
|
766
|
+
input.addEventListener('keydown', function (ev) {
|
|
767
|
+
ev.stopPropagation();
|
|
768
|
+
if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); commitNote(input.value); }
|
|
769
|
+
else if (ev.key === 'Escape') { ev.preventDefault(); closeNoteInput(); }
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
rebuildBadges();
|
|
773
|
+
updatePillForSelection();
|
|
774
|
+
setTimeout(function () { input.focus(); }, 0);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function commitNote(val) {
|
|
778
|
+
val = (val || '').trim();
|
|
779
|
+
if (noteTargetEl) {
|
|
780
|
+
if (val) noteMap.set(noteTargetEl, val);
|
|
781
|
+
else noteMap.delete(noteTargetEl);
|
|
782
|
+
}
|
|
783
|
+
closeNoteInput();
|
|
784
|
+
rebuildBadges();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function closeNoteInput() {
|
|
788
|
+
if (noteInputEl) { noteInputEl.remove(); noteInputEl = null; }
|
|
789
|
+
noteTargetEl = null;
|
|
790
|
+
}
|
|
791
|
+
|
|
578
792
|
// \u2500\u2500\u2500 Pin (measure) \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
|
|
579
793
|
function setPin(el) {
|
|
580
794
|
pinEl = el;
|
|
@@ -871,6 +1085,7 @@ function getClientScript(options) {
|
|
|
871
1085
|
var target = e.target;
|
|
872
1086
|
if (!target || target === pillWrap || pillWrap.contains(target)) return;
|
|
873
1087
|
if (target === tooltip || tooltip.contains(target)) return;
|
|
1088
|
+
if (noteInputEl && (target === noteInputEl || noteInputEl.contains(target))) return;
|
|
874
1089
|
|
|
875
1090
|
lastHovered = target;
|
|
876
1091
|
|
|
@@ -905,6 +1120,9 @@ function getClientScript(options) {
|
|
|
905
1120
|
|
|
906
1121
|
if (!fiActive) return;
|
|
907
1122
|
|
|
1123
|
+
// Note editor open: let the field handle its own keys (Enter/Esc) and native copy/paste.
|
|
1124
|
+
if (noteInputEl) return;
|
|
1125
|
+
|
|
908
1126
|
if (e.key === 'Alt' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
|
909
1127
|
optionHeld = true;
|
|
910
1128
|
return;
|
|
@@ -935,6 +1153,12 @@ function getClientScript(options) {
|
|
|
935
1153
|
return;
|
|
936
1154
|
}
|
|
937
1155
|
|
|
1156
|
+
if (e.key === 'n' || e.key === 'N') {
|
|
1157
|
+
if (!lastHovered) return;
|
|
1158
|
+
openNoteInput(lastHovered);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
938
1162
|
if (e.key === 'm' || e.key === 'M') {
|
|
939
1163
|
if (!measureMode || !lastHovered) return;
|
|
940
1164
|
if (pinEl) {
|
|
@@ -979,8 +1203,9 @@ function getClientScript(options) {
|
|
|
979
1203
|
|
|
980
1204
|
window.__specterToggle = function() { if (fiActive) deactivate(); else activate(); };
|
|
981
1205
|
|
|
982
|
-
|
|
983
|
-
|
|
1206
|
+
var _rt = (typeof browser !== 'undefined' && browser.runtime) || (typeof chrome !== 'undefined' && chrome.runtime);
|
|
1207
|
+
if (_rt && _rt.onMessage) {
|
|
1208
|
+
_rt.onMessage.addListener(function(msg) {
|
|
984
1209
|
if (msg && msg.type === 'specter-toggle') window.__specterToggle();
|
|
985
1210
|
});
|
|
986
1211
|
}
|
package/extension/background.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
// Chrome MV3 service worker
|
|
2
|
-
// Toolbar click injects Specter on any page (not just localhost)
|
|
3
2
|
chrome.action.onClicked.addListener((tab) => {
|
|
4
|
-
chrome.
|
|
5
|
-
target: { tabId: tab.id },
|
|
6
|
-
files: ['content.js'],
|
|
7
|
-
});
|
|
3
|
+
chrome.tabs.sendMessage(tab.id, { type: 'specter-toggle' });
|
|
8
4
|
});
|