@ourroadmaps/web-sdk 1.4.0 → 1.5.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.
@@ -15,6 +15,17 @@ async function validateToken(token) {
15
15
  const body = await res.json();
16
16
  return body.data;
17
17
  }
18
+ async function identify(token, name, email) {
19
+ const body = {};
20
+ if (name) body.name = name;
21
+ if (email) body.email = email;
22
+ const res = await fetch(`${API_URL}/v1/prototype-review/${token}/identify`, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(body)
26
+ });
27
+ if (!res.ok) throw new ReviewError("error", "Failed to identify");
28
+ }
18
29
  async function submitComment(token, comment) {
19
30
  const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {
20
31
  method: "POST",
@@ -225,6 +236,242 @@ var REVIEW_STYLES = `
225
236
  box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);
226
237
  }
227
238
 
239
+ /* \u2500\u2500\u2500 Mode Toggle \u2500\u2500\u2500 */
240
+ .review-mode-toggle {
241
+ display: flex;
242
+ border-radius: 6px;
243
+ overflow: hidden;
244
+ background: rgba(255, 255, 255, 0.1);
245
+ }
246
+
247
+ .review-mode-btn {
248
+ padding: 5px 10px;
249
+ font-family: inherit;
250
+ font-size: 12px;
251
+ font-weight: 500;
252
+ border: none;
253
+ cursor: pointer;
254
+ color: rgba(255, 255, 255, 0.6);
255
+ background: transparent;
256
+ transition: background 0.15s ease, color 0.15s ease;
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 4px;
260
+ white-space: nowrap;
261
+ }
262
+
263
+ .review-mode-btn:hover {
264
+ color: rgba(255, 255, 255, 0.85);
265
+ background: rgba(255, 255, 255, 0.08);
266
+ }
267
+
268
+ .review-mode-btn--active {
269
+ background: #7c3aed;
270
+ color: #fff;
271
+ }
272
+
273
+ .review-mode-btn--active:hover {
274
+ background: #6d28d9;
275
+ color: #fff;
276
+ }
277
+
278
+ /* \u2500\u2500\u2500 Comments Panel \u2500\u2500\u2500 */
279
+ .review-panel {
280
+ position: fixed;
281
+ top: 0;
282
+ right: 0;
283
+ bottom: 0;
284
+ width: 320px;
285
+ background: #111;
286
+ color: #fff;
287
+ font-family: system-ui, -apple-system, sans-serif;
288
+ z-index: 10001;
289
+ display: flex;
290
+ flex-direction: column;
291
+ border-left: 1px solid rgba(255, 255, 255, 0.1);
292
+ transform: translateX(100%);
293
+ transition: transform 0.2s ease;
294
+ }
295
+
296
+ .review-panel--open {
297
+ transform: translateX(0);
298
+ }
299
+
300
+ .review-panel-header {
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: space-between;
304
+ padding: 14px 16px;
305
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
306
+ flex-shrink: 0;
307
+ }
308
+
309
+ .review-panel-header h3 {
310
+ margin: 0;
311
+ font-size: 14px;
312
+ font-weight: 600;
313
+ color: rgba(255, 255, 255, 0.9);
314
+ }
315
+
316
+ .review-panel-close {
317
+ background: none;
318
+ border: none;
319
+ color: rgba(255, 255, 255, 0.5);
320
+ cursor: pointer;
321
+ padding: 4px;
322
+ border-radius: 4px;
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ transition: color 0.15s ease, background 0.15s ease;
327
+ }
328
+
329
+ .review-panel-close:hover {
330
+ color: rgba(255, 255, 255, 0.9);
331
+ background: rgba(255, 255, 255, 0.1);
332
+ }
333
+
334
+ .review-panel-body {
335
+ flex: 1;
336
+ overflow-y: auto;
337
+ padding: 12px 16px;
338
+ }
339
+
340
+ .review-panel-section {
341
+ margin-bottom: 16px;
342
+ }
343
+
344
+ .review-panel-section-header {
345
+ font-size: 11px;
346
+ font-weight: 600;
347
+ text-transform: uppercase;
348
+ letter-spacing: 0.05em;
349
+ color: rgba(255, 255, 255, 0.4);
350
+ margin-bottom: 8px;
351
+ padding-bottom: 6px;
352
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
353
+ }
354
+
355
+ .review-panel-section-toggle {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 6px;
359
+ font-size: 11px;
360
+ font-weight: 600;
361
+ text-transform: uppercase;
362
+ letter-spacing: 0.05em;
363
+ color: rgba(255, 255, 255, 0.4);
364
+ margin-bottom: 8px;
365
+ padding-bottom: 6px;
366
+ border: none;
367
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
368
+ background: none;
369
+ cursor: pointer;
370
+ width: 100%;
371
+ text-align: left;
372
+ transition: color 0.15s ease;
373
+ }
374
+
375
+ .review-panel-section-toggle:hover {
376
+ color: rgba(255, 255, 255, 0.6);
377
+ }
378
+
379
+ .review-panel-section-toggle svg {
380
+ transition: transform 0.15s ease;
381
+ }
382
+
383
+ .review-panel-section-toggle--expanded svg {
384
+ transform: rotate(90deg);
385
+ }
386
+
387
+ .review-panel-page-label {
388
+ font-size: 11px;
389
+ color: rgba(255, 255, 255, 0.3);
390
+ margin: 10px 0 6px;
391
+ font-family: monospace;
392
+ }
393
+
394
+ .review-panel-page-label:first-child {
395
+ margin-top: 0;
396
+ }
397
+
398
+ /* \u2500\u2500\u2500 Comment Item \u2500\u2500\u2500 */
399
+ .review-panel-comment {
400
+ background: rgba(255, 255, 255, 0.05);
401
+ border-radius: 6px;
402
+ padding: 10px 12px;
403
+ margin-bottom: 6px;
404
+ cursor: pointer;
405
+ transition: background 0.15s ease;
406
+ }
407
+
408
+ .review-panel-comment:hover {
409
+ background: rgba(255, 255, 255, 0.08);
410
+ }
411
+
412
+ .review-panel-comment-header {
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 8px;
416
+ margin-bottom: 4px;
417
+ }
418
+
419
+ .review-panel-pin-badge {
420
+ width: 18px;
421
+ height: 18px;
422
+ border-radius: 50%;
423
+ background: #7c3aed;
424
+ color: #fff;
425
+ font-size: 10px;
426
+ font-weight: 600;
427
+ display: flex;
428
+ align-items: center;
429
+ justify-content: center;
430
+ flex-shrink: 0;
431
+ }
432
+
433
+ .review-panel-general-badge {
434
+ width: 18px;
435
+ height: 18px;
436
+ display: flex;
437
+ align-items: center;
438
+ justify-content: center;
439
+ flex-shrink: 0;
440
+ color: rgba(255, 255, 255, 0.5);
441
+ }
442
+
443
+ .review-panel-comment-author {
444
+ font-size: 12px;
445
+ font-weight: 600;
446
+ color: rgba(255, 255, 255, 0.8);
447
+ }
448
+
449
+ .review-panel-comment-text {
450
+ font-size: 13px;
451
+ color: rgba(255, 255, 255, 0.6);
452
+ line-height: 1.4;
453
+ margin-bottom: 4px;
454
+ }
455
+
456
+ .review-panel-comment-time {
457
+ font-size: 11px;
458
+ color: rgba(255, 255, 255, 0.3);
459
+ }
460
+
461
+ .review-panel-empty {
462
+ text-align: center;
463
+ padding: 24px 16px;
464
+ color: rgba(255, 255, 255, 0.3);
465
+ font-size: 13px;
466
+ }
467
+
468
+ .review-panel-loading {
469
+ text-align: center;
470
+ padding: 24px 16px;
471
+ color: rgba(255, 255, 255, 0.4);
472
+ font-size: 13px;
473
+ }
474
+
228
475
  /* \u2500\u2500\u2500 Comment Card \u2500\u2500\u2500 */
229
476
  .review-comment-card {
230
477
  position: fixed;
@@ -405,6 +652,81 @@ var REVIEW_STYLES = `
405
652
  backdrop-filter: blur(8px);
406
653
  }
407
654
 
655
+ /* \u2500\u2500\u2500 Name Entry Overlay \u2500\u2500\u2500 */
656
+ .review-name-overlay {
657
+ position: fixed;
658
+ inset: 0;
659
+ background: rgba(0, 0, 0, 0.4);
660
+ display: flex;
661
+ align-items: center;
662
+ justify-content: center;
663
+ z-index: 10003;
664
+ backdrop-filter: blur(2px);
665
+ }
666
+
667
+ .review-name-card {
668
+ background: #fff;
669
+ border-radius: 12px;
670
+ padding: 28px 32px;
671
+ font-family: system-ui, -apple-system, sans-serif;
672
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
673
+ max-width: 360px;
674
+ width: 90%;
675
+ border: 1px solid rgba(0, 0, 0, 0.06);
676
+ }
677
+
678
+ .review-name-card h3 {
679
+ margin: 0 0 4px;
680
+ font-size: 17px;
681
+ font-weight: 600;
682
+ color: #111;
683
+ }
684
+
685
+ .review-name-card p {
686
+ margin: 0 0 20px;
687
+ font-size: 14px;
688
+ color: #666;
689
+ }
690
+
691
+ .review-name-card label {
692
+ display: block;
693
+ font-size: 13px;
694
+ font-weight: 500;
695
+ color: #374151;
696
+ margin-bottom: 6px;
697
+ }
698
+
699
+ .review-name-card input {
700
+ width: 100%;
701
+ border: 1px solid #d1d5db;
702
+ border-radius: 8px;
703
+ padding: 10px 12px;
704
+ font-family: inherit;
705
+ font-size: 14px;
706
+ color: #1f2937;
707
+ background: #fafafa;
708
+ box-sizing: border-box;
709
+ outline: none;
710
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
711
+ }
712
+
713
+ .review-name-card input:focus {
714
+ border-color: #7c3aed;
715
+ box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);
716
+ background: #fff;
717
+ }
718
+
719
+ .review-name-card input::placeholder {
720
+ color: #9ca3af;
721
+ }
722
+
723
+ .review-name-actions {
724
+ display: flex;
725
+ justify-content: flex-end;
726
+ gap: 8px;
727
+ margin-top: 20px;
728
+ }
729
+
408
730
  /* \u2500\u2500\u2500 Reduced Motion \u2500\u2500\u2500 */
