web-remarq 0.6.0 → 0.7.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 +27 -2
- package/dist/core/index.cjs +101 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +39 -3
- package/dist/core/index.d.ts +39 -3
- package/dist/core/index.js +96 -4
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +456 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +456 -76
- package/dist/index.js.map +1 -1
- package/dist/web-remarq.global.global.js +456 -76
- package/dist/web-remarq.global.global.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65,6 +65,21 @@ function destroyViewportListener() {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// src/core/storage.ts
|
|
68
|
+
function migrateAnnotation(legacy) {
|
|
69
|
+
const rawStatus = legacy.status;
|
|
70
|
+
const status = rawStatus === "resolved" ? "verified" : rawStatus;
|
|
71
|
+
if (Array.isArray(legacy.lifecycle) && legacy.lifecycle.length > 0) {
|
|
72
|
+
return __spreadProps(__spreadValues({}, legacy), { status, lifecycle: legacy.lifecycle });
|
|
73
|
+
}
|
|
74
|
+
const createdTs = typeof legacy.timestamp === "number" ? legacy.timestamp : Date.now();
|
|
75
|
+
const lifecycle = [
|
|
76
|
+
{ type: "created", actor: "designer", timestamp: createdTs }
|
|
77
|
+
];
|
|
78
|
+
if (rawStatus === "resolved") {
|
|
79
|
+
lifecycle.push({ type: "migrated", actor: null, timestamp: Date.now() });
|
|
80
|
+
}
|
|
81
|
+
return __spreadProps(__spreadValues({}, legacy), { status, lifecycle });
|
|
82
|
+
}
|
|
68
83
|
var AnnotationStorage = class {
|
|
69
84
|
constructor(adapter) {
|
|
70
85
|
this.adapter = adapter;
|
|
@@ -81,6 +96,9 @@ var AnnotationStorage = class {
|
|
|
81
96
|
getByRoute(route) {
|
|
82
97
|
return this.cache.filter((a) => a.route === route);
|
|
83
98
|
}
|
|
99
|
+
getById(id) {
|
|
100
|
+
return this.cache.find((a) => a.id === id);
|
|
101
|
+
}
|
|
84
102
|
async add(annotation) {
|
|
85
103
|
this.cache.push(annotation);
|
|
86
104
|
await this.adapter.save(annotation);
|
|
@@ -107,7 +125,7 @@ var AnnotationStorage = class {
|
|
|
107
125
|
};
|
|
108
126
|
}
|
|
109
127
|
async importJSON(data) {
|
|
110
|
-
this.cache =
|
|
128
|
+
this.cache = data.annotations.map(migrateAnnotation);
|
|
111
129
|
this.migrateViewportBuckets();
|
|
112
130
|
await this.adapter.clear();
|
|
113
131
|
for (const ann of this.cache) {
|
|
@@ -117,7 +135,7 @@ var AnnotationStorage = class {
|
|
|
117
135
|
async init() {
|
|
118
136
|
const data = await this.adapter.load();
|
|
119
137
|
if (data) {
|
|
120
|
-
this.cache = data.annotations;
|
|
138
|
+
this.cache = data.annotations.map(migrateAnnotation);
|
|
121
139
|
this.migrateViewportBuckets();
|
|
122
140
|
}
|
|
123
141
|
}
|
|
@@ -587,7 +605,16 @@ function generateAgentExport(annotations, viewportBucket) {
|
|
|
587
605
|
status: ann.status,
|
|
588
606
|
timestamp: ann.timestamp,
|
|
589
607
|
source: resolveSource(ann.fingerprint),
|
|
590
|
-
searchHints: buildSearchHints(ann.fingerprint)
|
|
608
|
+
searchHints: buildSearchHints(ann.fingerprint),
|
|
609
|
+
lifecycle: ann.lifecycle.map((ev) => {
|
|
610
|
+
const out = {
|
|
611
|
+
type: ev.type,
|
|
612
|
+
actor: ev.actor,
|
|
613
|
+
timestamp: ev.timestamp
|
|
614
|
+
};
|
|
615
|
+
if (ev.reason !== void 0) out.reason = ev.reason;
|
|
616
|
+
return out;
|
|
617
|
+
})
|
|
591
618
|
}));
|
|
592
619
|
return {
|
|
593
620
|
version: 1,
|
|
@@ -597,6 +624,67 @@ function generateAgentExport(annotations, viewportBucket) {
|
|
|
597
624
|
};
|
|
598
625
|
}
|
|
599
626
|
|
|
627
|
+
// src/core/lifecycle.ts
|
|
628
|
+
var InvalidTransitionError = class extends Error {
|
|
629
|
+
constructor(from, action) {
|
|
630
|
+
super(`Cannot ${action} from status "${from}"`);
|
|
631
|
+
this.name = "InvalidTransitionError";
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var ACTION_TO_EVENT = {
|
|
635
|
+
acknowledge: "acknowledged",
|
|
636
|
+
claimFix: "fix_claimed",
|
|
637
|
+
verify: "verified",
|
|
638
|
+
reject: "rejected",
|
|
639
|
+
dismiss: "dismissed",
|
|
640
|
+
reopen: "reopened"
|
|
641
|
+
};
|
|
642
|
+
var DEFAULT_ACTOR_BY_EVENT = {
|
|
643
|
+
created: "designer",
|
|
644
|
+
acknowledged: "developer",
|
|
645
|
+
fix_claimed: "agent",
|
|
646
|
+
verified: "developer",
|
|
647
|
+
rejected: "developer",
|
|
648
|
+
dismissed: "developer",
|
|
649
|
+
reopened: "developer",
|
|
650
|
+
migrated: null
|
|
651
|
+
};
|
|
652
|
+
function createEvent(type, opts = {}) {
|
|
653
|
+
var _a3, _b;
|
|
654
|
+
const event = {
|
|
655
|
+
type,
|
|
656
|
+
actor: (_a3 = opts.actor) != null ? _a3 : DEFAULT_ACTOR_BY_EVENT[type],
|
|
657
|
+
timestamp: (_b = opts.timestamp) != null ? _b : Date.now()
|
|
658
|
+
};
|
|
659
|
+
if (opts.actorName !== void 0) event.actorName = opts.actorName;
|
|
660
|
+
if (opts.reason !== void 0) event.reason = opts.reason;
|
|
661
|
+
return event;
|
|
662
|
+
}
|
|
663
|
+
function nextStatus(from, action) {
|
|
664
|
+
switch (action) {
|
|
665
|
+
case "acknowledge":
|
|
666
|
+
return from === "pending" ? "in_progress" : null;
|
|
667
|
+
case "claimFix":
|
|
668
|
+
return from === "pending" || from === "in_progress" ? "fixed_unverified" : null;
|
|
669
|
+
case "verify":
|
|
670
|
+
return from === "fixed_unverified" || from === "in_progress" ? "verified" : null;
|
|
671
|
+
case "reject":
|
|
672
|
+
return from === "fixed_unverified" ? "pending" : null;
|
|
673
|
+
case "dismiss":
|
|
674
|
+
return from === "pending" || from === "in_progress" || from === "fixed_unverified" ? "dismissed" : null;
|
|
675
|
+
case "reopen":
|
|
676
|
+
return from === "dismissed" || from === "verified" ? "pending" : null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function transition(annotation, action, opts = {}) {
|
|
680
|
+
const next = nextStatus(annotation.status, action);
|
|
681
|
+
if (next === null) {
|
|
682
|
+
throw new InvalidTransitionError(annotation.status, action);
|
|
683
|
+
}
|
|
684
|
+
const event = createEvent(ACTION_TO_EVENT[action], opts);
|
|
685
|
+
return { status: next, event };
|
|
686
|
+
}
|
|
687
|
+
|
|
600
688
|
// src/ui/styles.ts
|
|
601
689
|
var STYLES_ID = "data-remarq-styles";
|
|
602
690
|
var CSS = `
|
|
@@ -611,6 +699,11 @@ var CSS = `
|
|
|
611
699
|
--remarq-resolved: #22c55e;
|
|
612
700
|
--remarq-overlay: rgba(59, 130, 246, 0.15);
|
|
613
701
|
--remarq-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
702
|
+
--remarq-status-pending: #f97316;
|
|
703
|
+
--remarq-status-in-progress: #eab308;
|
|
704
|
+
--remarq-status-fixed-unverified: #3b82f6;
|
|
705
|
+
--remarq-status-verified: #22c55e;
|
|
706
|
+
--remarq-status-dismissed: #6b7280;
|
|
614
707
|
}
|
|
615
708
|
|
|
616
709
|
[data-remarq-theme="dark"] {
|
|
@@ -624,6 +717,11 @@ var CSS = `
|
|
|
624
717
|
--remarq-resolved: #4ade80;
|
|
625
718
|
--remarq-overlay: rgba(96, 165, 250, 0.15);
|
|
626
719
|
--remarq-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
720
|
+
--remarq-status-pending: #fb923c;
|
|
721
|
+
--remarq-status-in-progress: #facc15;
|
|
722
|
+
--remarq-status-fixed-unverified: #60a5fa;
|
|
723
|
+
--remarq-status-verified: #4ade80;
|
|
724
|
+
--remarq-status-dismissed: #9ca3af;
|
|
627
725
|
}
|
|
628
726
|
|
|
629
727
|
.remarq-toolbar {
|
|
@@ -667,19 +765,29 @@ var CSS = `
|
|
|
667
765
|
|
|
668
766
|
.remarq-badge {
|
|
669
767
|
position: absolute;
|
|
670
|
-
top: -
|
|
671
|
-
right: -
|
|
672
|
-
|
|
673
|
-
height:
|
|
674
|
-
padding: 0
|
|
675
|
-
border-radius:
|
|
768
|
+
top: -6px;
|
|
769
|
+
right: -6px;
|
|
770
|
+
width: 18px;
|
|
771
|
+
height: 18px;
|
|
772
|
+
padding: 0;
|
|
773
|
+
border-radius: 50%;
|
|
676
774
|
background: var(--remarq-pending);
|
|
677
775
|
color: #ffffff;
|
|
678
776
|
font-size: 10px;
|
|
679
777
|
font-weight: 600;
|
|
778
|
+
line-height: 1;
|
|
680
779
|
display: flex;
|
|
681
780
|
align-items: center;
|
|
682
781
|
justify-content: center;
|
|
782
|
+
box-sizing: border-box;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.remarq-toolbar-badge--verification {
|
|
786
|
+
top: auto;
|
|
787
|
+
bottom: -6px;
|
|
788
|
+
right: -6px;
|
|
789
|
+
background: var(--remarq-status-fixed-unverified);
|
|
790
|
+
cursor: pointer;
|
|
683
791
|
}
|
|
684
792
|
|
|
685
793
|
.remarq-overlay {
|
|
@@ -729,8 +837,11 @@ var CSS = `
|
|
|
729
837
|
}
|
|
730
838
|
|
|
731
839
|
.remarq-marker:hover { transform: scale(1.2); }
|
|
732
|
-
.remarq-marker
|
|
733
|
-
.remarq-marker
|
|
840
|
+
.remarq-marker--pending { background: var(--remarq-status-pending); }
|
|
841
|
+
.remarq-marker--in-progress { background: var(--remarq-status-in-progress); }
|
|
842
|
+
.remarq-marker--fixed-unverified { background: var(--remarq-status-fixed-unverified); }
|
|
843
|
+
.remarq-marker--verified { background: var(--remarq-status-verified); opacity: 0.7; }
|
|
844
|
+
.remarq-marker--dismissed { background: var(--remarq-status-dismissed); opacity: 0.5; }
|
|
734
845
|
|
|
735
846
|
.remarq-popup {
|
|
736
847
|
position: absolute;
|
|
@@ -775,14 +886,30 @@ var CSS = `
|
|
|
775
886
|
|
|
776
887
|
.remarq-popup-actions {
|
|
777
888
|
display: flex;
|
|
778
|
-
|
|
779
|
-
gap:
|
|
889
|
+
flex-direction: column;
|
|
890
|
+
gap: 6px;
|
|
780
891
|
padding: 8px 12px;
|
|
781
892
|
border-top: 1px solid var(--remarq-border);
|
|
782
893
|
}
|
|
783
894
|
|
|
895
|
+
.remarq-popup-actions-row {
|
|
896
|
+
display: flex;
|
|
897
|
+
flex-wrap: wrap;
|
|
898
|
+
gap: 6px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.remarq-popup-actions-row--transitions {
|
|
902
|
+
justify-content: flex-start;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.remarq-popup-actions-row--utility {
|
|
906
|
+
justify-content: flex-end;
|
|
907
|
+
padding-top: 6px;
|
|
908
|
+
border-top: 1px dashed var(--remarq-border);
|
|
909
|
+
}
|
|
910
|
+
|
|
784
911
|
.remarq-popup-actions button {
|
|
785
|
-
padding:
|
|
912
|
+
padding: 5px 12px;
|
|
786
913
|
border: 1px solid var(--remarq-border);
|
|
787
914
|
border-radius: 4px;
|
|
788
915
|
background: var(--remarq-bg);
|
|
@@ -797,6 +924,19 @@ var CSS = `
|
|
|
797
924
|
color: #ffffff;
|
|
798
925
|
}
|
|
799
926
|
|
|
927
|
+
.remarq-popup-utility-btn {
|
|
928
|
+
font-size: 11px !important;
|
|
929
|
+
padding: 3px 10px !important;
|
|
930
|
+
color: var(--remarq-text-secondary) !important;
|
|
931
|
+
background: transparent !important;
|
|
932
|
+
border-color: transparent !important;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.remarq-popup-utility-btn:hover {
|
|
936
|
+
background: var(--remarq-bg-secondary) !important;
|
|
937
|
+
color: var(--remarq-text) !important;
|
|
938
|
+
}
|
|
939
|
+
|
|
800
940
|
.remarq-detached-panel {
|
|
801
941
|
position: fixed;
|
|
802
942
|
z-index: 2147483646;
|
|
@@ -1055,6 +1195,51 @@ var CSS = `
|
|
|
1055
1195
|
color: var(--remarq-text-secondary);
|
|
1056
1196
|
margin-top: 4px;
|
|
1057
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
.remarq-popup-history {
|
|
1200
|
+
margin-top: 8px;
|
|
1201
|
+
font-size: 12px;
|
|
1202
|
+
color: var(--remarq-text-secondary);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.remarq-popup-history summary {
|
|
1206
|
+
cursor: pointer;
|
|
1207
|
+
padding: 4px 0;
|
|
1208
|
+
user-select: none;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.remarq-popup-history-list {
|
|
1212
|
+
list-style: none;
|
|
1213
|
+
margin: 4px 0 0 0;
|
|
1214
|
+
padding: 0;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.remarq-popup-history-list li {
|
|
1218
|
+
padding: 2px 0;
|
|
1219
|
+
font-size: 11px;
|
|
1220
|
+
line-height: 1.4;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.remarq-popup-reason {
|
|
1224
|
+
width: 100%;
|
|
1225
|
+
min-height: 50px;
|
|
1226
|
+
margin-bottom: 8px;
|
|
1227
|
+
padding: 6px;
|
|
1228
|
+
border: 1px solid var(--remarq-border);
|
|
1229
|
+
border-radius: 4px;
|
|
1230
|
+
background: var(--remarq-bg-secondary);
|
|
1231
|
+
color: var(--remarq-text);
|
|
1232
|
+
font-family: inherit;
|
|
1233
|
+
font-size: 12px;
|
|
1234
|
+
resize: vertical;
|
|
1235
|
+
box-sizing: border-box;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.remarq-popup-reason-row {
|
|
1239
|
+
display: flex;
|
|
1240
|
+
justify-content: flex-end;
|
|
1241
|
+
gap: 8px;
|
|
1242
|
+
}
|
|
1058
1243
|
`;
|
|
1059
1244
|
function injectStyles() {
|
|
1060
1245
|
if (document.querySelector(`style[${STYLES_ID}]`)) return;
|
|
@@ -1162,8 +1347,19 @@ var Toolbar = class {
|
|
|
1162
1347
|
this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
|
|
1163
1348
|
this.badgeEl = document.createElement("span");
|
|
1164
1349
|
this.badgeEl.className = "remarq-badge";
|
|
1350
|
+
this.badgeEl.title = "Needs attention";
|
|
1165
1351
|
this.badgeEl.style.display = "none";
|
|
1166
1352
|
this.inspectBtn.appendChild(this.badgeEl);
|
|
1353
|
+
this.verificationBadgeEl = document.createElement("span");
|
|
1354
|
+
this.verificationBadgeEl.className = "remarq-badge remarq-toolbar-badge--verification";
|
|
1355
|
+
this.verificationBadgeEl.title = "Pending your verification";
|
|
1356
|
+
this.verificationBadgeEl.style.display = "none";
|
|
1357
|
+
this.verificationBadgeEl.addEventListener("click", (e) => {
|
|
1358
|
+
var _a3;
|
|
1359
|
+
e.stopPropagation();
|
|
1360
|
+
(_a3 = callbacks.onVerificationBadgeClick) == null ? void 0 : _a3.call(callbacks);
|
|
1361
|
+
});
|
|
1362
|
+
this.inspectBtn.appendChild(this.verificationBadgeEl);
|
|
1167
1363
|
this.spacingBtn = this.createButton("spacing", ICONS.spacing, () => callbacks.onSpacingToggle());
|
|
1168
1364
|
this.spacingBtn.disabled = true;
|
|
1169
1365
|
const copyBtn = this.createButton("copy", ICONS.copy, () => callbacks.onCopy());
|
|
@@ -1210,6 +1406,10 @@ var Toolbar = class {
|
|
|
1210
1406
|
this.badgeEl.textContent = String(count);
|
|
1211
1407
|
this.badgeEl.style.display = count > 0 ? "flex" : "none";
|
|
1212
1408
|
}
|
|
1409
|
+
setVerificationBadgeCount(count) {
|
|
1410
|
+
this.verificationBadgeEl.textContent = String(count);
|
|
1411
|
+
this.verificationBadgeEl.style.display = count > 0 ? "flex" : "none";
|
|
1412
|
+
}
|
|
1213
1413
|
getFileInput() {
|
|
1214
1414
|
return this.fileInput;
|
|
1215
1415
|
}
|
|
@@ -1693,6 +1893,47 @@ var SpacingOverlay = class {
|
|
|
1693
1893
|
};
|
|
1694
1894
|
|
|
1695
1895
|
// src/ui/popup.ts
|
|
1896
|
+
var STATUS_LABEL = {
|
|
1897
|
+
pending: "Pending",
|
|
1898
|
+
in_progress: "In progress",
|
|
1899
|
+
fixed_unverified: "Fix claimed",
|
|
1900
|
+
verified: "Verified",
|
|
1901
|
+
dismissed: "Dismissed"
|
|
1902
|
+
};
|
|
1903
|
+
var EVENT_LABEL = {
|
|
1904
|
+
created: "Created",
|
|
1905
|
+
acknowledged: "In progress",
|
|
1906
|
+
fix_claimed: "Fix claimed",
|
|
1907
|
+
verified: "Verified",
|
|
1908
|
+
rejected: "Rejected",
|
|
1909
|
+
dismissed: "Dismissed",
|
|
1910
|
+
reopened: "Reopened",
|
|
1911
|
+
migrated: "Migrated"
|
|
1912
|
+
};
|
|
1913
|
+
function actionsForStatus(status) {
|
|
1914
|
+
switch (status) {
|
|
1915
|
+
case "pending":
|
|
1916
|
+
return [
|
|
1917
|
+
{ label: "Acknowledge", action: "acknowledge", primary: true },
|
|
1918
|
+
{ label: "Dismiss", action: "dismiss", needsReason: true }
|
|
1919
|
+
];
|
|
1920
|
+
case "in_progress":
|
|
1921
|
+
return [
|
|
1922
|
+
{ label: "Mark verified", action: "verify", primary: true },
|
|
1923
|
+
{ label: "Dismiss", action: "dismiss", needsReason: true }
|
|
1924
|
+
];
|
|
1925
|
+
case "fixed_unverified":
|
|
1926
|
+
return [
|
|
1927
|
+
{ label: "Verify", action: "verify", primary: true },
|
|
1928
|
+
{ label: "Reject", action: "reject", needsReason: true },
|
|
1929
|
+
{ label: "Dismiss", action: "dismiss", needsReason: true }
|
|
1930
|
+
];
|
|
1931
|
+
case "verified":
|
|
1932
|
+
return [{ label: "Reopen", action: "reopen" }];
|
|
1933
|
+
case "dismissed":
|
|
1934
|
+
return [{ label: "Reopen", action: "reopen" }];
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1696
1937
|
var POPUP_WIDTH = 300;
|
|
1697
1938
|
var POPUP_MARGIN = 8;
|
|
1698
1939
|
var Popup = class {
|
|
@@ -1701,6 +1942,7 @@ var Popup = class {
|
|
|
1701
1942
|
this.popupEl = null;
|
|
1702
1943
|
this.keyHandler = null;
|
|
1703
1944
|
this.outsideClickHandler = null;
|
|
1945
|
+
this.pendingEditFlush = null;
|
|
1704
1946
|
}
|
|
1705
1947
|
show(info, position, onSubmit, onCancel) {
|
|
1706
1948
|
this.hide();
|
|
@@ -1785,7 +2027,7 @@ var Popup = class {
|
|
|
1785
2027
|
popup2.className = "remarq-popup";
|
|
1786
2028
|
const header = document.createElement("div");
|
|
1787
2029
|
header.className = "remarq-popup-header";
|
|
1788
|
-
header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${info.status}]`;
|
|
2030
|
+
header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${STATUS_LABEL[info.status]}]`;
|
|
1789
2031
|
const body = document.createElement("div");
|
|
1790
2032
|
body.className = "remarq-popup-body";
|
|
1791
2033
|
const makeCommentEl = () => {
|
|
@@ -1797,38 +2039,10 @@ var Popup = class {
|
|
|
1797
2039
|
return el;
|
|
1798
2040
|
};
|
|
1799
2041
|
body.appendChild(makeCommentEl());
|
|
2042
|
+
body.appendChild(this.buildLifecycleViewer(info.lifecycle));
|
|
1800
2043
|
const actions = document.createElement("div");
|
|
1801
2044
|
actions.className = "remarq-popup-actions";
|
|
1802
|
-
|
|
1803
|
-
const resolveBtn = document.createElement("button");
|
|
1804
|
-
resolveBtn.className = "remarq-primary";
|
|
1805
|
-
resolveBtn.textContent = "Resolve";
|
|
1806
|
-
resolveBtn.addEventListener("click", () => {
|
|
1807
|
-
this.hide();
|
|
1808
|
-
callbacks.onResolve();
|
|
1809
|
-
});
|
|
1810
|
-
actions.appendChild(resolveBtn);
|
|
1811
|
-
}
|
|
1812
|
-
const copyBtn = document.createElement("button");
|
|
1813
|
-
copyBtn.textContent = "Copy";
|
|
1814
|
-
copyBtn.addEventListener("click", () => {
|
|
1815
|
-
callbacks.onCopy();
|
|
1816
|
-
});
|
|
1817
|
-
actions.appendChild(copyBtn);
|
|
1818
|
-
const deleteBtn = document.createElement("button");
|
|
1819
|
-
deleteBtn.textContent = "Delete";
|
|
1820
|
-
deleteBtn.addEventListener("click", () => {
|
|
1821
|
-
this.hide();
|
|
1822
|
-
callbacks.onDelete();
|
|
1823
|
-
});
|
|
1824
|
-
actions.appendChild(deleteBtn);
|
|
1825
|
-
const closeBtn = document.createElement("button");
|
|
1826
|
-
closeBtn.textContent = "Close";
|
|
1827
|
-
closeBtn.addEventListener("click", () => {
|
|
1828
|
-
this.hide();
|
|
1829
|
-
callbacks.onClose();
|
|
1830
|
-
});
|
|
1831
|
-
actions.appendChild(closeBtn);
|
|
2045
|
+
this.renderActionButtons(actions, info, callbacks);
|
|
1832
2046
|
popup2.appendChild(header);
|
|
1833
2047
|
popup2.appendChild(body);
|
|
1834
2048
|
popup2.appendChild(actions);
|
|
@@ -1855,7 +2069,103 @@ var Popup = class {
|
|
|
1855
2069
|
document.addEventListener("mousedown", this.outsideClickHandler);
|
|
1856
2070
|
}, 0);
|
|
1857
2071
|
}
|
|
2072
|
+
buildLifecycleViewer(lifecycle) {
|
|
2073
|
+
var _a3, _b;
|
|
2074
|
+
const details = document.createElement("details");
|
|
2075
|
+
details.className = "remarq-popup-history";
|
|
2076
|
+
const summary = document.createElement("summary");
|
|
2077
|
+
summary.textContent = `History (${lifecycle.length})`;
|
|
2078
|
+
details.appendChild(summary);
|
|
2079
|
+
const list = document.createElement("ul");
|
|
2080
|
+
list.className = "remarq-popup-history-list";
|
|
2081
|
+
for (const ev of lifecycle) {
|
|
2082
|
+
const li = document.createElement("li");
|
|
2083
|
+
const when = new Date(ev.timestamp).toLocaleString();
|
|
2084
|
+
const who = (_a3 = ev.actor) != null ? _a3 : "system";
|
|
2085
|
+
const what = (_b = EVENT_LABEL[ev.type]) != null ? _b : ev.type;
|
|
2086
|
+
let text = `${when} \xB7 ${who} \xB7 ${what}`;
|
|
2087
|
+
if (ev.reason) text += ` \u2014 ${ev.reason}`;
|
|
2088
|
+
li.textContent = text;
|
|
2089
|
+
list.appendChild(li);
|
|
2090
|
+
}
|
|
2091
|
+
details.appendChild(list);
|
|
2092
|
+
return details;
|
|
2093
|
+
}
|
|
2094
|
+
renderActionButtons(container, info, callbacks) {
|
|
2095
|
+
container.replaceChildren();
|
|
2096
|
+
const transitions = document.createElement("div");
|
|
2097
|
+
transitions.className = "remarq-popup-actions-row remarq-popup-actions-row--transitions";
|
|
2098
|
+
for (const def of actionsForStatus(info.status)) {
|
|
2099
|
+
const btn = document.createElement("button");
|
|
2100
|
+
btn.textContent = def.label;
|
|
2101
|
+
if (def.primary) btn.className = "remarq-primary";
|
|
2102
|
+
btn.addEventListener("click", () => {
|
|
2103
|
+
if (def.needsReason) {
|
|
2104
|
+
this.showReasonInput(container, info, callbacks, def);
|
|
2105
|
+
} else {
|
|
2106
|
+
this.hide();
|
|
2107
|
+
callbacks.onTransition(def.action);
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
transitions.appendChild(btn);
|
|
2111
|
+
}
|
|
2112
|
+
container.appendChild(transitions);
|
|
2113
|
+
const utility = document.createElement("div");
|
|
2114
|
+
utility.className = "remarq-popup-actions-row remarq-popup-actions-row--utility";
|
|
2115
|
+
const copyBtn = document.createElement("button");
|
|
2116
|
+
copyBtn.className = "remarq-popup-utility-btn";
|
|
2117
|
+
copyBtn.textContent = "Copy";
|
|
2118
|
+
copyBtn.addEventListener("click", () => callbacks.onCopy());
|
|
2119
|
+
utility.appendChild(copyBtn);
|
|
2120
|
+
const deleteBtn = document.createElement("button");
|
|
2121
|
+
deleteBtn.className = "remarq-popup-utility-btn";
|
|
2122
|
+
deleteBtn.textContent = "Delete";
|
|
2123
|
+
deleteBtn.addEventListener("click", () => {
|
|
2124
|
+
this.hide();
|
|
2125
|
+
callbacks.onDelete();
|
|
2126
|
+
});
|
|
2127
|
+
utility.appendChild(deleteBtn);
|
|
2128
|
+
const closeBtn = document.createElement("button");
|
|
2129
|
+
closeBtn.className = "remarq-popup-utility-btn";
|
|
2130
|
+
closeBtn.textContent = "Close";
|
|
2131
|
+
closeBtn.addEventListener("click", () => {
|
|
2132
|
+
this.hide();
|
|
2133
|
+
callbacks.onClose();
|
|
2134
|
+
});
|
|
2135
|
+
utility.appendChild(closeBtn);
|
|
2136
|
+
container.appendChild(utility);
|
|
2137
|
+
}
|
|
2138
|
+
showReasonInput(container, info, callbacks, def) {
|
|
2139
|
+
container.replaceChildren();
|
|
2140
|
+
const textarea = document.createElement("textarea");
|
|
2141
|
+
textarea.placeholder = `Reason for ${def.label.toLowerCase()} (optional)\u2026`;
|
|
2142
|
+
textarea.className = "remarq-popup-reason";
|
|
2143
|
+
container.appendChild(textarea);
|
|
2144
|
+
const row = document.createElement("div");
|
|
2145
|
+
row.className = "remarq-popup-reason-row";
|
|
2146
|
+
const cancel = document.createElement("button");
|
|
2147
|
+
cancel.textContent = "Cancel";
|
|
2148
|
+
cancel.addEventListener("click", () => {
|
|
2149
|
+
this.renderActionButtons(container, info, callbacks);
|
|
2150
|
+
});
|
|
2151
|
+
const submit = document.createElement("button");
|
|
2152
|
+
submit.className = "remarq-primary";
|
|
2153
|
+
submit.textContent = "Submit";
|
|
2154
|
+
submit.addEventListener("click", () => {
|
|
2155
|
+
const reason = textarea.value.trim() || void 0;
|
|
2156
|
+
this.hide();
|
|
2157
|
+
callbacks.onTransition(def.action, reason);
|
|
2158
|
+
});
|
|
2159
|
+
row.appendChild(cancel);
|
|
2160
|
+
row.appendChild(submit);
|
|
2161
|
+
container.appendChild(row);
|
|
2162
|
+
textarea.focus();
|
|
2163
|
+
}
|
|
1858
2164
|
hide() {
|
|
2165
|
+
if (this.pendingEditFlush) {
|
|
2166
|
+
this.pendingEditFlush();
|
|
2167
|
+
this.pendingEditFlush = null;
|
|
2168
|
+
}
|
|
1859
2169
|
if (this.popupEl) {
|
|
1860
2170
|
this.popupEl.remove();
|
|
1861
2171
|
this.popupEl = null;
|
|
@@ -1890,12 +2200,8 @@ var Popup = class {
|
|
|
1890
2200
|
commentEl.replaceWith(textarea);
|
|
1891
2201
|
textarea.focus();
|
|
1892
2202
|
textarea.selectionStart = textarea.value.length;
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
if (newComment && newComment !== info.comment) {
|
|
1896
|
-
info.comment = newComment;
|
|
1897
|
-
callbacks.onEdit(newComment);
|
|
1898
|
-
}
|
|
2203
|
+
const restoreView = () => {
|
|
2204
|
+
if (!textarea.isConnected) return;
|
|
1899
2205
|
const restored = document.createElement("div");
|
|
1900
2206
|
restored.textContent = info.comment;
|
|
1901
2207
|
restored.style.cursor = "pointer";
|
|
@@ -1903,26 +2209,33 @@ var Popup = class {
|
|
|
1903
2209
|
restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
|
|
1904
2210
|
textarea.replaceWith(restored);
|
|
1905
2211
|
};
|
|
2212
|
+
const commitEdit = () => {
|
|
2213
|
+
this.pendingEditFlush = null;
|
|
2214
|
+
const newComment = textarea.value.trim();
|
|
2215
|
+
if (newComment && newComment !== info.comment) {
|
|
2216
|
+
info.comment = newComment;
|
|
2217
|
+
callbacks.onEdit(newComment);
|
|
2218
|
+
}
|
|
2219
|
+
restoreView();
|
|
2220
|
+
};
|
|
2221
|
+
const cancelEdit = () => {
|
|
2222
|
+
this.pendingEditFlush = null;
|
|
2223
|
+
restoreView();
|
|
2224
|
+
};
|
|
2225
|
+
this.pendingEditFlush = commitEdit;
|
|
1906
2226
|
textarea.addEventListener("keydown", (e) => {
|
|
1907
2227
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
1908
2228
|
e.preventDefault();
|
|
1909
|
-
|
|
2229
|
+
commitEdit();
|
|
1910
2230
|
}
|
|
1911
2231
|
if (e.key === "Escape") {
|
|
1912
2232
|
e.stopPropagation();
|
|
1913
|
-
|
|
1914
|
-
restored.textContent = info.comment;
|
|
1915
|
-
restored.style.cursor = "pointer";
|
|
1916
|
-
restored.title = "Click to edit";
|
|
1917
|
-
restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
|
|
1918
|
-
textarea.replaceWith(restored);
|
|
2233
|
+
cancelEdit();
|
|
1919
2234
|
}
|
|
1920
2235
|
});
|
|
1921
2236
|
textarea.addEventListener("blur", () => {
|
|
1922
2237
|
setTimeout(() => {
|
|
1923
|
-
if (textarea.isConnected)
|
|
1924
|
-
saveEdit();
|
|
1925
|
-
}
|
|
2238
|
+
if (textarea.isConnected) commitEdit();
|
|
1926
2239
|
}, 50);
|
|
1927
2240
|
});
|
|
1928
2241
|
}
|
|
@@ -1950,6 +2263,16 @@ var Popup = class {
|
|
|
1950
2263
|
};
|
|
1951
2264
|
|
|
1952
2265
|
// src/ui/markers.ts
|
|
2266
|
+
var STATUS_COLOR = {
|
|
2267
|
+
pending: "var(--remarq-status-pending)",
|
|
2268
|
+
in_progress: "var(--remarq-status-in-progress)",
|
|
2269
|
+
fixed_unverified: "var(--remarq-status-fixed-unverified)",
|
|
2270
|
+
verified: "var(--remarq-status-verified)",
|
|
2271
|
+
dismissed: "var(--remarq-status-dismissed)"
|
|
2272
|
+
};
|
|
2273
|
+
function statusClass(status) {
|
|
2274
|
+
return `remarq-marker--${status.replace("_", "-")}`;
|
|
2275
|
+
}
|
|
1953
2276
|
var MarkerManager = class {
|
|
1954
2277
|
constructor(container, onClick) {
|
|
1955
2278
|
this.container = container;
|
|
@@ -1962,7 +2285,7 @@ var MarkerManager = class {
|
|
|
1962
2285
|
addMarker(annotation, target) {
|
|
1963
2286
|
this.counter++;
|
|
1964
2287
|
const markerEl = document.createElement("div");
|
|
1965
|
-
markerEl.className =
|
|
2288
|
+
markerEl.className = `remarq-marker ${statusClass(annotation.status)}`;
|
|
1966
2289
|
markerEl.setAttribute("data-status", annotation.status);
|
|
1967
2290
|
markerEl.setAttribute("data-annotation-id", annotation.id);
|
|
1968
2291
|
markerEl.textContent = String(this.counter);
|
|
@@ -1988,7 +2311,17 @@ var MarkerManager = class {
|
|
|
1988
2311
|
const entry = this.markers.get(id);
|
|
1989
2312
|
if (entry) {
|
|
1990
2313
|
entry.annotation.status = status;
|
|
2314
|
+
entry.markerEl.className = `remarq-marker ${statusClass(status)}`;
|
|
1991
2315
|
entry.markerEl.setAttribute("data-status", status);
|
|
2316
|
+
this.applyOutline(entry.target, status);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
scrollToMarker(id) {
|
|
2320
|
+
const entry = this.markers.get(id);
|
|
2321
|
+
if (!entry) return;
|
|
2322
|
+
try {
|
|
2323
|
+
entry.target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2324
|
+
} catch (e) {
|
|
1992
2325
|
}
|
|
1993
2326
|
}
|
|
1994
2327
|
clear() {
|
|
@@ -2007,7 +2340,7 @@ var MarkerManager = class {
|
|
|
2007
2340
|
this.clear();
|
|
2008
2341
|
}
|
|
2009
2342
|
applyOutline(target, status) {
|
|
2010
|
-
const color = status
|
|
2343
|
+
const color = STATUS_COLOR[status];
|
|
2011
2344
|
target.style.outline = `2px solid ${color}`;
|
|
2012
2345
|
target.style.outlineOffset = "2px";
|
|
2013
2346
|
}
|
|
@@ -2354,8 +2687,18 @@ function refreshMarkers() {
|
|
|
2354
2687
|
markers.addMarker(ann, el);
|
|
2355
2688
|
}
|
|
2356
2689
|
detachedPanel.update(otherBreakpoint, detached);
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2690
|
+
const needsAttention = anns.filter(
|
|
2691
|
+
(a) => a.status === "pending" || a.status === "in_progress"
|
|
2692
|
+
).length;
|
|
2693
|
+
const needsVerification = anns.filter((a) => a.status === "fixed_unverified").length;
|
|
2694
|
+
toolbar.setBadgeCount(needsAttention);
|
|
2695
|
+
toolbar.setVerificationBadgeCount(needsVerification);
|
|
2696
|
+
}
|
|
2697
|
+
function jumpToFirstUnverified() {
|
|
2698
|
+
if (!storage || !markers) return;
|
|
2699
|
+
const ann = storage.getByRoute(currentRoute()).find((a) => a.status === "fixed_unverified");
|
|
2700
|
+
if (!ann) return;
|
|
2701
|
+
markers.scrollToMarker(ann.id);
|
|
2359
2702
|
}
|
|
2360
2703
|
function scheduleRefresh() {
|
|
2361
2704
|
if (refreshScheduled) return;
|
|
@@ -2388,6 +2731,7 @@ function handleInspectClick(e) {
|
|
|
2388
2731
|
classFilter: options.classFilter,
|
|
2389
2732
|
dataAttribute: options.dataAttribute
|
|
2390
2733
|
});
|
|
2734
|
+
const now = Date.now();
|
|
2391
2735
|
const ann = {
|
|
2392
2736
|
id: generateId(),
|
|
2393
2737
|
comment,
|
|
@@ -2395,8 +2739,9 @@ function handleInspectClick(e) {
|
|
|
2395
2739
|
route: currentRoute(),
|
|
2396
2740
|
viewport: `${window.innerWidth}x${window.innerHeight}`,
|
|
2397
2741
|
viewportBucket: toBucket(window.innerWidth),
|
|
2398
|
-
timestamp:
|
|
2399
|
-
status: "pending"
|
|
2742
|
+
timestamp: now,
|
|
2743
|
+
status: "pending",
|
|
2744
|
+
lifecycle: [{ type: "created", actor: "designer", timestamp: now }]
|
|
2400
2745
|
};
|
|
2401
2746
|
cacheElement(ann.id, target);
|
|
2402
2747
|
storage.add(ann);
|
|
@@ -2488,7 +2833,8 @@ function handleMarkerClick(annotationId) {
|
|
|
2488
2833
|
tag: ann.fingerprint.tagName,
|
|
2489
2834
|
text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
|
|
2490
2835
|
comment: ann.comment,
|
|
2491
|
-
status: ann.status
|
|
2836
|
+
status: ann.status,
|
|
2837
|
+
lifecycle: ann.lifecycle
|
|
2492
2838
|
},
|
|
2493
2839
|
{
|
|
2494
2840
|
top: window.scrollY + rect.bottom + 8,
|
|
@@ -2496,9 +2842,8 @@ function handleMarkerClick(annotationId) {
|
|
|
2496
2842
|
anchorBottom: window.scrollY + rect.top - 8
|
|
2497
2843
|
},
|
|
2498
2844
|
{
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
refreshMarkers();
|
|
2845
|
+
onTransition: (action, reason) => {
|
|
2846
|
+
applyTransition(ann.id, action, reason ? { reason } : void 0);
|
|
2502
2847
|
},
|
|
2503
2848
|
onDelete: () => {
|
|
2504
2849
|
elementCache.delete(ann.id);
|
|
@@ -2512,12 +2857,14 @@ function handleMarkerClick(annotationId) {
|
|
|
2512
2857
|
refreshMarkers();
|
|
2513
2858
|
},
|
|
2514
2859
|
onCopy: () => {
|
|
2515
|
-
|
|
2860
|
+
var _a4;
|
|
2861
|
+
const fresh = (_a4 = storage.getById(ann.id)) != null ? _a4 : ann;
|
|
2862
|
+
const fp = fresh.fingerprint;
|
|
2516
2863
|
const lines = [
|
|
2517
|
-
`[${
|
|
2864
|
+
`[${fresh.status}] "${fresh.comment}"`,
|
|
2518
2865
|
`Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
|
|
2519
|
-
`Route: ${
|
|
2520
|
-
`Viewport: ${
|
|
2866
|
+
`Route: ${fresh.route}`,
|
|
2867
|
+
`Viewport: ${fresh.viewportBucket}px`
|
|
2521
2868
|
];
|
|
2522
2869
|
if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
|
|
2523
2870
|
navigator.clipboard.writeText(lines.join("\n")).then(() => {
|
|
@@ -2652,6 +2999,16 @@ function copyAgentToClipboard() {
|
|
|
2652
2999
|
console.warn("[web-remarq] Clipboard write failed");
|
|
2653
3000
|
});
|
|
2654
3001
|
}
|
|
3002
|
+
function applyTransition(id, action, opts = {}) {
|
|
3003
|
+
if (!storage) return;
|
|
3004
|
+
const ann = storage.getById(id);
|
|
3005
|
+
if (!ann) return;
|
|
3006
|
+
const { status, event } = transition(ann, action, opts);
|
|
3007
|
+
const lifecycle = [...ann.lifecycle, event];
|
|
3008
|
+
storage.update(id, { status, lifecycle });
|
|
3009
|
+
markers == null ? void 0 : markers.updateStatus(id, status);
|
|
3010
|
+
refreshMarkers();
|
|
3011
|
+
}
|
|
2655
3012
|
function setupMutationObserver() {
|
|
2656
3013
|
mutationObserver = new MutationObserver((mutations) => {
|
|
2657
3014
|
let hasExternalMutation = false;
|
|
@@ -2713,7 +3070,8 @@ var WebRemarq = {
|
|
|
2713
3070
|
showToast(themeManager.container, "All annotations cleared");
|
|
2714
3071
|
},
|
|
2715
3072
|
onThemeToggle: () => themeManager.toggle(),
|
|
2716
|
-
onHelp: () => showShortcutsModal(themeManager.container)
|
|
3073
|
+
onHelp: () => showShortcutsModal(themeManager.container),
|
|
3074
|
+
onVerificationBadgeClick: jumpToFirstUnverified
|
|
2717
3075
|
}, position);
|
|
2718
3076
|
routeObserver = new RouteObserver();
|
|
2719
3077
|
unsubRoute = routeObserver.onChange(() => refreshMarkers());
|
|
@@ -2807,6 +3165,28 @@ var WebRemarq = {
|
|
|
2807
3165
|
elementCache.clear();
|
|
2808
3166
|
storage == null ? void 0 : storage.clearAll();
|
|
2809
3167
|
if (initialized) refreshMarkers();
|
|
3168
|
+
},
|
|
3169
|
+
acknowledge(id, opts) {
|
|
3170
|
+
applyTransition(id, "acknowledge", opts);
|
|
3171
|
+
},
|
|
3172
|
+
claimFix(id, opts) {
|
|
3173
|
+
applyTransition(id, "claimFix", opts);
|
|
3174
|
+
},
|
|
3175
|
+
verify(id, opts) {
|
|
3176
|
+
applyTransition(id, "verify", opts);
|
|
3177
|
+
},
|
|
3178
|
+
reject(id, opts) {
|
|
3179
|
+
applyTransition(id, "reject", opts);
|
|
3180
|
+
},
|
|
3181
|
+
dismiss(id, opts) {
|
|
3182
|
+
applyTransition(id, "dismiss", opts);
|
|
3183
|
+
},
|
|
3184
|
+
reopen(id, opts) {
|
|
3185
|
+
applyTransition(id, "reopen", opts);
|
|
3186
|
+
},
|
|
3187
|
+
/** @deprecated Use verify() instead. */
|
|
3188
|
+
markResolved(id) {
|
|
3189
|
+
applyTransition(id, "verify");
|
|
2810
3190
|
}
|
|
2811
3191
|
};
|
|
2812
3192
|
export {
|