@pixelatlarge/peanut-gallery-widget 0.1.0 → 0.1.1
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/dist/{chunk-O6C4PORF.js → chunk-X4WAQVYW.js} +114 -23
- package/dist/index.cjs +113 -23
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +1 -1
- package/dist/peanut-gallery.global.js +7 -7
- package/dist/react.cjs +113 -23
- package/dist/react.js +1 -1
- package/package.json +2 -2
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
newIssueUrl,
|
|
6
6
|
uid,
|
|
7
7
|
LABELS,
|
|
8
|
+
MAX_STEPS,
|
|
8
9
|
resolveTheme,
|
|
9
10
|
themeToCssVars,
|
|
10
11
|
DEFAULT_FONTS_URL
|
|
@@ -82,6 +83,37 @@ var css = `
|
|
|
82
83
|
`;
|
|
83
84
|
|
|
84
85
|
// src/widget.ts
|
|
86
|
+
var LOCATION_EVENT = "peanut-gallery:locationchange";
|
|
87
|
+
function normalizePageUrl(href) {
|
|
88
|
+
try {
|
|
89
|
+
const u = new URL(href);
|
|
90
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
91
|
+
if (key === "peanut" || key.startsWith("utm_") || key === "fbclid" || key === "gclid") u.searchParams.delete(key);
|
|
92
|
+
}
|
|
93
|
+
const search = u.searchParams.toString();
|
|
94
|
+
const hash = /^#!?\//.test(u.hash) ? u.hash : "";
|
|
95
|
+
return u.origin + u.pathname + (search ? `?${search}` : "") + hash;
|
|
96
|
+
} catch {
|
|
97
|
+
return href;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function watchHistory() {
|
|
101
|
+
if (typeof history === "undefined") return;
|
|
102
|
+
const h = history;
|
|
103
|
+
if (h.__peanutGallery) return;
|
|
104
|
+
h.__peanutGallery = true;
|
|
105
|
+
for (const name of ["pushState", "replaceState"]) {
|
|
106
|
+
const original = h[name];
|
|
107
|
+
h[name] = function(...args) {
|
|
108
|
+
const result = original.apply(this, args);
|
|
109
|
+
try {
|
|
110
|
+
window.dispatchEvent(new Event(LOCATION_EVENT));
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
85
117
|
var store = {
|
|
86
118
|
get(key) {
|
|
87
119
|
try {
|
|
@@ -112,6 +144,10 @@ var PeanutGalleryWidget = class {
|
|
|
112
144
|
toolbar;
|
|
113
145
|
raf = 0;
|
|
114
146
|
destroyed = false;
|
|
147
|
+
observer;
|
|
148
|
+
/** The reviewer's recent clicks on this page (see Annotation.steps). */
|
|
149
|
+
steps = [];
|
|
150
|
+
stepsPage = "";
|
|
115
151
|
constructor(cfg) {
|
|
116
152
|
if (!cfg.repo || !/^[\w.-]+\/[\w.-]+$/.test(cfg.repo)) throw new Error('PeanutGallery: config.repo must be "owner/name"');
|
|
117
153
|
this.cfg = {
|
|
@@ -155,21 +191,44 @@ ${css}`;
|
|
|
155
191
|
this.root.append(this.highlight, this.pinsLayer, this.toolbar);
|
|
156
192
|
document.body.append(this.host);
|
|
157
193
|
document.addEventListener("mousemove", this.onMouseMove, true);
|
|
194
|
+
document.addEventListener("click", this.recordStep, true);
|
|
158
195
|
document.addEventListener("click", this.onClick, true);
|
|
159
196
|
document.addEventListener("keydown", this.onKey, true);
|
|
160
197
|
window.addEventListener("scroll", this.schedulePins, true);
|
|
161
198
|
window.addEventListener("resize", this.schedulePins);
|
|
199
|
+
watchHistory();
|
|
200
|
+
window.addEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
201
|
+
window.addEventListener("popstate", this.onLocationChange);
|
|
202
|
+
window.addEventListener("hashchange", this.onLocationChange);
|
|
203
|
+
if (typeof MutationObserver !== "undefined") {
|
|
204
|
+
this.observer = new MutationObserver((records) => {
|
|
205
|
+
if (records.some((r) => r.target !== this.host)) this.schedulePins();
|
|
206
|
+
});
|
|
207
|
+
this.observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden", "open", "aria-hidden"], characterData: true });
|
|
208
|
+
}
|
|
162
209
|
this.renderPins();
|
|
163
210
|
this.updateCount();
|
|
164
211
|
return this;
|
|
165
212
|
}
|
|
213
|
+
onLocationChange = () => {
|
|
214
|
+
if (this.destroyed) return;
|
|
215
|
+
this.closeComposer();
|
|
216
|
+
this.schedulePins();
|
|
217
|
+
if (this.panel) this.renderPanel();
|
|
218
|
+
};
|
|
166
219
|
destroy() {
|
|
167
220
|
this.destroyed = true;
|
|
168
221
|
document.removeEventListener("mousemove", this.onMouseMove, true);
|
|
169
222
|
document.removeEventListener("click", this.onClick, true);
|
|
223
|
+
document.removeEventListener("click", this.recordStep, true);
|
|
170
224
|
document.removeEventListener("keydown", this.onKey, true);
|
|
171
225
|
window.removeEventListener("scroll", this.schedulePins, true);
|
|
172
226
|
window.removeEventListener("resize", this.schedulePins);
|
|
227
|
+
window.removeEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
228
|
+
window.removeEventListener("popstate", this.onLocationChange);
|
|
229
|
+
window.removeEventListener("hashchange", this.onLocationChange);
|
|
230
|
+
this.observer?.disconnect();
|
|
231
|
+
if (this.raf) cancelAnimationFrame(this.raf);
|
|
173
232
|
this.host?.remove();
|
|
174
233
|
}
|
|
175
234
|
/** Current annotations (all pages). */
|
|
@@ -206,7 +265,7 @@ ${css}`;
|
|
|
206
265
|
if (this.panel) this.renderPanel();
|
|
207
266
|
}
|
|
208
267
|
pageUrl() {
|
|
209
|
-
return location.
|
|
268
|
+
return normalizePageUrl(location.href);
|
|
210
269
|
}
|
|
211
270
|
// ---------- toolbar ----------
|
|
212
271
|
buildToolbar() {
|
|
@@ -271,6 +330,30 @@ ${css}`;
|
|
|
271
330
|
this.setMode("idle");
|
|
272
331
|
this.openComposer(target, e.clientX, e.clientY);
|
|
273
332
|
};
|
|
333
|
+
/**
|
|
334
|
+
* Remember what the reviewer clicks while using the app (not while placing a note), so a
|
|
335
|
+
* note inside a modal or a menu carries the clicks that opened it. Recorded in the capture
|
|
336
|
+
* phase, before the app re-renders. Only the element is kept, never what was typed.
|
|
337
|
+
*/
|
|
338
|
+
recordStep = (e) => {
|
|
339
|
+
if (this.mode === "commenting" || this.destroyed) return;
|
|
340
|
+
if (this.host && e.composedPath().includes(this.host)) return;
|
|
341
|
+
const raw = e.target;
|
|
342
|
+
if (!(raw instanceof Element)) return;
|
|
343
|
+
const target = raw.closest('button, a, summary, label, select, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"], [role="checkbox"], [data-testid]') ?? raw;
|
|
344
|
+
const here = this.pageUrl();
|
|
345
|
+
if (here !== this.stepsPage) {
|
|
346
|
+
this.steps = [];
|
|
347
|
+
this.stepsPage = here;
|
|
348
|
+
}
|
|
349
|
+
const anchor = createAnchor(target, e.clientX, e.clientY);
|
|
350
|
+
this.steps.push({ ...anchor, context: void 0, textFingerprint: anchor.textFingerprint.slice(0, 60) });
|
|
351
|
+
if (this.steps.length > MAX_STEPS) this.steps.shift();
|
|
352
|
+
};
|
|
353
|
+
currentSteps() {
|
|
354
|
+
if (this.stepsPage !== this.pageUrl() || !this.steps.length) return void 0;
|
|
355
|
+
return this.steps.map((st) => ({ ...st }));
|
|
356
|
+
}
|
|
274
357
|
onKey = (e) => {
|
|
275
358
|
if (e.key === "Escape") {
|
|
276
359
|
if (this.composer) this.closeComposer();
|
|
@@ -309,7 +392,8 @@ ${css}`;
|
|
|
309
392
|
pageUrl: this.pageUrl(),
|
|
310
393
|
viewport: { width: innerWidth, height: innerHeight },
|
|
311
394
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
312
|
-
author: this.session.author
|
|
395
|
+
author: this.session.author,
|
|
396
|
+
...this.currentSteps() ? { steps: this.currentSteps() } : {}
|
|
313
397
|
});
|
|
314
398
|
}
|
|
315
399
|
this.save();
|
|
@@ -347,31 +431,38 @@ ${css}`;
|
|
|
347
431
|
this.renderPins();
|
|
348
432
|
});
|
|
349
433
|
};
|
|
434
|
+
pinsSignature = "";
|
|
350
435
|
renderPins() {
|
|
351
|
-
this.pinsLayer.replaceChildren();
|
|
352
436
|
const here = this.pageUrl();
|
|
437
|
+
const placed = [];
|
|
353
438
|
this.session.annotations.forEach((a, i) => {
|
|
354
|
-
if (a.pageUrl !== here) return;
|
|
439
|
+
if (normalizePageUrl(a.pageUrl) !== here) return;
|
|
355
440
|
const { el: target, confidence } = resolveAnchor(a.anchor, document);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
y = a.anchor.rect.y + a.anchor.rect.height * a.anchor.offset.y;
|
|
364
|
-
}
|
|
365
|
-
const pin = el("div", "pg-pin" + (confidence < 0.5 ? " pg-low" : ""), String(i + 1));
|
|
366
|
-
pin.title = a.text;
|
|
367
|
-
pin.style.left = `${x}px`;
|
|
368
|
-
pin.style.top = `${y}px`;
|
|
369
|
-
pin.onclick = (ev) => {
|
|
370
|
-
ev.stopPropagation();
|
|
371
|
-
this.openComposer(target ?? document.body, x - scrollX, y - scrollY, a);
|
|
372
|
-
};
|
|
373
|
-
this.pinsLayer.append(pin);
|
|
441
|
+
if (!target || confidence < 0.4) return;
|
|
442
|
+
const vis = target.checkVisibility;
|
|
443
|
+
if (typeof vis === "function" && !vis.call(target)) return;
|
|
444
|
+
const r = target.getBoundingClientRect();
|
|
445
|
+
const x = Math.round(r.left + scrollX + r.width * a.anchor.offset.x);
|
|
446
|
+
const y = Math.round(r.top + scrollY + r.height * a.anchor.offset.y);
|
|
447
|
+
placed.push({ a, n: i + 1, x, y, low: confidence < 0.5 });
|
|
374
448
|
});
|
|
449
|
+
const sig = placed.map((p) => `${p.a.id}:${p.n}:${p.x}:${p.y}:${p.low}:${p.a.text}`).join("|");
|
|
450
|
+
if (sig === this.pinsSignature && this.pinsLayer.childElementCount === placed.length) return;
|
|
451
|
+
this.pinsSignature = sig;
|
|
452
|
+
this.pinsLayer.replaceChildren(
|
|
453
|
+
...placed.map(({ a, n, x, y, low }) => {
|
|
454
|
+
const pin = el("div", "pg-pin" + (low ? " pg-low" : ""), String(n));
|
|
455
|
+
pin.title = a.text;
|
|
456
|
+
pin.style.left = `${x}px`;
|
|
457
|
+
pin.style.top = `${y}px`;
|
|
458
|
+
pin.onclick = (ev) => {
|
|
459
|
+
ev.stopPropagation();
|
|
460
|
+
const target = resolveAnchor(a.anchor, document).el ?? document.body;
|
|
461
|
+
this.openComposer(target, x - scrollX, y - scrollY, a);
|
|
462
|
+
};
|
|
463
|
+
return pin;
|
|
464
|
+
})
|
|
465
|
+
);
|
|
375
466
|
}
|
|
376
467
|
// ---------- panel ----------
|
|
377
468
|
togglePanel() {
|
|
@@ -398,7 +489,7 @@ ${css}`;
|
|
|
398
489
|
const text = el("div", "pg-text");
|
|
399
490
|
if (a.kind) text.append(el("span", "pg-kind", a.kind));
|
|
400
491
|
text.append(`${i + 1}. ${a.text}`);
|
|
401
|
-
const meta = el("div", "pg-meta", `${a.pageUrl === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
492
|
+
const meta = el("div", "pg-meta", `${normalizePageUrl(a.pageUrl) === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
402
493
|
item.append(text, meta);
|
|
403
494
|
p.append(item);
|
|
404
495
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -101,6 +101,37 @@ var css = `
|
|
|
101
101
|
`;
|
|
102
102
|
|
|
103
103
|
// src/widget.ts
|
|
104
|
+
var LOCATION_EVENT = "peanut-gallery:locationchange";
|
|
105
|
+
function normalizePageUrl(href) {
|
|
106
|
+
try {
|
|
107
|
+
const u = new URL(href);
|
|
108
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
109
|
+
if (key === "peanut" || key.startsWith("utm_") || key === "fbclid" || key === "gclid") u.searchParams.delete(key);
|
|
110
|
+
}
|
|
111
|
+
const search = u.searchParams.toString();
|
|
112
|
+
const hash = /^#!?\//.test(u.hash) ? u.hash : "";
|
|
113
|
+
return u.origin + u.pathname + (search ? `?${search}` : "") + hash;
|
|
114
|
+
} catch {
|
|
115
|
+
return href;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function watchHistory() {
|
|
119
|
+
if (typeof history === "undefined") return;
|
|
120
|
+
const h = history;
|
|
121
|
+
if (h.__peanutGallery) return;
|
|
122
|
+
h.__peanutGallery = true;
|
|
123
|
+
for (const name of ["pushState", "replaceState"]) {
|
|
124
|
+
const original = h[name];
|
|
125
|
+
h[name] = function(...args) {
|
|
126
|
+
const result = original.apply(this, args);
|
|
127
|
+
try {
|
|
128
|
+
window.dispatchEvent(new Event(LOCATION_EVENT));
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
104
135
|
var store = {
|
|
105
136
|
get(key) {
|
|
106
137
|
try {
|
|
@@ -131,6 +162,10 @@ var PeanutGalleryWidget = class {
|
|
|
131
162
|
toolbar;
|
|
132
163
|
raf = 0;
|
|
133
164
|
destroyed = false;
|
|
165
|
+
observer;
|
|
166
|
+
/** The reviewer's recent clicks on this page (see Annotation.steps). */
|
|
167
|
+
steps = [];
|
|
168
|
+
stepsPage = "";
|
|
134
169
|
constructor(cfg) {
|
|
135
170
|
if (!cfg.repo || !/^[\w.-]+\/[\w.-]+$/.test(cfg.repo)) throw new Error('PeanutGallery: config.repo must be "owner/name"');
|
|
136
171
|
this.cfg = {
|
|
@@ -174,21 +209,44 @@ ${css}`;
|
|
|
174
209
|
this.root.append(this.highlight, this.pinsLayer, this.toolbar);
|
|
175
210
|
document.body.append(this.host);
|
|
176
211
|
document.addEventListener("mousemove", this.onMouseMove, true);
|
|
212
|
+
document.addEventListener("click", this.recordStep, true);
|
|
177
213
|
document.addEventListener("click", this.onClick, true);
|
|
178
214
|
document.addEventListener("keydown", this.onKey, true);
|
|
179
215
|
window.addEventListener("scroll", this.schedulePins, true);
|
|
180
216
|
window.addEventListener("resize", this.schedulePins);
|
|
217
|
+
watchHistory();
|
|
218
|
+
window.addEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
219
|
+
window.addEventListener("popstate", this.onLocationChange);
|
|
220
|
+
window.addEventListener("hashchange", this.onLocationChange);
|
|
221
|
+
if (typeof MutationObserver !== "undefined") {
|
|
222
|
+
this.observer = new MutationObserver((records) => {
|
|
223
|
+
if (records.some((r) => r.target !== this.host)) this.schedulePins();
|
|
224
|
+
});
|
|
225
|
+
this.observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden", "open", "aria-hidden"], characterData: true });
|
|
226
|
+
}
|
|
181
227
|
this.renderPins();
|
|
182
228
|
this.updateCount();
|
|
183
229
|
return this;
|
|
184
230
|
}
|
|
231
|
+
onLocationChange = () => {
|
|
232
|
+
if (this.destroyed) return;
|
|
233
|
+
this.closeComposer();
|
|
234
|
+
this.schedulePins();
|
|
235
|
+
if (this.panel) this.renderPanel();
|
|
236
|
+
};
|
|
185
237
|
destroy() {
|
|
186
238
|
this.destroyed = true;
|
|
187
239
|
document.removeEventListener("mousemove", this.onMouseMove, true);
|
|
188
240
|
document.removeEventListener("click", this.onClick, true);
|
|
241
|
+
document.removeEventListener("click", this.recordStep, true);
|
|
189
242
|
document.removeEventListener("keydown", this.onKey, true);
|
|
190
243
|
window.removeEventListener("scroll", this.schedulePins, true);
|
|
191
244
|
window.removeEventListener("resize", this.schedulePins);
|
|
245
|
+
window.removeEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
246
|
+
window.removeEventListener("popstate", this.onLocationChange);
|
|
247
|
+
window.removeEventListener("hashchange", this.onLocationChange);
|
|
248
|
+
this.observer?.disconnect();
|
|
249
|
+
if (this.raf) cancelAnimationFrame(this.raf);
|
|
192
250
|
this.host?.remove();
|
|
193
251
|
}
|
|
194
252
|
/** Current annotations (all pages). */
|
|
@@ -225,7 +283,7 @@ ${css}`;
|
|
|
225
283
|
if (this.panel) this.renderPanel();
|
|
226
284
|
}
|
|
227
285
|
pageUrl() {
|
|
228
|
-
return location.
|
|
286
|
+
return normalizePageUrl(location.href);
|
|
229
287
|
}
|
|
230
288
|
// ---------- toolbar ----------
|
|
231
289
|
buildToolbar() {
|
|
@@ -290,6 +348,30 @@ ${css}`;
|
|
|
290
348
|
this.setMode("idle");
|
|
291
349
|
this.openComposer(target, e.clientX, e.clientY);
|
|
292
350
|
};
|
|
351
|
+
/**
|
|
352
|
+
* Remember what the reviewer clicks while using the app (not while placing a note), so a
|
|
353
|
+
* note inside a modal or a menu carries the clicks that opened it. Recorded in the capture
|
|
354
|
+
* phase, before the app re-renders. Only the element is kept, never what was typed.
|
|
355
|
+
*/
|
|
356
|
+
recordStep = (e) => {
|
|
357
|
+
if (this.mode === "commenting" || this.destroyed) return;
|
|
358
|
+
if (this.host && e.composedPath().includes(this.host)) return;
|
|
359
|
+
const raw = e.target;
|
|
360
|
+
if (!(raw instanceof Element)) return;
|
|
361
|
+
const target = raw.closest('button, a, summary, label, select, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"], [role="checkbox"], [data-testid]') ?? raw;
|
|
362
|
+
const here = this.pageUrl();
|
|
363
|
+
if (here !== this.stepsPage) {
|
|
364
|
+
this.steps = [];
|
|
365
|
+
this.stepsPage = here;
|
|
366
|
+
}
|
|
367
|
+
const anchor = (0, import_peanut_gallery_core.createAnchor)(target, e.clientX, e.clientY);
|
|
368
|
+
this.steps.push({ ...anchor, context: void 0, textFingerprint: anchor.textFingerprint.slice(0, 60) });
|
|
369
|
+
if (this.steps.length > import_peanut_gallery_core.MAX_STEPS) this.steps.shift();
|
|
370
|
+
};
|
|
371
|
+
currentSteps() {
|
|
372
|
+
if (this.stepsPage !== this.pageUrl() || !this.steps.length) return void 0;
|
|
373
|
+
return this.steps.map((st) => ({ ...st }));
|
|
374
|
+
}
|
|
293
375
|
onKey = (e) => {
|
|
294
376
|
if (e.key === "Escape") {
|
|
295
377
|
if (this.composer) this.closeComposer();
|
|
@@ -328,7 +410,8 @@ ${css}`;
|
|
|
328
410
|
pageUrl: this.pageUrl(),
|
|
329
411
|
viewport: { width: innerWidth, height: innerHeight },
|
|
330
412
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
331
|
-
author: this.session.author
|
|
413
|
+
author: this.session.author,
|
|
414
|
+
...this.currentSteps() ? { steps: this.currentSteps() } : {}
|
|
332
415
|
});
|
|
333
416
|
}
|
|
334
417
|
this.save();
|
|
@@ -366,31 +449,38 @@ ${css}`;
|
|
|
366
449
|
this.renderPins();
|
|
367
450
|
});
|
|
368
451
|
};
|
|
452
|
+
pinsSignature = "";
|
|
369
453
|
renderPins() {
|
|
370
|
-
this.pinsLayer.replaceChildren();
|
|
371
454
|
const here = this.pageUrl();
|
|
455
|
+
const placed = [];
|
|
372
456
|
this.session.annotations.forEach((a, i) => {
|
|
373
|
-
if (a.pageUrl !== here) return;
|
|
457
|
+
if (normalizePageUrl(a.pageUrl) !== here) return;
|
|
374
458
|
const { el: target, confidence } = (0, import_peanut_gallery_core.resolveAnchor)(a.anchor, document);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
y = a.anchor.rect.y + a.anchor.rect.height * a.anchor.offset.y;
|
|
383
|
-
}
|
|
384
|
-
const pin = el("div", "pg-pin" + (confidence < 0.5 ? " pg-low" : ""), String(i + 1));
|
|
385
|
-
pin.title = a.text;
|
|
386
|
-
pin.style.left = `${x}px`;
|
|
387
|
-
pin.style.top = `${y}px`;
|
|
388
|
-
pin.onclick = (ev) => {
|
|
389
|
-
ev.stopPropagation();
|
|
390
|
-
this.openComposer(target ?? document.body, x - scrollX, y - scrollY, a);
|
|
391
|
-
};
|
|
392
|
-
this.pinsLayer.append(pin);
|
|
459
|
+
if (!target || confidence < 0.4) return;
|
|
460
|
+
const vis = target.checkVisibility;
|
|
461
|
+
if (typeof vis === "function" && !vis.call(target)) return;
|
|
462
|
+
const r = target.getBoundingClientRect();
|
|
463
|
+
const x = Math.round(r.left + scrollX + r.width * a.anchor.offset.x);
|
|
464
|
+
const y = Math.round(r.top + scrollY + r.height * a.anchor.offset.y);
|
|
465
|
+
placed.push({ a, n: i + 1, x, y, low: confidence < 0.5 });
|
|
393
466
|
});
|
|
467
|
+
const sig = placed.map((p) => `${p.a.id}:${p.n}:${p.x}:${p.y}:${p.low}:${p.a.text}`).join("|");
|
|
468
|
+
if (sig === this.pinsSignature && this.pinsLayer.childElementCount === placed.length) return;
|
|
469
|
+
this.pinsSignature = sig;
|
|
470
|
+
this.pinsLayer.replaceChildren(
|
|
471
|
+
...placed.map(({ a, n, x, y, low }) => {
|
|
472
|
+
const pin = el("div", "pg-pin" + (low ? " pg-low" : ""), String(n));
|
|
473
|
+
pin.title = a.text;
|
|
474
|
+
pin.style.left = `${x}px`;
|
|
475
|
+
pin.style.top = `${y}px`;
|
|
476
|
+
pin.onclick = (ev) => {
|
|
477
|
+
ev.stopPropagation();
|
|
478
|
+
const target = (0, import_peanut_gallery_core.resolveAnchor)(a.anchor, document).el ?? document.body;
|
|
479
|
+
this.openComposer(target, x - scrollX, y - scrollY, a);
|
|
480
|
+
};
|
|
481
|
+
return pin;
|
|
482
|
+
})
|
|
483
|
+
);
|
|
394
484
|
}
|
|
395
485
|
// ---------- panel ----------
|
|
396
486
|
togglePanel() {
|
|
@@ -417,7 +507,7 @@ ${css}`;
|
|
|
417
507
|
const text = el("div", "pg-text");
|
|
418
508
|
if (a.kind) text.append(el("span", "pg-kind", a.kind));
|
|
419
509
|
text.append(`${i + 1}. ${a.text}`);
|
|
420
|
-
const meta = el("div", "pg-meta", `${a.pageUrl === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
510
|
+
const meta = el("div", "pg-meta", `${normalizePageUrl(a.pageUrl) === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
421
511
|
item.append(text, meta);
|
|
422
512
|
p.append(item);
|
|
423
513
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { AnnotationKind, Theme, ReviewSession, Annotation } from '@pixelatlarge/peanut-gallery-core';
|
|
2
2
|
export { Annotation, AnnotationKind, ReviewSession } from '@pixelatlarge/peanut-gallery-core';
|
|
3
3
|
|
|
4
|
+
type SubmitResult = void | {
|
|
5
|
+
issueUrl?: string;
|
|
6
|
+
keep?: boolean;
|
|
7
|
+
};
|
|
4
8
|
interface PeanutGalleryConfig {
|
|
5
9
|
/** GitHub repo as "owner/name". Required. */
|
|
6
10
|
repo: string;
|
|
@@ -39,10 +43,7 @@ interface PeanutGalleryConfig {
|
|
|
39
43
|
* Overrides `mode`. Resolve to clear the notes; return `{ issueUrl }` to link to the result,
|
|
40
44
|
* or `{ keep: true }` to leave the notes on the page. Throw to show an error and keep them.
|
|
41
45
|
*/
|
|
42
|
-
onSubmit?: (session: ReviewSession) =>
|
|
43
|
-
issueUrl?: string;
|
|
44
|
-
keep?: boolean;
|
|
45
|
-
}>;
|
|
46
|
+
onSubmit?: (session: ReviewSession) => SubmitResult | Promise<SubmitResult>;
|
|
46
47
|
/** Start from these notes instead of the ones saved in this browser (e.g. a demo or a test). */
|
|
47
48
|
session?: ReviewSession;
|
|
48
49
|
}
|
|
@@ -60,10 +61,15 @@ declare class PeanutGalleryWidget {
|
|
|
60
61
|
private toolbar;
|
|
61
62
|
private raf;
|
|
62
63
|
private destroyed;
|
|
64
|
+
private observer?;
|
|
65
|
+
/** The reviewer's recent clicks on this page (see Annotation.steps). */
|
|
66
|
+
private steps;
|
|
67
|
+
private stepsPage;
|
|
63
68
|
constructor(cfg: PeanutGalleryConfig);
|
|
64
69
|
get storageKey(): string;
|
|
65
70
|
/** Mount the widget. Safe to call once DOM is ready. */
|
|
66
71
|
mount(): this;
|
|
72
|
+
private onLocationChange;
|
|
67
73
|
destroy(): void;
|
|
68
74
|
/** Current annotations (all pages). */
|
|
69
75
|
getAnnotations(): Annotation[];
|
|
@@ -78,10 +84,18 @@ declare class PeanutGalleryWidget {
|
|
|
78
84
|
private onMouseMove;
|
|
79
85
|
private hideHighlight;
|
|
80
86
|
private onClick;
|
|
87
|
+
/**
|
|
88
|
+
* Remember what the reviewer clicks while using the app (not while placing a note), so a
|
|
89
|
+
* note inside a modal or a menu carries the clicks that opened it. Recorded in the capture
|
|
90
|
+
* phase, before the app re-renders. Only the element is kept, never what was typed.
|
|
91
|
+
*/
|
|
92
|
+
private recordStep;
|
|
93
|
+
private currentSteps;
|
|
81
94
|
private onKey;
|
|
82
95
|
private openComposer;
|
|
83
96
|
private closeComposer;
|
|
84
97
|
private schedulePins;
|
|
98
|
+
private pinsSignature;
|
|
85
99
|
private renderPins;
|
|
86
100
|
private togglePanel;
|
|
87
101
|
private renderPanel;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { AnnotationKind, Theme, ReviewSession, Annotation } from '@pixelatlarge/peanut-gallery-core';
|
|
2
2
|
export { Annotation, AnnotationKind, ReviewSession } from '@pixelatlarge/peanut-gallery-core';
|
|
3
3
|
|
|
4
|
+
type SubmitResult = void | {
|
|
5
|
+
issueUrl?: string;
|
|
6
|
+
keep?: boolean;
|
|
7
|
+
};
|
|
4
8
|
interface PeanutGalleryConfig {
|
|
5
9
|
/** GitHub repo as "owner/name". Required. */
|
|
6
10
|
repo: string;
|
|
@@ -39,10 +43,7 @@ interface PeanutGalleryConfig {
|
|
|
39
43
|
* Overrides `mode`. Resolve to clear the notes; return `{ issueUrl }` to link to the result,
|
|
40
44
|
* or `{ keep: true }` to leave the notes on the page. Throw to show an error and keep them.
|
|
41
45
|
*/
|
|
42
|
-
onSubmit?: (session: ReviewSession) =>
|
|
43
|
-
issueUrl?: string;
|
|
44
|
-
keep?: boolean;
|
|
45
|
-
}>;
|
|
46
|
+
onSubmit?: (session: ReviewSession) => SubmitResult | Promise<SubmitResult>;
|
|
46
47
|
/** Start from these notes instead of the ones saved in this browser (e.g. a demo or a test). */
|
|
47
48
|
session?: ReviewSession;
|
|
48
49
|
}
|
|
@@ -60,10 +61,15 @@ declare class PeanutGalleryWidget {
|
|
|
60
61
|
private toolbar;
|
|
61
62
|
private raf;
|
|
62
63
|
private destroyed;
|
|
64
|
+
private observer?;
|
|
65
|
+
/** The reviewer's recent clicks on this page (see Annotation.steps). */
|
|
66
|
+
private steps;
|
|
67
|
+
private stepsPage;
|
|
63
68
|
constructor(cfg: PeanutGalleryConfig);
|
|
64
69
|
get storageKey(): string;
|
|
65
70
|
/** Mount the widget. Safe to call once DOM is ready. */
|
|
66
71
|
mount(): this;
|
|
72
|
+
private onLocationChange;
|
|
67
73
|
destroy(): void;
|
|
68
74
|
/** Current annotations (all pages). */
|
|
69
75
|
getAnnotations(): Annotation[];
|
|
@@ -78,10 +84,18 @@ declare class PeanutGalleryWidget {
|
|
|
78
84
|
private onMouseMove;
|
|
79
85
|
private hideHighlight;
|
|
80
86
|
private onClick;
|
|
87
|
+
/**
|
|
88
|
+
* Remember what the reviewer clicks while using the app (not while placing a note), so a
|
|
89
|
+
* note inside a modal or a menu carries the clicks that opened it. Recorded in the capture
|
|
90
|
+
* phase, before the app re-renders. Only the element is kept, never what was typed.
|
|
91
|
+
*/
|
|
92
|
+
private recordStep;
|
|
93
|
+
private currentSteps;
|
|
81
94
|
private onKey;
|
|
82
95
|
private openComposer;
|
|
83
96
|
private closeComposer;
|
|
84
97
|
private schedulePins;
|
|
98
|
+
private pinsSignature;
|
|
85
99
|
private renderPins;
|
|
86
100
|
private togglePanel;
|
|
87
101
|
private renderPanel;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var PeanutGallery=(()=>{var w=Object.defineProperty;var
|
|
2
|
-
`)&&
|
|
1
|
+
"use strict";var PeanutGallery=(()=>{var w=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var K=(e,t)=>{for(var o in t)w(e,o,{get:t[o],enumerable:!0})},X=(e,t,o,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!q.call(e,n)&&n!==o&&w(e,n,{get:()=>t[n],enumerable:!(i=z(t,n))||i.enumerable});return e};var Y=e=>X(w({},"__esModule",{value:!0}),e);var ht={};K(ht,{destroy:()=>O,init:()=>P});var R=5;function V(e){let t=(e.textFingerprint??"").trim();return t?`\u201C${t.length>40?t.slice(0,39)+"\u2026":t}\u201D`:e.selector.length>60?e.tag:e.selector}var S={root:"peanut-gallery",feedback:"peanut-gallery:feedback",proposal:"peanut-gallery:proposal",accepted:"peanut-gallery:accepted",rejected:"peanut-gallery:rejected"},J="json peanut-gallery",W=80;function y(e){return(e??"").replace(/\s+/g," ").trim().slice(0,W)}function T(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/([^a-zA-Z0-9_-])/g,"\\$1")}var Z=["data-testid","data-test","data-cy","data-proto-id","id","name","aria-label"];function U(e){return/[0-9a-f]{6,}/i.test(e)||/^(css|sc|emotion|svelte|jsx)-/.test(e)||/^_/.test(e)||e.length>40}function Q(e){for(let r of Z){let s=e.getAttribute(r);if(s&&!U(s)){let l=r==="id"?`#${T(s)}`:`${e.tagName.toLowerCase()}[${r}="${s.replace(/"/g,'\\"')}"]`,h=e.ownerDocument;try{if(h.querySelectorAll(l).length===1)return{seg:l,unique:!0}}catch{}}}let t=e.tagName.toLowerCase(),o=Array.from(e.classList).filter(r=>!U(r)).slice(0,2),i=t+o.map(r=>`.${T(r)}`).join(""),n=e.parentElement;if(n){let r=Array.from(n.children).filter(s=>s.tagName===e.tagName);r.length>1&&(i+=`:nth-of-type(${r.indexOf(e)+1})`)}return{seg:i,unique:!1}}function tt(e){let t=[],o=e;for(;o&&o!==o.ownerDocument.documentElement;){let{seg:i,unique:n}=Q(o);if(t.unshift(i),n||(o=o.parentElement,t.length>8))break}return t.join(" > ")}function et(e){let t=e;for(;t;){let o=t.querySelector('h1, h2, h3, legend, [role="heading"]');if(o&&o!==e&&t!==e)return y(o.textContent);let i=t.getAttribute?.("aria-label")??t.getAttribute?.("aria-labelledby");if(i&&t!==e)return y(i);t=t.parentElement}}function k(e,t,o){let i=e.getBoundingClientRect(),n=e.ownerDocument.defaultView;return{selector:tt(e),textFingerprint:y(e.textContent),tag:e.tagName.toLowerCase(),rect:{x:i.left+n.scrollX,y:i.top+n.scrollY,width:i.width,height:i.height},offset:{x:i.width?Math.min(1,Math.max(0,(t-i.left)/i.width)):.5,y:i.height?Math.min(1,Math.max(0,(o-i.top)/i.height)):.5},context:et(e)}}function E(e,t){if(!t)return .5;let o=y(e.textContent);return o===t?1:o.includes(t)||t.includes(o)?.75:0}function C(e,t){let o=[];try{o=Array.from(t.querySelectorAll(e.selector))}catch{o=[]}if(o.length===1&&E(o[0],e.textFingerprint)>0)return{el:o[0],confidence:.95};if(o.length>1){let n=o.map(r=>({el:r,s:E(r,e.textFingerprint)})).sort((r,s)=>s.s-r.s);if(n[0].s>0)return{el:n[0].el,confidence:.8}}if(e.textFingerprint){let r=Array.from(t.getElementsByTagName(e.tag)).map(s=>({el:s,s:E(s,e.textFingerprint)})).sort((s,l)=>l.s-s.s);if(r[0]?.s>=.75)return{el:r[0].el,confidence:.6}}if(o.length===1)return{el:o[0],confidence:.5};let i=t.defaultView;if(i&&typeof t.elementFromPoint=="function"){let n=e.rect.x+e.rect.width/2-i.scrollX,r=e.rect.y+e.rect.height/2-i.scrollY,s=t.elementFromPoint(n,r);if(s)return{el:s,confidence:.3}}return{el:null,confidence:0}}function nt(e){let t=e.annotations.length,o=it(e.annotations[0]?.pageUrl??e.prototypeUrl),i=`Prototype feedback: ${t} comment${t===1?"":"s"} on ${o}`,n=[];return n.push(`**Prototype:** ${e.prototypeUrl}`),e.author&&n.push(`**Reviewer:** ${e.author}`),n.push(`**Session:** \`${e.id}\` \xB7 ${e.createdAt}`),n.push(""),e.annotations.forEach((r,s)=>{n.push(`### ${s+1}. ${r.kind?`[${r.kind}] `:""}${ot(r.text)}`),r.text.includes(`
|
|
2
|
+
`)&&n.push(r.text.split(`
|
|
3
3
|
`).slice(1).join(`
|
|
4
|
-
`)),
|
|
5
|
-
`)}}function
|
|
6
|
-
`)[0].trim().slice(0,100)||"(no text)"}function
|
|
4
|
+
`)),n.push(""),n.push(`- **Page:** ${r.pageUrl}`),n.push(`- **Element:** \`${r.anchor.tag??"?"}\` \u2192 \`${r.anchor.selector}\``),r.anchor.textFingerprint&&n.push(`- **Text:** \u201C${r.anchor.textFingerprint}\u201D`),r.anchor.context&&n.push(`- **Near:** ${r.anchor.context}`),r.steps?.length&&n.push(`- **Reached by clicking:** ${r.steps.map(V).join(" \u2192 ")}`),r.viewport&&Number.isFinite(r.viewport.width)&&Number.isFinite(r.viewport.height)&&n.push(`- **Viewport:** ${r.viewport.width}\xD7${r.viewport.height}`),n.push("")}),n.push("<details><summary>peanut-gallery data (do not edit)</summary>"),n.push(""),n.push("```"+J),n.push(JSON.stringify(e,null,2)),n.push("```"),n.push(""),n.push("</details>"),{title:i,body:n.join(`
|
|
5
|
+
`)}}function D(e,t,o=[],i=7500){let n=`https://github.com/${e}/issues/new`,r=a=>{let{title:u,body:d}=nt(a),v=new URLSearchParams({title:u,body:d});return o.length&&v.set("labels",o.join(",")),`${n}?${v.toString()}`},s=r(t);if(s.length<=i)return s;let l={...t,userAgent:void 0,annotations:t.annotations.map(a=>({...a,anchor:{...a.anchor,context:void 0,textFingerprint:a.anchor.textFingerprint.slice(0,40),rect:F(a.anchor.rect)},steps:a.steps?.map(u=>({...u,context:void 0,textFingerprint:u.textFingerprint.slice(0,40),rect:F(u.rect)}))}))};if(s=r(l),s.length<=i)return s;let h=[];for(let a of l.annotations){if(r({...l,annotations:[...h,a]}).length>i)break;h.push(a)}return r({...l,annotations:h})}function F(e){return{x:Math.round(e.x),y:Math.round(e.y),width:Math.round(e.width),height:Math.round(e.height)}}function ot(e){return e.split(`
|
|
6
|
+
`)[0].trim().slice(0,100)||"(no text)"}function it(e){try{let t=new URL(e);return t.pathname==="/"?t.host:t.pathname}catch{return e}}function L(e=""){let t=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID().slice(0,8):Math.random().toString(36).slice(2,10);return e?`${e}_${t}`:t}var c={conifer:"#1E4D36",emerald:"#0F7B52",wasabi:"#719872",matcha:"#9CC98A",pistachio:"#CFE1B4",seaGlass:"#CDE2F2",hydrangea:"#A6BFE5",harbor:"#40648A",chambray:"#6C8EBD",prep:"#152A4A",sunnyside:"#EAC26C",linen:"#F6F3E4",white:"#FFFFFF",gull:"#A5B0B6",carbon:"#22272B"},rt={bg:c.linen,surface:c.white,text:c.carbon,muted:"#5A6268",line:"#DEDCCB",heading:c.conifer,primary:c.emerald,primaryHover:"#006340",onPrimary:c.white,toolbar:c.conifer,toolbarButton:"#325D46",toolbarButtonHover:"#4E7660",onToolbar:c.white,accent:c.sunnyside,onAccent:c.carbon,highlight:c.emerald,highlightTint:"rgba(15, 123, 82, 0.10)",pinLow:c.gull,chip:c.seaGlass,onChip:c.prep,count:c.pistachio,onCount:c.conifer,ghost:"#EDEBE3",ghostHover:"#DBD8CA",link:c.prep,toast:c.prep,onToast:c.linen,danger:"#F6E3DF",onDanger:"#9B3426",success:c.emerald,successTint:"#D4F4E2",fontBody:'"Manrope", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',fontDisplay:'"Young Serif", Georgia, "Times New Roman", serif',fontMono:'"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace'},A="https://fonts.googleapis.com/css2?family=Young+Serif&family=Manrope:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap";function H(e){return{...rt,...e??{}}}var st=e=>e.replace(/[A-Z]/g,t=>"-"+t.toLowerCase());function B(e){return Object.keys(e).map(t=>`--pg-${st(t)}: ${e[t]};`).join(" ")}var N=`
|
|
7
7
|
:host { all: initial; }
|
|
8
8
|
* { box-sizing: border-box; font-family: var(--pg-font-body); font-size: 13px; line-height: 1.4; color: var(--pg-text); }
|
|
9
9
|
.pg-hidden { display: none !important; }
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
.pg-empty { color: var(--pg-muted); padding: 12px 0; }
|
|
72
72
|
.pg-link { color: var(--pg-link); }
|
|
73
73
|
.pg-toast { position: fixed; left: 50%; bottom: 72px; transform: translateX(-50%); background: var(--pg-toast); color: var(--pg-on-toast); padding: 8px 14px; border-radius: 999px; z-index: 2147483200; font-weight: 500; }
|
|
74
|
-
`;var b={get(e){try{return localStorage.getItem(e)}catch{return null}},set(e,t){try{localStorage.setItem(e,t)}catch{}}},
|
|
75
|
-
${B}`,this.cfg.loadFonts&&!document.querySelector(`link[href="${C}"]`)){let n=document.createElement("link");n.rel="stylesheet",n.href=C,document.head.append(n)}return this.root.append(t),this.highlight=a("div","pg-highlight pg-hidden"),this.highlight.append(a("span","pg-tag")),this.pinsLayer=a("div"),this.toolbar=this.buildToolbar(),this.root.append(this.highlight,this.pinsLayer,this.toolbar),document.body.append(this.host),document.addEventListener("mousemove",this.onMouseMove,!0),document.addEventListener("click",this.onClick,!0),document.addEventListener("keydown",this.onKey,!0),window.addEventListener("scroll",this.schedulePins,!0),window.addEventListener("resize",this.schedulePins),this.renderPins(),this.updateCount(),this}destroy(){this.destroyed=!0,document.removeEventListener("mousemove",this.onMouseMove,!0),document.removeEventListener("click",this.onClick,!0),document.removeEventListener("keydown",this.onKey,!0),window.removeEventListener("scroll",this.schedulePins,!0),window.removeEventListener("resize",this.schedulePins),this.host?.remove()}getAnnotations(){return[...this.session.annotations]}loadSession(){if(this.cfg.session&&Array.isArray(this.cfg.session.annotations))return structuredClone(this.cfg.session);try{let t=b.get(this.storageKey);if(t){let n=JSON.parse(t);if(Array.isArray(n.annotations))return n}}catch{}return this.freshSession()}freshSession(){return{id:S("rs"),prototypeUrl:this.cfg.prototypeUrl,annotations:[],createdAt:new Date().toISOString(),author:this.cfg.author??b.get("peanut-gallery:author")??void 0,userAgent:navigator.userAgent}}save(){b.set(this.storageKey,JSON.stringify(this.session)),this.updateCount(),this.renderPins(),this.panel&&this.renderPanel()}pageUrl(){return location.origin+location.pathname+location.search}buildToolbar(){let t=a("div","pg-toolbar"),n=a("span","pg-brand","Notes"),i=a("button","pg-btn","Comment");i.dataset.role="comment",i.onclick=()=>this.setMode(this.mode==="commenting"?"idle":"commenting");let o=a("button","pg-btn","Review");o.dataset.role="review",o.onclick=()=>this.togglePanel();let r=a("span","pg-count","0");return r.dataset.role="count",o.append(r),t.append(n,i,o),t}updateCount(){let t=this.toolbar.querySelector("[data-role=count]");t.textContent=String(this.session.annotations.length)}setMode(t){this.mode=t;let n=this.toolbar.querySelector("[data-role=comment]");n.classList.toggle("pg-active",t==="commenting"),n.textContent=t==="commenting"?"Click an element\u2026 (Esc)":"Comment",document.body.style.cursor=t==="commenting"?"crosshair":"",t==="idle"&&this.hideHighlight()}isOwn(t){return t instanceof Node&&(this.host===t||this.host.contains(t))}onMouseMove=t=>{if(this.mode!=="commenting")return;let n=t.composedPath();if(n.includes(this.host))return this.hideHighlight();let i=n[0];if(!(i instanceof Element)||i===document.documentElement||i===document.body)return this.hideHighlight();this.hoverEl=i;let o=i.getBoundingClientRect();Object.assign(this.highlight.style,{left:`${o.left+scrollX}px`,top:`${o.top+scrollY}px`,width:`${o.width}px`,height:`${o.height}px`}),this.highlight.querySelector(".pg-tag").textContent=it(i),this.highlight.classList.remove("pg-hidden")};hideHighlight(){this.hoverEl=null,this.highlight.classList.add("pg-hidden")}onClick=t=>{if(this.mode!=="commenting"||t.composedPath().includes(this.host))return;let n=this.hoverEl??t.target;n instanceof Element&&(t.preventDefault(),t.stopPropagation(),this.setMode("idle"),this.openComposer(n,t.clientX,t.clientY))};onKey=t=>{t.key==="Escape"&&(this.composer?this.closeComposer():this.mode==="commenting"&&this.setMode("idle"))};openComposer(t,n,i,o){this.closeComposer();let r=o?.anchor??M(t,n,i),s=a("div","pg-composer"),p=a("div","pg-meta",`${r.tag} \xB7 ${r.selector}`);p.title=r.selector;let c=document.createElement("textarea");c.placeholder="What should change here? Be specific \u2014 this becomes a task.",c.value=o?.text??"";let l=document.createElement("select");l.append(new Option("type\u2026",""));for(let d of this.cfg.kinds)l.append(new Option(d,d,!1,o?.kind===d));let u=a("div","pg-row"),f=a("button","pg-btn pg-primary",o?"Update":"Add note"),v=a("button","pg-btn pg-ghost","Cancel");if(v.onclick=()=>this.closeComposer(),f.onclick=()=>{let d=c.value.trim();if(!d)return c.focus();o?(o.text=d,o.kind=l.value||void 0):this.session.annotations.push({id:S("an"),text:d,kind:l.value||void 0,anchor:r,pageUrl:this.pageUrl(),viewport:{width:innerWidth,height:innerHeight},createdAt:new Date().toISOString(),author:this.session.author}),this.save(),this.closeComposer()},u.append(l,a("span","pg-spacer")),o){let d=a("button","pg-btn pg-danger","Delete");d.onclick=()=>{this.session.annotations=this.session.annotations.filter(N=>N.id!==o.id),this.save(),this.closeComposer()},u.append(d)}u.append(v,f),s.append(p,c,u);let R=Math.min(n+12,innerWidth-312)+scrollX,H=Math.min(i+12,innerHeight-190)+scrollY;s.style.left=`${Math.max(8,R)}px`,s.style.top=`${Math.max(8,H)}px`,this.root.append(s),this.composer=s,c.focus()}closeComposer(){this.composer?.remove(),this.composer=null}schedulePins=()=>{this.raf||this.destroyed||(this.raf=requestAnimationFrame(()=>{this.raf=0,this.renderPins()}))};renderPins(){this.pinsLayer.replaceChildren();let t=this.pageUrl();this.session.annotations.forEach((n,i)=>{if(n.pageUrl!==t)return;let{el:o,confidence:r}=T(n.anchor,document),s,p;if(o){let l=o.getBoundingClientRect();s=l.left+scrollX+l.width*n.anchor.offset.x,p=l.top+scrollY+l.height*n.anchor.offset.y}else s=n.anchor.rect.x+n.anchor.rect.width*n.anchor.offset.x,p=n.anchor.rect.y+n.anchor.rect.height*n.anchor.offset.y;let c=a("div","pg-pin"+(r<.5?" pg-low":""),String(i+1));c.title=n.text,c.style.left=`${s}px`,c.style.top=`${p}px`,c.onclick=l=>{l.stopPropagation(),this.openComposer(o??document.body,s-scrollX,p-scrollY,n)},this.pinsLayer.append(c)})}togglePanel(){if(this.panel){this.panel.remove(),this.panel=null;return}this.panel=a("div","pg-panel"),this.root.append(this.panel),this.renderPanel()}renderPanel(){let t=this.panel;t.replaceChildren();let n=a("h3","",`Feedback (${this.session.annotations.length})`);t.append(n),this.session.annotations.length||t.append(a("div","pg-empty","No notes yet. Click \u201CComment\u201D, then click anything on the page."));let i=this.pageUrl();this.session.annotations.forEach((p,c)=>{let l=a("div","pg-item"),u=a("div","pg-text");p.kind&&u.append(a("span","pg-kind",p.kind)),u.append(`${c+1}. ${p.text}`);let f=a("div","pg-meta",`${p.pageUrl===i?"this page":rt(p.pageUrl)} \xB7 ${p.anchor.tag}${p.anchor.textFingerprint?` \u201C${p.anchor.textFingerprint.slice(0,30)}\u201D`:""}`);l.append(u,f),t.append(l)});let o=a("div","pg-row"),r=a("button","pg-btn pg-ghost","Clear");r.onclick=()=>{(!this.session.annotations.length||confirm("Discard all notes?"))&&(this.session=this.freshSession(),this.save())};let s=a("button","pg-btn pg-primary",this.effectiveMode()==="issue-url"?"Submit to GitHub":"Submit");s.disabled=!this.session.annotations.length,s.onclick=()=>this.submit(s),o.append(r,a("span","pg-spacer"),s),t.append(o)}effectiveMode(){return this.cfg.onSubmit?"callback":this.cfg.mode==="relay"?"relay":this.cfg.mode==="issue-url"?"issue-url":this.cfg.relayUrl?"relay":"issue-url"}ensureAuthor(){if(this.session.author)return;let t;try{t=prompt("Your name (shown on the feedback):")?.trim()}catch{}t&&(this.session.author=t,b.set("peanut-gallery:author",t),this.session.annotations.forEach(n=>n.author??=t))}async submit(t){this.ensureAuthor();let n={...this.session,createdAt:new Date().toISOString()};if(this.effectiveMode()==="callback"){t.disabled=!0,t.textContent="Submitting\u2026";try{let i=await this.cfg.onSubmit(n)||{};i.keep||this.resetAfterSubmit(),this.toast(i.issueUrl?`Submitted: ${i.issueUrl}`:"Submitted. Thank you!")}catch(i){this.toast(`Could not submit: ${i.message}`)}finally{t.disabled=!1,t.textContent="Submit"}return}if(this.effectiveMode()==="issue-url"){let i=U(this.cfg.repo,n,this.cfg.labels);if(!window.open(i,"_blank","noopener")){this.toast("Popup blocked \u2014 allow popups and try again.");return}this.toast("Opened GitHub. Press \u201CSubmit new issue\u201D there; notes stay here until you Clear.");return}t.disabled=!0,t.textContent="Submitting\u2026";try{let i=await fetch(this.cfg.relayUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({session:n,labels:this.cfg.labels})});if(!i.ok)throw new Error(`Relay responded ${i.status}`);let o=await i.json();this.resetAfterSubmit(),this.toast(o.issueUrl?`Submitted: ${o.issueUrl}`:"Submitted. Thank you!"),o.issueUrl&&window.open(o.issueUrl,"_blank","noopener")}catch(i){this.toast(`Could not submit: ${i.message}`),t.disabled=!1,t.textContent="Submit"}}resetAfterSubmit(){this.session=this.freshSession(),this.save(),this.panel&&(this.panel.remove(),this.panel=null),this.renderPins(),this.updateCount()}toast(t){let n=a("div","pg-toast",t);this.root.append(n),setTimeout(()=>n.remove(),5e3)}};function a(e,t="",n){let i=document.createElement(e);return t&&(i.className=t),n!==void 0&&(i.textContent=n),i}function it(e){let t=e.getAttribute("data-testid")??e.id;return e.tagName.toLowerCase()+(t?`#${t}`:"")}function rt(e){try{return new URL(e).pathname}catch{return e}}var m=null;function A(e){if(m)return m;m=new x(e);let t=()=>m.mount();return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t(),m}function D(){m?.destroy(),m=null}var g=typeof document<"u"?document.currentScript:null;g?.dataset.repo&&A({repo:g.dataset.repo,relayUrl:g.dataset.relayUrl,mode:g.dataset.mode,activation:g.dataset.activation,loadFonts:g.dataset.loadFonts==="true",author:g.dataset.author,labels:g.dataset.labels?.split(",").map(e=>e.trim())});return j(st);})();
|
|
74
|
+
`;var M="peanut-gallery:locationchange";function $(e){try{let t=new URL(e);for(let n of[...t.searchParams.keys()])(n==="peanut"||n.startsWith("utm_")||n==="fbclid"||n==="gclid")&&t.searchParams.delete(n);let o=t.searchParams.toString(),i=/^#!?\//.test(t.hash)?t.hash:"";return t.origin+t.pathname+(o?`?${o}`:"")+i}catch{return e}}function at(){if(typeof history>"u")return;let e=history;if(!e.__peanutGallery){e.__peanutGallery=!0;for(let t of["pushState","replaceState"]){let o=e[t];e[t]=function(...i){let n=o.apply(this,i);try{window.dispatchEvent(new Event(M))}catch{}return n}}}}var b={get(e){try{return localStorage.getItem(e)}catch{return null}},set(e,t){try{localStorage.setItem(e,t)}catch{}}},pt=["bug","copy","layout","visual","flow","question","praise"],x=class{cfg;host;root;session;mode="idle";hoverEl=null;highlight;pinsLayer;panel=null;composer=null;toolbar;raf=0;destroyed=!1;observer;steps=[];stepsPage="";constructor(t){if(!t.repo||!/^[\w.-]+\/[\w.-]+$/.test(t.repo))throw new Error('PeanutGallery: config.repo must be "owner/name"');this.cfg={mode:"auto",labels:[S.root,S.feedback],activation:"always",kinds:pt,prototypeUrl:typeof location<"u"?location.origin:"",...t}}get storageKey(){return`peanut-gallery:${this.cfg.repo}`}mount(){if(this.cfg.activation==="query"){let o=new URLSearchParams(location.search).has("peanut"),i=sessionStorage.getItem("peanut-gallery:active")==="1";if(!o&&!i)return this;sessionStorage.setItem("peanut-gallery:active","1")}this.session=this.loadSession(),this.host=document.createElement("peanut-gallery"),this.host.setAttribute("data-peanut-gallery",""),this.root=this.host.attachShadow({mode:"open"});let t=document.createElement("style");if(t.textContent=`:host { ${B(H(this.cfg.theme))} }
|
|
75
|
+
${N}`,this.cfg.loadFonts&&!document.querySelector(`link[href="${A}"]`)){let o=document.createElement("link");o.rel="stylesheet",o.href=A,document.head.append(o)}return this.root.append(t),this.highlight=p("div","pg-highlight pg-hidden"),this.highlight.append(p("span","pg-tag")),this.pinsLayer=p("div"),this.toolbar=this.buildToolbar(),this.root.append(this.highlight,this.pinsLayer,this.toolbar),document.body.append(this.host),document.addEventListener("mousemove",this.onMouseMove,!0),document.addEventListener("click",this.recordStep,!0),document.addEventListener("click",this.onClick,!0),document.addEventListener("keydown",this.onKey,!0),window.addEventListener("scroll",this.schedulePins,!0),window.addEventListener("resize",this.schedulePins),at(),window.addEventListener(M,this.onLocationChange),window.addEventListener("popstate",this.onLocationChange),window.addEventListener("hashchange",this.onLocationChange),typeof MutationObserver<"u"&&(this.observer=new MutationObserver(o=>{o.some(i=>i.target!==this.host)&&this.schedulePins()}),this.observer.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["class","style","hidden","open","aria-hidden"],characterData:!0})),this.renderPins(),this.updateCount(),this}onLocationChange=()=>{this.destroyed||(this.closeComposer(),this.schedulePins(),this.panel&&this.renderPanel())};destroy(){this.destroyed=!0,document.removeEventListener("mousemove",this.onMouseMove,!0),document.removeEventListener("click",this.onClick,!0),document.removeEventListener("click",this.recordStep,!0),document.removeEventListener("keydown",this.onKey,!0),window.removeEventListener("scroll",this.schedulePins,!0),window.removeEventListener("resize",this.schedulePins),window.removeEventListener(M,this.onLocationChange),window.removeEventListener("popstate",this.onLocationChange),window.removeEventListener("hashchange",this.onLocationChange),this.observer?.disconnect(),this.raf&&cancelAnimationFrame(this.raf),this.host?.remove()}getAnnotations(){return[...this.session.annotations]}loadSession(){if(this.cfg.session&&Array.isArray(this.cfg.session.annotations))return structuredClone(this.cfg.session);try{let t=b.get(this.storageKey);if(t){let o=JSON.parse(t);if(Array.isArray(o.annotations))return o}}catch{}return this.freshSession()}freshSession(){return{id:L("rs"),prototypeUrl:this.cfg.prototypeUrl,annotations:[],createdAt:new Date().toISOString(),author:this.cfg.author??b.get("peanut-gallery:author")??void 0,userAgent:navigator.userAgent}}save(){b.set(this.storageKey,JSON.stringify(this.session)),this.updateCount(),this.renderPins(),this.panel&&this.renderPanel()}pageUrl(){return $(location.href)}buildToolbar(){let t=p("div","pg-toolbar"),o=p("span","pg-brand","Notes"),i=p("button","pg-btn","Comment");i.dataset.role="comment",i.onclick=()=>this.setMode(this.mode==="commenting"?"idle":"commenting");let n=p("button","pg-btn","Review");n.dataset.role="review",n.onclick=()=>this.togglePanel();let r=p("span","pg-count","0");return r.dataset.role="count",n.append(r),t.append(o,i,n),t}updateCount(){let t=this.toolbar.querySelector("[data-role=count]");t.textContent=String(this.session.annotations.length)}setMode(t){this.mode=t;let o=this.toolbar.querySelector("[data-role=comment]");o.classList.toggle("pg-active",t==="commenting"),o.textContent=t==="commenting"?"Click an element\u2026 (Esc)":"Comment",document.body.style.cursor=t==="commenting"?"crosshair":"",t==="idle"&&this.hideHighlight()}isOwn(t){return t instanceof Node&&(this.host===t||this.host.contains(t))}onMouseMove=t=>{if(this.mode!=="commenting")return;let o=t.composedPath();if(o.includes(this.host))return this.hideHighlight();let i=o[0];if(!(i instanceof Element)||i===document.documentElement||i===document.body)return this.hideHighlight();this.hoverEl=i;let n=i.getBoundingClientRect();Object.assign(this.highlight.style,{left:`${n.left+scrollX}px`,top:`${n.top+scrollY}px`,width:`${n.width}px`,height:`${n.height}px`}),this.highlight.querySelector(".pg-tag").textContent=lt(i),this.highlight.classList.remove("pg-hidden")};hideHighlight(){this.hoverEl=null,this.highlight.classList.add("pg-hidden")}onClick=t=>{if(this.mode!=="commenting"||t.composedPath().includes(this.host))return;let o=this.hoverEl??t.target;o instanceof Element&&(t.preventDefault(),t.stopPropagation(),this.setMode("idle"),this.openComposer(o,t.clientX,t.clientY))};recordStep=t=>{if(this.mode==="commenting"||this.destroyed||this.host&&t.composedPath().includes(this.host))return;let o=t.target;if(!(o instanceof Element))return;let i=o.closest('button, a, summary, label, select, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"], [role="checkbox"], [data-testid]')??o,n=this.pageUrl();n!==this.stepsPage&&(this.steps=[],this.stepsPage=n);let r=k(i,t.clientX,t.clientY);this.steps.push({...r,context:void 0,textFingerprint:r.textFingerprint.slice(0,60)}),this.steps.length>R&&this.steps.shift()};currentSteps(){if(!(this.stepsPage!==this.pageUrl()||!this.steps.length))return this.steps.map(t=>({...t}))}onKey=t=>{t.key==="Escape"&&(this.composer?this.closeComposer():this.mode==="commenting"&&this.setMode("idle"))};openComposer(t,o,i,n){this.closeComposer();let r=n?.anchor??k(t,o,i),s=p("div","pg-composer"),l=p("div","pg-meta",`${r.tag} \xB7 ${r.selector}`);l.title=r.selector;let h=document.createElement("textarea");h.placeholder="What should change here? Be specific \u2014 this becomes a task.",h.value=n?.text??"";let a=document.createElement("select");a.append(new Option("type\u2026",""));for(let g of this.cfg.kinds)a.append(new Option(g,g,!1,n?.kind===g));let u=p("div","pg-row"),d=p("button","pg-btn pg-primary",n?"Update":"Add note"),v=p("button","pg-btn pg-ghost","Cancel");if(v.onclick=()=>this.closeComposer(),d.onclick=()=>{let g=h.value.trim();if(!g)return h.focus();n?(n.text=g,n.kind=a.value||void 0):this.session.annotations.push({id:L("an"),text:g,kind:a.value||void 0,anchor:r,pageUrl:this.pageUrl(),viewport:{width:innerWidth,height:innerHeight},createdAt:new Date().toISOString(),author:this.session.author,...this.currentSteps()?{steps:this.currentSteps()}:{}}),this.save(),this.closeComposer()},u.append(a,p("span","pg-spacer")),n){let g=p("button","pg-btn pg-danger","Delete");g.onclick=()=>{this.session.annotations=this.session.annotations.filter(I=>I.id!==n.id),this.save(),this.closeComposer()},u.append(g)}u.append(v,d),s.append(l,h,u);let _=Math.min(o+12,innerWidth-312)+scrollX,G=Math.min(i+12,innerHeight-190)+scrollY;s.style.left=`${Math.max(8,_)}px`,s.style.top=`${Math.max(8,G)}px`,this.root.append(s),this.composer=s,h.focus()}closeComposer(){this.composer?.remove(),this.composer=null}schedulePins=()=>{this.raf||this.destroyed||(this.raf=requestAnimationFrame(()=>{this.raf=0,this.renderPins()}))};pinsSignature="";renderPins(){let t=this.pageUrl(),o=[];this.session.annotations.forEach((n,r)=>{if($(n.pageUrl)!==t)return;let{el:s,confidence:l}=C(n.anchor,document);if(!s||l<.4)return;let h=s.checkVisibility;if(typeof h=="function"&&!h.call(s))return;let a=s.getBoundingClientRect(),u=Math.round(a.left+scrollX+a.width*n.anchor.offset.x),d=Math.round(a.top+scrollY+a.height*n.anchor.offset.y);o.push({a:n,n:r+1,x:u,y:d,low:l<.5})});let i=o.map(n=>`${n.a.id}:${n.n}:${n.x}:${n.y}:${n.low}:${n.a.text}`).join("|");i===this.pinsSignature&&this.pinsLayer.childElementCount===o.length||(this.pinsSignature=i,this.pinsLayer.replaceChildren(...o.map(({a:n,n:r,x:s,y:l,low:h})=>{let a=p("div","pg-pin"+(h?" pg-low":""),String(r));return a.title=n.text,a.style.left=`${s}px`,a.style.top=`${l}px`,a.onclick=u=>{u.stopPropagation();let d=C(n.anchor,document).el??document.body;this.openComposer(d,s-scrollX,l-scrollY,n)},a})))}togglePanel(){if(this.panel){this.panel.remove(),this.panel=null;return}this.panel=p("div","pg-panel"),this.root.append(this.panel),this.renderPanel()}renderPanel(){let t=this.panel;t.replaceChildren();let o=p("h3","",`Feedback (${this.session.annotations.length})`);t.append(o),this.session.annotations.length||t.append(p("div","pg-empty","No notes yet. Click \u201CComment\u201D, then click anything on the page."));let i=this.pageUrl();this.session.annotations.forEach((l,h)=>{let a=p("div","pg-item"),u=p("div","pg-text");l.kind&&u.append(p("span","pg-kind",l.kind)),u.append(`${h+1}. ${l.text}`);let d=p("div","pg-meta",`${$(l.pageUrl)===i?"this page":ct(l.pageUrl)} \xB7 ${l.anchor.tag}${l.anchor.textFingerprint?` \u201C${l.anchor.textFingerprint.slice(0,30)}\u201D`:""}`);a.append(u,d),t.append(a)});let n=p("div","pg-row"),r=p("button","pg-btn pg-ghost","Clear");r.onclick=()=>{(!this.session.annotations.length||confirm("Discard all notes?"))&&(this.session=this.freshSession(),this.save())};let s=p("button","pg-btn pg-primary",this.effectiveMode()==="issue-url"?"Submit to GitHub":"Submit");s.disabled=!this.session.annotations.length,s.onclick=()=>this.submit(s),n.append(r,p("span","pg-spacer"),s),t.append(n)}effectiveMode(){return this.cfg.onSubmit?"callback":this.cfg.mode==="relay"?"relay":this.cfg.mode==="issue-url"?"issue-url":this.cfg.relayUrl?"relay":"issue-url"}ensureAuthor(){if(this.session.author)return;let t;try{t=prompt("Your name (shown on the feedback):")?.trim()}catch{}t&&(this.session.author=t,b.set("peanut-gallery:author",t),this.session.annotations.forEach(o=>o.author??=t))}async submit(t){this.ensureAuthor();let o={...this.session,createdAt:new Date().toISOString()};if(this.effectiveMode()==="callback"){t.disabled=!0,t.textContent="Submitting\u2026";try{let i=await this.cfg.onSubmit(o)||{};i.keep||this.resetAfterSubmit(),this.toast(i.issueUrl?`Submitted: ${i.issueUrl}`:"Submitted. Thank you!")}catch(i){this.toast(`Could not submit: ${i.message}`)}finally{t.disabled=!1,t.textContent="Submit"}return}if(this.effectiveMode()==="issue-url"){let i=D(this.cfg.repo,o,this.cfg.labels);if(!window.open(i,"_blank","noopener")){this.toast("Popup blocked \u2014 allow popups and try again.");return}this.toast("Opened GitHub. Press \u201CSubmit new issue\u201D there; notes stay here until you Clear.");return}t.disabled=!0,t.textContent="Submitting\u2026";try{let i=await fetch(this.cfg.relayUrl,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({session:o,labels:this.cfg.labels})});if(!i.ok)throw new Error(`Relay responded ${i.status}`);let n=await i.json();this.resetAfterSubmit(),this.toast(n.issueUrl?`Submitted: ${n.issueUrl}`:"Submitted. Thank you!"),n.issueUrl&&window.open(n.issueUrl,"_blank","noopener")}catch(i){this.toast(`Could not submit: ${i.message}`),t.disabled=!1,t.textContent="Submit"}}resetAfterSubmit(){this.session=this.freshSession(),this.save(),this.panel&&(this.panel.remove(),this.panel=null),this.renderPins(),this.updateCount()}toast(t){let o=p("div","pg-toast",t);this.root.append(o),setTimeout(()=>o.remove(),5e3)}};function p(e,t="",o){let i=document.createElement(e);return t&&(i.className=t),o!==void 0&&(i.textContent=o),i}function lt(e){let t=e.getAttribute("data-testid")??e.id;return e.tagName.toLowerCase()+(t?`#${t}`:"")}function ct(e){try{return new URL(e).pathname}catch{return e}}var f=null;function P(e){if(f)return f;f=new x(e);let t=()=>f.mount();return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t(),f}function O(){f?.destroy(),f=null}var m=typeof document<"u"?document.currentScript:null;m?.dataset.repo&&P({repo:m.dataset.repo,relayUrl:m.dataset.relayUrl,mode:m.dataset.mode,activation:m.dataset.activation,loadFonts:m.dataset.loadFonts==="true",author:m.dataset.author,labels:m.dataset.labels?.split(",").map(e=>e.trim())});return Y(ht);})();
|
package/dist/react.cjs
CHANGED
|
@@ -102,6 +102,37 @@ var css = `
|
|
|
102
102
|
`;
|
|
103
103
|
|
|
104
104
|
// src/widget.ts
|
|
105
|
+
var LOCATION_EVENT = "peanut-gallery:locationchange";
|
|
106
|
+
function normalizePageUrl(href) {
|
|
107
|
+
try {
|
|
108
|
+
const u = new URL(href);
|
|
109
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
110
|
+
if (key === "peanut" || key.startsWith("utm_") || key === "fbclid" || key === "gclid") u.searchParams.delete(key);
|
|
111
|
+
}
|
|
112
|
+
const search = u.searchParams.toString();
|
|
113
|
+
const hash = /^#!?\//.test(u.hash) ? u.hash : "";
|
|
114
|
+
return u.origin + u.pathname + (search ? `?${search}` : "") + hash;
|
|
115
|
+
} catch {
|
|
116
|
+
return href;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function watchHistory() {
|
|
120
|
+
if (typeof history === "undefined") return;
|
|
121
|
+
const h = history;
|
|
122
|
+
if (h.__peanutGallery) return;
|
|
123
|
+
h.__peanutGallery = true;
|
|
124
|
+
for (const name of ["pushState", "replaceState"]) {
|
|
125
|
+
const original = h[name];
|
|
126
|
+
h[name] = function(...args) {
|
|
127
|
+
const result = original.apply(this, args);
|
|
128
|
+
try {
|
|
129
|
+
window.dispatchEvent(new Event(LOCATION_EVENT));
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
105
136
|
var store = {
|
|
106
137
|
get(key) {
|
|
107
138
|
try {
|
|
@@ -132,6 +163,10 @@ var PeanutGalleryWidget = class {
|
|
|
132
163
|
toolbar;
|
|
133
164
|
raf = 0;
|
|
134
165
|
destroyed = false;
|
|
166
|
+
observer;
|
|
167
|
+
/** The reviewer's recent clicks on this page (see Annotation.steps). */
|
|
168
|
+
steps = [];
|
|
169
|
+
stepsPage = "";
|
|
135
170
|
constructor(cfg) {
|
|
136
171
|
if (!cfg.repo || !/^[\w.-]+\/[\w.-]+$/.test(cfg.repo)) throw new Error('PeanutGallery: config.repo must be "owner/name"');
|
|
137
172
|
this.cfg = {
|
|
@@ -175,21 +210,44 @@ ${css}`;
|
|
|
175
210
|
this.root.append(this.highlight, this.pinsLayer, this.toolbar);
|
|
176
211
|
document.body.append(this.host);
|
|
177
212
|
document.addEventListener("mousemove", this.onMouseMove, true);
|
|
213
|
+
document.addEventListener("click", this.recordStep, true);
|
|
178
214
|
document.addEventListener("click", this.onClick, true);
|
|
179
215
|
document.addEventListener("keydown", this.onKey, true);
|
|
180
216
|
window.addEventListener("scroll", this.schedulePins, true);
|
|
181
217
|
window.addEventListener("resize", this.schedulePins);
|
|
218
|
+
watchHistory();
|
|
219
|
+
window.addEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
220
|
+
window.addEventListener("popstate", this.onLocationChange);
|
|
221
|
+
window.addEventListener("hashchange", this.onLocationChange);
|
|
222
|
+
if (typeof MutationObserver !== "undefined") {
|
|
223
|
+
this.observer = new MutationObserver((records) => {
|
|
224
|
+
if (records.some((r) => r.target !== this.host)) this.schedulePins();
|
|
225
|
+
});
|
|
226
|
+
this.observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden", "open", "aria-hidden"], characterData: true });
|
|
227
|
+
}
|
|
182
228
|
this.renderPins();
|
|
183
229
|
this.updateCount();
|
|
184
230
|
return this;
|
|
185
231
|
}
|
|
232
|
+
onLocationChange = () => {
|
|
233
|
+
if (this.destroyed) return;
|
|
234
|
+
this.closeComposer();
|
|
235
|
+
this.schedulePins();
|
|
236
|
+
if (this.panel) this.renderPanel();
|
|
237
|
+
};
|
|
186
238
|
destroy() {
|
|
187
239
|
this.destroyed = true;
|
|
188
240
|
document.removeEventListener("mousemove", this.onMouseMove, true);
|
|
189
241
|
document.removeEventListener("click", this.onClick, true);
|
|
242
|
+
document.removeEventListener("click", this.recordStep, true);
|
|
190
243
|
document.removeEventListener("keydown", this.onKey, true);
|
|
191
244
|
window.removeEventListener("scroll", this.schedulePins, true);
|
|
192
245
|
window.removeEventListener("resize", this.schedulePins);
|
|
246
|
+
window.removeEventListener(LOCATION_EVENT, this.onLocationChange);
|
|
247
|
+
window.removeEventListener("popstate", this.onLocationChange);
|
|
248
|
+
window.removeEventListener("hashchange", this.onLocationChange);
|
|
249
|
+
this.observer?.disconnect();
|
|
250
|
+
if (this.raf) cancelAnimationFrame(this.raf);
|
|
193
251
|
this.host?.remove();
|
|
194
252
|
}
|
|
195
253
|
/** Current annotations (all pages). */
|
|
@@ -226,7 +284,7 @@ ${css}`;
|
|
|
226
284
|
if (this.panel) this.renderPanel();
|
|
227
285
|
}
|
|
228
286
|
pageUrl() {
|
|
229
|
-
return location.
|
|
287
|
+
return normalizePageUrl(location.href);
|
|
230
288
|
}
|
|
231
289
|
// ---------- toolbar ----------
|
|
232
290
|
buildToolbar() {
|
|
@@ -291,6 +349,30 @@ ${css}`;
|
|
|
291
349
|
this.setMode("idle");
|
|
292
350
|
this.openComposer(target, e.clientX, e.clientY);
|
|
293
351
|
};
|
|
352
|
+
/**
|
|
353
|
+
* Remember what the reviewer clicks while using the app (not while placing a note), so a
|
|
354
|
+
* note inside a modal or a menu carries the clicks that opened it. Recorded in the capture
|
|
355
|
+
* phase, before the app re-renders. Only the element is kept, never what was typed.
|
|
356
|
+
*/
|
|
357
|
+
recordStep = (e) => {
|
|
358
|
+
if (this.mode === "commenting" || this.destroyed) return;
|
|
359
|
+
if (this.host && e.composedPath().includes(this.host)) return;
|
|
360
|
+
const raw = e.target;
|
|
361
|
+
if (!(raw instanceof Element)) return;
|
|
362
|
+
const target = raw.closest('button, a, summary, label, select, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"], [role="checkbox"], [data-testid]') ?? raw;
|
|
363
|
+
const here = this.pageUrl();
|
|
364
|
+
if (here !== this.stepsPage) {
|
|
365
|
+
this.steps = [];
|
|
366
|
+
this.stepsPage = here;
|
|
367
|
+
}
|
|
368
|
+
const anchor = (0, import_peanut_gallery_core.createAnchor)(target, e.clientX, e.clientY);
|
|
369
|
+
this.steps.push({ ...anchor, context: void 0, textFingerprint: anchor.textFingerprint.slice(0, 60) });
|
|
370
|
+
if (this.steps.length > import_peanut_gallery_core.MAX_STEPS) this.steps.shift();
|
|
371
|
+
};
|
|
372
|
+
currentSteps() {
|
|
373
|
+
if (this.stepsPage !== this.pageUrl() || !this.steps.length) return void 0;
|
|
374
|
+
return this.steps.map((st) => ({ ...st }));
|
|
375
|
+
}
|
|
294
376
|
onKey = (e) => {
|
|
295
377
|
if (e.key === "Escape") {
|
|
296
378
|
if (this.composer) this.closeComposer();
|
|
@@ -329,7 +411,8 @@ ${css}`;
|
|
|
329
411
|
pageUrl: this.pageUrl(),
|
|
330
412
|
viewport: { width: innerWidth, height: innerHeight },
|
|
331
413
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
332
|
-
author: this.session.author
|
|
414
|
+
author: this.session.author,
|
|
415
|
+
...this.currentSteps() ? { steps: this.currentSteps() } : {}
|
|
333
416
|
});
|
|
334
417
|
}
|
|
335
418
|
this.save();
|
|
@@ -367,31 +450,38 @@ ${css}`;
|
|
|
367
450
|
this.renderPins();
|
|
368
451
|
});
|
|
369
452
|
};
|
|
453
|
+
pinsSignature = "";
|
|
370
454
|
renderPins() {
|
|
371
|
-
this.pinsLayer.replaceChildren();
|
|
372
455
|
const here = this.pageUrl();
|
|
456
|
+
const placed = [];
|
|
373
457
|
this.session.annotations.forEach((a, i) => {
|
|
374
|
-
if (a.pageUrl !== here) return;
|
|
458
|
+
if (normalizePageUrl(a.pageUrl) !== here) return;
|
|
375
459
|
const { el: target, confidence } = (0, import_peanut_gallery_core.resolveAnchor)(a.anchor, document);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
y = a.anchor.rect.y + a.anchor.rect.height * a.anchor.offset.y;
|
|
384
|
-
}
|
|
385
|
-
const pin = el("div", "pg-pin" + (confidence < 0.5 ? " pg-low" : ""), String(i + 1));
|
|
386
|
-
pin.title = a.text;
|
|
387
|
-
pin.style.left = `${x}px`;
|
|
388
|
-
pin.style.top = `${y}px`;
|
|
389
|
-
pin.onclick = (ev) => {
|
|
390
|
-
ev.stopPropagation();
|
|
391
|
-
this.openComposer(target ?? document.body, x - scrollX, y - scrollY, a);
|
|
392
|
-
};
|
|
393
|
-
this.pinsLayer.append(pin);
|
|
460
|
+
if (!target || confidence < 0.4) return;
|
|
461
|
+
const vis = target.checkVisibility;
|
|
462
|
+
if (typeof vis === "function" && !vis.call(target)) return;
|
|
463
|
+
const r = target.getBoundingClientRect();
|
|
464
|
+
const x = Math.round(r.left + scrollX + r.width * a.anchor.offset.x);
|
|
465
|
+
const y = Math.round(r.top + scrollY + r.height * a.anchor.offset.y);
|
|
466
|
+
placed.push({ a, n: i + 1, x, y, low: confidence < 0.5 });
|
|
394
467
|
});
|
|
468
|
+
const sig = placed.map((p) => `${p.a.id}:${p.n}:${p.x}:${p.y}:${p.low}:${p.a.text}`).join("|");
|
|
469
|
+
if (sig === this.pinsSignature && this.pinsLayer.childElementCount === placed.length) return;
|
|
470
|
+
this.pinsSignature = sig;
|
|
471
|
+
this.pinsLayer.replaceChildren(
|
|
472
|
+
...placed.map(({ a, n, x, y, low }) => {
|
|
473
|
+
const pin = el("div", "pg-pin" + (low ? " pg-low" : ""), String(n));
|
|
474
|
+
pin.title = a.text;
|
|
475
|
+
pin.style.left = `${x}px`;
|
|
476
|
+
pin.style.top = `${y}px`;
|
|
477
|
+
pin.onclick = (ev) => {
|
|
478
|
+
ev.stopPropagation();
|
|
479
|
+
const target = (0, import_peanut_gallery_core.resolveAnchor)(a.anchor, document).el ?? document.body;
|
|
480
|
+
this.openComposer(target, x - scrollX, y - scrollY, a);
|
|
481
|
+
};
|
|
482
|
+
return pin;
|
|
483
|
+
})
|
|
484
|
+
);
|
|
395
485
|
}
|
|
396
486
|
// ---------- panel ----------
|
|
397
487
|
togglePanel() {
|
|
@@ -418,7 +508,7 @@ ${css}`;
|
|
|
418
508
|
const text = el("div", "pg-text");
|
|
419
509
|
if (a.kind) text.append(el("span", "pg-kind", a.kind));
|
|
420
510
|
text.append(`${i + 1}. ${a.text}`);
|
|
421
|
-
const meta = el("div", "pg-meta", `${a.pageUrl === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
511
|
+
const meta = el("div", "pg-meta", `${normalizePageUrl(a.pageUrl) === here ? "this page" : pathOf(a.pageUrl)} \xB7 ${a.anchor.tag}${a.anchor.textFingerprint ? ` \u201C${a.anchor.textFingerprint.slice(0, 30)}\u201D` : ""}`);
|
|
422
512
|
item.append(text, meta);
|
|
423
513
|
p.append(item);
|
|
424
514
|
});
|
package/dist/react.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixelatlarge/peanut-gallery-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Drop-in annotation layer for hosted prototypes. Comments attach to DOM elements and submit to GitHub Issues.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"prototype",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@pixelatlarge/peanut-gallery-core": "0.1.
|
|
52
|
+
"@pixelatlarge/peanut-gallery-core": "0.1.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": ">=17"
|