@pixelatlarge/peanut-gallery-widget 0.0.0-stage → 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.
@@ -0,0 +1,114 @@
1
+ import { AnnotationKind, Theme, ReviewSession, Annotation } from '@pixelatlarge/peanut-gallery-core';
2
+ export { Annotation, AnnotationKind, ReviewSession } from '@pixelatlarge/peanut-gallery-core';
3
+
4
+ type SubmitResult = void | {
5
+ issueUrl?: string;
6
+ keep?: boolean;
7
+ };
8
+ interface PeanutGalleryConfig {
9
+ /** GitHub repo as "owner/name". Required. */
10
+ repo: string;
11
+ /**
12
+ * How to submit:
13
+ * - "issue-url": open a prefilled github.com/.../issues/new tab (zero infra, reviewer needs GitHub access)
14
+ * - "relay": POST the session to `relayUrl`, which creates the issue server-side
15
+ * - "auto" (default): relay if `relayUrl` is set, otherwise issue-url
16
+ */
17
+ mode?: 'issue-url' | 'relay' | 'auto';
18
+ relayUrl?: string;
19
+ /** Labels to apply. Defaults to ["peanut-gallery", "peanut-gallery:feedback"]. */
20
+ labels?: string[];
21
+ /** Reviewer name; if omitted the widget asks once and remembers it. */
22
+ author?: string;
23
+ /** Base URL of the prototype, defaults to location.origin. */
24
+ prototypeUrl?: string;
25
+ /**
26
+ * When to show the widget:
27
+ * - "always" (default)
28
+ * - "query": only when the URL has ?peanut (sticky for the session)
29
+ */
30
+ activation?: 'always' | 'query';
31
+ /** Which annotation kinds to offer. */
32
+ kinds?: AnnotationKind[];
33
+ /** Override any colour or font role. Defaults to the Conifer / Prep / Wasabi palette. */
34
+ theme?: Partial<Theme>;
35
+ /**
36
+ * Load the default fonts (Manrope, Young Serif, JetBrains Mono) from Google Fonts into the page.
37
+ * Off by default so the widget never adds third-party requests to a prototype unasked;
38
+ * without it the widget uses those fonts if the page already has them, else the system UI font.
39
+ */
40
+ loadFonts?: boolean;
41
+ /**
42
+ * Handle submission yourself instead of GitHub (e.g. send to your own backend, or a demo).
43
+ * Overrides `mode`. Resolve to clear the notes; return `{ issueUrl }` to link to the result,
44
+ * or `{ keep: true }` to leave the notes on the page. Throw to show an error and keep them.
45
+ */
46
+ onSubmit?: (session: ReviewSession) => SubmitResult | Promise<SubmitResult>;
47
+ /** Start from these notes instead of the ones saved in this browser (e.g. a demo or a test). */
48
+ session?: ReviewSession;
49
+ }
50
+ declare class PeanutGalleryWidget {
51
+ private cfg;
52
+ private host;
53
+ private root;
54
+ private session;
55
+ private mode;
56
+ private hoverEl;
57
+ private highlight;
58
+ private pinsLayer;
59
+ private panel;
60
+ private composer;
61
+ private toolbar;
62
+ private raf;
63
+ private destroyed;
64
+ private observer?;
65
+ /** The reviewer's recent clicks on this page (see Annotation.steps). */
66
+ private steps;
67
+ private stepsPage;
68
+ constructor(cfg: PeanutGalleryConfig);
69
+ get storageKey(): string;
70
+ /** Mount the widget. Safe to call once DOM is ready. */
71
+ mount(): this;
72
+ private onLocationChange;
73
+ destroy(): void;
74
+ /** Current annotations (all pages). */
75
+ getAnnotations(): Annotation[];
76
+ private loadSession;
77
+ private freshSession;
78
+ private save;
79
+ private pageUrl;
80
+ private buildToolbar;
81
+ private updateCount;
82
+ private setMode;
83
+ private isOwn;
84
+ private onMouseMove;
85
+ private hideHighlight;
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;
94
+ private onKey;
95
+ private openComposer;
96
+ private closeComposer;
97
+ private schedulePins;
98
+ private pinsSignature;
99
+ private renderPins;
100
+ private togglePanel;
101
+ private renderPanel;
102
+ private effectiveMode;
103
+ private ensureAuthor;
104
+ private submit;
105
+ /** Clear the submitted notes and redraw: pins, count and the panel. */
106
+ private resetAfterSubmit;
107
+ private toast;
108
+ }
109
+
110
+ /** Initialize (idempotent). Returns the widget instance. */
111
+ declare function init(config: PeanutGalleryConfig): PeanutGalleryWidget;
112
+ declare function destroy(): void;
113
+
114
+ export { type PeanutGalleryConfig, PeanutGalleryWidget, destroy, init };
@@ -0,0 +1,114 @@
1
+ import { AnnotationKind, Theme, ReviewSession, Annotation } from '@pixelatlarge/peanut-gallery-core';
2
+ export { Annotation, AnnotationKind, ReviewSession } from '@pixelatlarge/peanut-gallery-core';
3
+
4
+ type SubmitResult = void | {
5
+ issueUrl?: string;
6
+ keep?: boolean;
7
+ };
8
+ interface PeanutGalleryConfig {
9
+ /** GitHub repo as "owner/name". Required. */
10
+ repo: string;
11
+ /**
12
+ * How to submit:
13
+ * - "issue-url": open a prefilled github.com/.../issues/new tab (zero infra, reviewer needs GitHub access)
14
+ * - "relay": POST the session to `relayUrl`, which creates the issue server-side
15
+ * - "auto" (default): relay if `relayUrl` is set, otherwise issue-url
16
+ */
17
+ mode?: 'issue-url' | 'relay' | 'auto';
18
+ relayUrl?: string;
19
+ /** Labels to apply. Defaults to ["peanut-gallery", "peanut-gallery:feedback"]. */
20
+ labels?: string[];
21
+ /** Reviewer name; if omitted the widget asks once and remembers it. */
22
+ author?: string;
23
+ /** Base URL of the prototype, defaults to location.origin. */
24
+ prototypeUrl?: string;
25
+ /**
26
+ * When to show the widget:
27
+ * - "always" (default)
28
+ * - "query": only when the URL has ?peanut (sticky for the session)
29
+ */
30
+ activation?: 'always' | 'query';
31
+ /** Which annotation kinds to offer. */
32
+ kinds?: AnnotationKind[];
33
+ /** Override any colour or font role. Defaults to the Conifer / Prep / Wasabi palette. */
34
+ theme?: Partial<Theme>;
35
+ /**
36
+ * Load the default fonts (Manrope, Young Serif, JetBrains Mono) from Google Fonts into the page.
37
+ * Off by default so the widget never adds third-party requests to a prototype unasked;
38
+ * without it the widget uses those fonts if the page already has them, else the system UI font.
39
+ */
40
+ loadFonts?: boolean;
41
+ /**
42
+ * Handle submission yourself instead of GitHub (e.g. send to your own backend, or a demo).
43
+ * Overrides `mode`. Resolve to clear the notes; return `{ issueUrl }` to link to the result,
44
+ * or `{ keep: true }` to leave the notes on the page. Throw to show an error and keep them.
45
+ */
46
+ onSubmit?: (session: ReviewSession) => SubmitResult | Promise<SubmitResult>;
47
+ /** Start from these notes instead of the ones saved in this browser (e.g. a demo or a test). */
48
+ session?: ReviewSession;
49
+ }
50
+ declare class PeanutGalleryWidget {
51
+ private cfg;
52
+ private host;
53
+ private root;
54
+ private session;
55
+ private mode;
56
+ private hoverEl;
57
+ private highlight;
58
+ private pinsLayer;
59
+ private panel;
60
+ private composer;
61
+ private toolbar;
62
+ private raf;
63
+ private destroyed;
64
+ private observer?;
65
+ /** The reviewer's recent clicks on this page (see Annotation.steps). */
66
+ private steps;
67
+ private stepsPage;
68
+ constructor(cfg: PeanutGalleryConfig);
69
+ get storageKey(): string;
70
+ /** Mount the widget. Safe to call once DOM is ready. */
71
+ mount(): this;
72
+ private onLocationChange;
73
+ destroy(): void;
74
+ /** Current annotations (all pages). */
75
+ getAnnotations(): Annotation[];
76
+ private loadSession;
77
+ private freshSession;
78
+ private save;
79
+ private pageUrl;
80
+ private buildToolbar;
81
+ private updateCount;
82
+ private setMode;
83
+ private isOwn;
84
+ private onMouseMove;
85
+ private hideHighlight;
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;
94
+ private onKey;
95
+ private openComposer;
96
+ private closeComposer;
97
+ private schedulePins;
98
+ private pinsSignature;
99
+ private renderPins;
100
+ private togglePanel;
101
+ private renderPanel;
102
+ private effectiveMode;
103
+ private ensureAuthor;
104
+ private submit;
105
+ /** Clear the submitted notes and redraw: pins, count and the panel. */
106
+ private resetAfterSubmit;
107
+ private toast;
108
+ }
109
+
110
+ /** Initialize (idempotent). Returns the widget instance. */
111
+ declare function init(config: PeanutGalleryConfig): PeanutGalleryWidget;
112
+ declare function destroy(): void;
113
+
114
+ export { type PeanutGalleryConfig, PeanutGalleryWidget, destroy, init };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ PeanutGalleryWidget,
3
+ destroy,
4
+ init
5
+ } from "./chunk-X4WAQVYW.js";
6
+ export {
7
+ PeanutGalleryWidget,
8
+ destroy,
9
+ init
10
+ };
@@ -0,0 +1,75 @@
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
+ `).slice(1).join(`
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
+ :host { all: initial; }
8
+ * { box-sizing: border-box; font-family: var(--pg-font-body); font-size: 13px; line-height: 1.4; color: var(--pg-text); }
9
+ .pg-hidden { display: none !important; }
10
+
11
+ .pg-toolbar {
12
+ position: fixed; right: 16px; bottom: 16px; z-index: 2147483000;
13
+ display: flex; align-items: center; gap: 6px;
14
+ background: var(--pg-toolbar); border-radius: 999px; padding: 6px 8px 6px 14px;
15
+ box-shadow: 0 10px 28px rgba(21, 42, 74, .28); user-select: none;
16
+ }
17
+ .pg-toolbar .pg-brand { font-weight: 700; color: var(--pg-on-toolbar); margin-right: 4px; letter-spacing: .01em; }
18
+ .pg-btn {
19
+ appearance: none; border: 0; cursor: pointer; border-radius: 999px; padding: 6px 12px;
20
+ background: var(--pg-toolbar-button); color: var(--pg-on-toolbar); font-weight: 600;
21
+ }
22
+ .pg-btn:hover { background: var(--pg-toolbar-button-hover); }
23
+ .pg-btn.pg-primary { background: var(--pg-primary); color: var(--pg-on-primary); }
24
+ .pg-btn.pg-primary:hover { background: var(--pg-primary-hover); }
25
+ .pg-btn.pg-active, .pg-btn.pg-active:hover { background: var(--pg-accent); color: var(--pg-on-accent); }
26
+ .pg-btn:disabled { opacity: .5; cursor: default; }
27
+ .pg-btn:focus-visible, .pg-pin:focus-visible { outline: 2px solid var(--pg-accent); outline-offset: 2px; }
28
+ .pg-count { background: var(--pg-count); color: var(--pg-on-count); border-radius: 999px; padding: 1px 7px; font-weight: 700; margin-left: 6px; }
29
+
30
+ .pg-highlight {
31
+ position: absolute; z-index: 2147482000; pointer-events: none;
32
+ outline: 2px solid var(--pg-highlight); outline-offset: 2px; background: var(--pg-highlight-tint); border-radius: 3px;
33
+ }
34
+ .pg-tag {
35
+ position: absolute; top: -23px; left: -2px; background: var(--pg-highlight); color: var(--pg-on-primary); font-size: 11px;
36
+ padding: 2px 6px; border-radius: 4px; white-space: nowrap; font-family: var(--pg-font-mono);
37
+ }
38
+
39
+ .pg-pin {
40
+ position: absolute; z-index: 2147482500; width: 24px; height: 24px; margin: -12px 0 0 -12px;
41
+ border-radius: 999px 999px 999px 2px; background: var(--pg-accent); color: var(--pg-on-accent); font-weight: 700; font-size: 12px;
42
+ display: flex; align-items: center; justify-content: center; cursor: pointer;
43
+ box-shadow: 0 2px 8px rgba(21, 42, 74, .35); border: 2px solid var(--pg-surface);
44
+ }
45
+ .pg-pin.pg-low { background: var(--pg-pin-low); }
46
+ .pg-pin:hover { transform: scale(1.1); }
47
+
48
+ .pg-composer, .pg-panel {
49
+ position: absolute; z-index: 2147483100; background: var(--pg-surface); border-radius: 12px;
50
+ border: 1px solid var(--pg-line); box-shadow: 0 14px 40px rgba(21, 42, 74, .22); padding: 12px; width: 300px;
51
+ }
52
+ .pg-composer textarea {
53
+ width: 100%; min-height: 80px; resize: vertical; border: 1px solid var(--pg-line); border-radius: 8px; padding: 8px;
54
+ font-size: 13px; background: var(--pg-surface);
55
+ }
56
+ .pg-composer textarea:focus { outline: 2px solid var(--pg-primary); border-color: transparent; }
57
+ .pg-row { display: flex; gap: 6px; align-items: center; margin-top: 8px; }
58
+ .pg-row .pg-spacer { flex: 1; }
59
+ .pg-composer select { border: 1px solid var(--pg-line); border-radius: 8px; padding: 5px 6px; background: var(--pg-surface); }
60
+ .pg-meta { color: var(--pg-muted); font-size: 11px; margin-bottom: 6px; font-family: var(--pg-font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
61
+ .pg-btn.pg-ghost { background: var(--pg-ghost); color: var(--pg-text); }
62
+ .pg-btn.pg-ghost:hover { background: var(--pg-ghost-hover); }
63
+ .pg-btn.pg-danger { background: var(--pg-danger); color: var(--pg-on-danger); }
64
+
65
+ .pg-panel { position: fixed; right: 16px; bottom: 64px; width: 340px; max-height: 70vh; overflow: auto; }
66
+ .pg-panel h3 { margin: 0 0 8px; font-family: var(--pg-font-display); font-weight: 400; font-size: 18px; color: var(--pg-heading); }
67
+ .pg-item { border-top: 1px solid var(--pg-line); padding: 8px 0; }
68
+ .pg-item .pg-text { white-space: pre-wrap; }
69
+ .pg-item .pg-meta { margin: 2px 0 0; }
70
+ .pg-item .pg-kind { display: inline-block; background: var(--pg-chip); color: var(--pg-on-chip); border-radius: 4px; padding: 0 5px; font-size: 11px; font-weight: 600; margin-right: 4px; }
71
+ .pg-empty { color: var(--pg-muted); padding: 12px 0; }
72
+ .pg-link { color: var(--pg-link); }
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 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);})();