409
731
  @media (prefers-reduced-motion: reduce) {
410
732
  *, *::before, *::after {
@@ -492,6 +814,11 @@ var PinManager = class {
492
814
  document.body.appendChild(this.container);
493
815
  }
494
816
  addPin(pinNumber, x, y, isMine, pageUrl) {
817
+ const existing = this.pins.get(pinNumber);
818
+ if (existing) {
819
+ existing.el.remove();
820
+ this.pins.delete(pinNumber);
821
+ }
495
822
  const pin = document.createElement("div");
496
823
  pin.className = isMine ? "review-pin" : "review-pin review-pin--other";
497
824
  pin.textContent = String(pinNumber);
@@ -681,6 +1008,173 @@ var PageNavigationListener = class {
681
1008
  }
682
1009
  };
683
1010
 
1011
+ // src/review/CommentListPanel.ts
1012
+ var CommentListPanel = class {
1013
+ constructor(token, shadowRoot, pinManager) {
1014
+ this.token = token;
1015
+ this.shadowRoot = shadowRoot;
1016
+ this.pinManager = pinManager;
1017
+ __publicField(this, "panelEl");
1018
+ __publicField(this, "bodyEl", null);
1019
+ __publicField(this, "isOpen", false);
1020
+ __publicField(this, "allPagesExpanded", false);
1021
+ __publicField(this, "comments", []);
1022
+ this.panelEl = document.createElement("div");
1023
+ this.panelEl.className = "review-panel";
1024
+ this.panelEl.innerHTML = `
1025
+ <div class="review-panel-header">
1026
+ <h3>Comments</h3>
1027
+ <button class="review-panel-close" title="Close">
1028
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
1029
+ </button>
1030
+ </div>
1031
+ <div class="review-panel-body"></div>
1032
+ `;
1033
+ this.bodyEl = this.panelEl.querySelector(".review-panel-body");
1034
+ this.panelEl.querySelector(".review-panel-close")?.addEventListener("click", () => this.close());
1035
+ this.shadowRoot.appendChild(this.panelEl);
1036
+ }
1037
+ async toggle() {
1038
+ if (this.isOpen) {
1039
+ this.close();
1040
+ } else {
1041
+ await this.open();
1042
+ }
1043
+ }
1044
+ async open() {
1045
+ this.isOpen = true;
1046
+ this.panelEl.classList.add("review-panel--open");
1047
+ await this.refresh();
1048
+ }
1049
+ close() {
1050
+ this.isOpen = false;
1051
+ this.panelEl.classList.remove("review-panel--open");
1052
+ this.pinManager.clearHighlight();
1053
+ }
1054
+ async refresh() {
1055
+ if (!this.isOpen || !this.bodyEl) return;
1056
+ this.bodyEl.innerHTML = '<div class="review-panel-loading">Loading comments...</div>';
1057
+ try {
1058
+ this.comments = await fetchComments(this.token);
1059
+ this.render();
1060
+ } catch {
1061
+ this.bodyEl.innerHTML = '<div class="review-panel-empty">Failed to load comments</div>';
1062
+ }
1063
+ }
1064
+ render() {
1065
+ if (!this.bodyEl) return;
1066
+ const withText = this.comments.filter((c) => c.commentText);
1067
+ if (withText.length === 0) {
1068
+ this.bodyEl.innerHTML = '<div class="review-panel-empty">No comments yet</div>';
1069
+ return;
1070
+ }
1071
+ const currentPage = getCurrentPageId();
1072
+ const currentPageComments = withText.filter((c) => c.pageUrl === currentPage);
1073
+ const otherComments = withText.filter((c) => c.pageUrl !== currentPage);
1074
+ let html = "";
1075
+ html += '<div class="review-panel-section">';
1076
+ html += `<div class="review-panel-section-header">This Page</div>`;
1077
+ if (currentPageComments.length === 0) {
1078
+ html += '<div class="review-panel-empty" style="padding:8px 0;">No comments on this page</div>';
1079
+ } else {
1080
+ html += this.renderComments(currentPageComments);
1081
+ }
1082
+ html += "</div>";
1083
+ if (otherComments.length > 0) {
1084
+ const groups = this.groupByPage(otherComments);
1085
+ html += '<div class="review-panel-section">';
1086
+ html += `<button class="review-panel-section-toggle ${this.allPagesExpanded ? "review-panel-section-toggle--expanded" : ""}" data-action="toggle-all">
1087
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
1088
+ All Pages (${otherComments.length} comment${otherComments.length !== 1 ? "s" : ""})
1089
+ </button>`;
1090
+ if (this.allPagesExpanded) {
1091
+ html += '<div class="review-panel-all-pages">';
1092
+ for (const group of groups) {
1093
+ html += `<div class="review-panel-page-label">${this.escapeHtml(this.formatPageUrl(group.pageUrl))}</div>`;
1094
+ html += this.renderComments(group.comments);
1095
+ }
1096
+ html += "</div>";
1097
+ }
1098
+ html += "</div>";
1099
+ }
1100
+ this.bodyEl.innerHTML = html;
1101
+ this.attachEventListeners();
1102
+ }
1103
+ renderComments(comments) {
1104
+ return comments.map((c) => {
1105
+ const badge = c.pinNumber != null ? `<span class="review-panel-pin-badge">${c.pinNumber}</span>` : `<span class="review-panel-general-badge"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>`;
1106
+ const author = c.reviewerName || "Anonymous";
1107
+ const text = c.commentText || "";
1108
+ const time = this.formatTime(c.createdAt);
1109
+ const pinAttr = c.pinNumber != null ? `data-pin="${c.pinNumber}"` : "";
1110
+ const pageAttr = c.pageUrl ? `data-page="${this.escapeAttr(c.pageUrl)}"` : "";
1111
+ return `<div class="review-panel-comment" ${pinAttr} ${pageAttr}>
1112
+ <div class="review-panel-comment-header">
1113
+ ${badge}
1114
+ <span class="review-panel-comment-author">${this.escapeHtml(author)}</span>
1115
+ </div>
1116
+ <div class="review-panel-comment-text">${this.escapeHtml(text)}</div>
1117
+ <div class="review-panel-comment-time">${time}</div>
1118
+ </div>`;
1119
+ }).join("");
1120
+ }
1121
+ attachEventListeners() {
1122
+ if (!this.bodyEl) return;
1123
+ this.bodyEl.querySelector('[data-action="toggle-all"]')?.addEventListener("click", () => {
1124
+ this.allPagesExpanded = !this.allPagesExpanded;
1125
+ this.render();
1126
+ });
1127
+ this.bodyEl.querySelectorAll(".review-panel-comment[data-pin]").forEach((el) => {
1128
+ el.addEventListener("click", () => {
1129
+ const pinNumber = Number(el.dataset.pin);
1130
+ if (pinNumber) {
1131
+ this.pinManager.highlightPin(pinNumber);
1132
+ }
1133
+ });
1134
+ });
1135
+ }
1136
+ groupByPage(comments) {
1137
+ const groups = /* @__PURE__ */ new Map();
1138
+ for (const c of comments) {
1139
+ const key = c.pageUrl || "(unknown)";
1140
+ const arr = groups.get(key) || [];
1141
+ arr.push(c);
1142
+ groups.set(key, arr);
1143
+ }
1144
+ return Array.from(groups.entries()).map(([pageUrl, comments2]) => ({ pageUrl, comments: comments2 }));
1145
+ }
1146
+ formatPageUrl(url) {
1147
+ try {
1148
+ const u = new URL(url, "https://placeholder");
1149
+ return u.pathname + u.search + u.hash;
1150
+ } catch {
1151
+ return url || "/";
1152
+ }
1153
+ }
1154
+ formatTime(iso) {
1155
+ const date = new Date(iso);
1156
+ if (Number.isNaN(date.getTime())) return "";
1157
+ const now = Date.now();
1158
+ const diffMs = Math.max(0, now - date.getTime());
1159
+ const diffMin = Math.floor(diffMs / 6e4);
1160
+ if (diffMin < 1) return "just now";
1161
+ if (diffMin < 60) return `${diffMin}m ago`;
1162
+ const diffHr = Math.floor(diffMin / 60);
1163
+ if (diffHr < 24) return `${diffHr}h ago`;
1164
+ const diffDays = Math.floor(diffHr / 24);
1165
+ return `${diffDays}d ago`;
1166
+ }
1167
+ escapeHtml(str) {
1168
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1169
+ }
1170
+ escapeAttr(str) {
1171
+ return str.replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1172
+ }
1173
+ destroy() {
1174
+ this.panelEl.remove();
1175
+ }
1176
+ };
1177
+
684
1178
  // src/review/ReviewMode.ts
685
1179
  var ReviewMode = class {
686
1180
  constructor(token, shadowRoot, initData) {
@@ -695,15 +1189,22 @@ var ReviewMode = class {
695
1189
  __publicField(this, "toolbarEl", null);
696
1190
  __publicField(this, "clickHandler", null);
697
1191
  __publicField(this, "pageListener", null);
1192
+ __publicField(this, "mode", "comment");
1193
+ __publicField(this, "keydownHandler", null);
1194
+ __publicField(this, "commentPanel", null);
698
1195
  this.pinManager = new PinManager();
699
1196
  this.commentCard = new CommentCard(shadowRoot);
700
1197
  this.nextPinNumber = initData.nextPinNumber;
701
1198
  }
702
1199
  async init() {
1200
+ if (!this.initData.reviewer.name) {
1201
+ await this.showNameEntry();
1202
+ }
703
1203
  document.body.style.cursor = "crosshair";
704
1204
  this.pinManager.mount();
705
1205
  this.showPrompt();
706
1206
  this.renderToolbar();
1207
+ this.commentPanel = new CommentListPanel(this.token, this.shadowRoot, this.pinManager);
707
1208
  await this.loadExistingPins();
708
1209
  this.pinManager.filterByPage(getCurrentPageId());
709
1210
  this.pageListener = new PageNavigationListener((pageId) => {
@@ -712,6 +1213,15 @@ var ReviewMode = class {
712
1213
  this.pageListener.start();
713
1214
  this.clickHandler = (e) => this.handleClick(e);
714
1215
  document.addEventListener("click", this.clickHandler, true);
1216
+ this.keydownHandler = (e) => {
1217
+ const tag = e.target?.tagName;
1218
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
1219
+ if (e.target?.isContentEditable) return;
1220
+ if (e.key === "c" && !e.ctrlKey && !e.metaKey && !e.altKey) {
1221
+ this.setMode(this.mode === "comment" ? "navigate" : "comment");
1222
+ }
1223
+ };
1224
+ document.addEventListener("keydown", this.keydownHandler);
715
1225
  }
716
1226
  showPrompt() {
717
1227
  this.promptEl = document.createElement("div");
@@ -729,6 +1239,59 @@ var ReviewMode = class {
729
1239
  this.promptEl = null;
730
1240
  }
731
1241
  }
1242
+ showNameEntry() {
1243
+ return new Promise((resolve) => {
1244
+ const overlay = document.createElement("div");
1245
+ overlay.className = "review-name-overlay";
1246
+ overlay.innerHTML = `
1247
+ <div class="review-name-card">
1248
+ <h3>Welcome to the review</h3>
1249
+ <p>Optionally enter your name so the team knows who left the feedback.</p>
1250
+ <div style="margin-bottom:12px;">
1251
+ <label>Name</label>
1252
+ <input type="text" class="review-name-input" placeholder="Your name (optional)" />
1253
+ </div>
1254
+ <div>
1255
+ <label>Email</label>
1256
+ <input type="email" class="review-email-input" placeholder="Your email (optional)" />
1257
+ </div>
1258
+ <div class="review-name-actions">
1259
+ <button class="review-btn review-btn--cancel review-name-skip">Skip</button>
1260
+ <button class="review-btn review-btn--submit review-name-continue">Continue</button>
1261
+ </div>
1262
+ </div>
1263
+ `;
1264
+ this.shadowRoot.appendChild(overlay);
1265
+ const nameInput = overlay.querySelector(".review-name-input");
1266
+ const emailInput = overlay.querySelector(".review-email-input");
1267
+ const skipBtn = overlay.querySelector(".review-name-skip");
1268
+ const continueBtn = overlay.querySelector(".review-name-continue");
1269
+ const finish = async () => {
1270
+ const name = nameInput.value.trim() || void 0;
1271
+ const email = emailInput.value.trim() || void 0;
1272
+ try {
1273
+ await identify(this.token, name, email);
1274
+ } catch {
1275
+ }
1276
+ overlay.remove();
1277
+ resolve();
1278
+ };
1279
+ skipBtn.addEventListener("click", () => {
1280
+ identify(this.token).catch(() => {
1281
+ });
1282
+ overlay.remove();
1283
+ resolve();
1284
+ });
1285
+ continueBtn.addEventListener("click", finish);
1286
+ nameInput.addEventListener("keydown", (e) => {
1287
+ if (e.key === "Enter") finish();
1288
+ });
1289
+ emailInput.addEventListener("keydown", (e) => {
1290
+ if (e.key === "Enter") finish();
1291
+ });
1292
+ nameInput.focus();
1293
+ });
1294
+ }
732
1295
  renderToolbar() {
733
1296
  this.toolbarEl = document.createElement("div");
734
1297
  this.toolbarEl.className = "review-toolbar";
@@ -738,15 +1301,41 @@ var ReviewMode = class {
738
1301
  updateToolbar() {
739
1302
  if (!this.toolbarEl) return;
740
1303
  const pinCount = this.nextPinNumber - 1;
1304
+ const isNav = this.mode === "navigate";
1305
+ const isComment = this.mode === "comment";
741
1306
  this.toolbarEl.innerHTML = `
1307
+ <div class="review-mode-toggle">
1308
+ <button class="review-mode-btn ${isNav ? "review-mode-btn--active" : ""}" data-mode="navigate">
1309
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
1310
+ Navigate
1311
+ </button>
1312
+ <button class="review-mode-btn ${isComment ? "review-mode-btn--active" : ""}" data-mode="comment">
1313
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0Z"/><circle cx="12" cy="10" r="3"/></svg>
1314
+ Comment
1315
+ </button>
1316
+ </div>
742
1317
  <span style="font-weight:500;">${this.initData.session.name}</span>
743
1318
  <span style="opacity:0.7;">${pinCount} pin${pinCount !== 1 ? "s" : ""}</span>
744
- <button class="review-btn review-btn--submit" style="margin-left:auto;padding:6px 12px;font-size:13px;">General Comment</button>
1319
+ <button class="review-btn review-btn--submit" data-action="general" style="margin-left:auto;padding:6px 12px;font-size:13px;">General Comment</button>
1320
+ <button class="review-mode-btn" data-action="panel" title="View all comments" style="padding:5px 8px;">
1321
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1322
+ </button>
745
1323
  `;
746
- this.toolbarEl.querySelector("button")?.addEventListener("click", (e) => {
1324
+ this.toolbarEl.querySelectorAll("[data-mode]").forEach((btn) => {
1325
+ btn.addEventListener("click", (e) => {
1326
+ e.stopPropagation();
1327
+ const mode = btn.dataset.mode;
1328
+ this.setMode(mode);
1329
+ });
1330
+ });
1331
+ this.toolbarEl.querySelector('[data-action="general"]')?.addEventListener("click", (e) => {
747
1332
  e.stopPropagation();
748
1333
  this.handleGeneralComment();
749
1334
  });
1335
+ this.toolbarEl.querySelector('[data-action="panel"]')?.addEventListener("click", (e) => {
1336
+ e.stopPropagation();
1337
+ this.toggleCommentPanel();
1338
+ });
750
1339
  }
751
1340
  async loadExistingPins() {
752
1341
  try {
@@ -761,6 +1350,7 @@ var ReviewMode = class {
761
1350
  }
762
1351
  }
763
1352
  handleClick(e) {
1353
+ if (this.mode === "navigate") return;
764
1354
  const path = e.composedPath();
765
1355
  if (path.some((el) => el instanceof HTMLElement && el.closest?.("#ourroadmaps-review"))) return;
766
1356
  if (this.pendingPinNumber != null) return;
@@ -783,6 +1373,7 @@ var ReviewMode = class {
783
1373
  this.nextPinNumber++;
784
1374
  this.updateToolbar();
785
1375
  this.showToast("Comment saved");
1376
+ this.commentPanel?.refresh();
786
1377
  },
787
1378
  onCancel: () => {
788
1379
  this.pinManager.removePin(pinNumber);
@@ -792,6 +1383,14 @@ var ReviewMode = class {
792
1383
  e.preventDefault();
793
1384
  e.stopPropagation();
794
1385
  }
1386
+ setMode(newMode) {
1387
+ this.mode = newMode;
1388
+ document.body.style.cursor = newMode === "comment" ? "crosshair" : "";
1389
+ this.updateToolbar();
1390
+ }
1391
+ toggleCommentPanel() {
1392
+ this.commentPanel?.toggle();
1393
+ }
795
1394
  handleGeneralComment() {
796
1395
  if (this.pendingPinNumber != null) return;
797
1396
  this.commentCard.showForGeneral({
@@ -803,6 +1402,7 @@ var ReviewMode = class {
803
1402
  pageUrl: getCurrentPageId()
804
1403
  });
805
1404
  this.showToast("Comment saved");
1405
+ this.commentPanel?.refresh();
806
1406
  },
807
1407
  onCancel: () => {
808
1408
  }
@@ -821,9 +1421,13 @@ var ReviewMode = class {
821
1421
  document.removeEventListener("click", this.clickHandler, true);
822
1422
  }
823
1423
  this.pageListener?.destroy();
1424
+ if (this.keydownHandler) {
1425
+ document.removeEventListener("keydown", this.keydownHandler);
1426
+ }
824
1427
  this.dismissPrompt();
825
1428
  this.toolbarEl?.remove();
826
1429
  this.commentCard.hide();
1430
+ this.commentPanel?.destroy();
827
1431
  this.pinManager.destroy();
828
1432
  }
829
1433
  };
@@ -1089,10 +1693,16 @@ var Review = class {
1089
1693
  tokenType = "review";
1090
1694
  token = reviewToken;
1091
1695
  storeToken("review", reviewToken);
1696
+ const clean = new URL(window.location.href);
1697
+ clean.searchParams.delete("review");
1698
+ history.replaceState(null, "", clean.toString());
1092
1699
  } else if (triageToken) {
1093
1700
  tokenType = "triage";
1094
1701
  token = triageToken;
1095
1702
  storeToken("triage", triageToken);
1703
+ const clean = new URL(window.location.href);
1704
+ clean.searchParams.delete("triage");
1705
+ history.replaceState(null, "", clean.toString());
1096
1706
  } else {
1097
1707
  const stored = getStoredToken();
1098
1708
  if (stored) {
@@ -1154,5 +1764,5 @@ var Review = class {
1154
1764
  };
1155
1765
 
1156
1766
  export { Review, getStoredToken, storeToken };
1157
- //# sourceMappingURL=chunk-EBLPFIUM.js.map
1158
- //# sourceMappingURL=chunk-EBLPFIUM.js.map
1767
+ //# sourceMappingURL=chunk-SCGNQ7Z7.js.map
1768
+ //# sourceMappingURL=chunk-SCGNQ7Z7.js.map