pipeup 0.7.0-beta.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cli/check.mjs +267 -0
- package/cli/pipeup.mjs +15 -0
- package/dist/pipeup.core.js +1 -1
- package/dist/pipeup.esm.js +1 -1
- package/dist/pipeup.min.js +1 -1
- package/dist/types/core.d.ts +1 -1
- package/dist/types/ui/styles.d.ts +1 -1
- package/package.json +1 -1
- package/skills/pipeup-add/SKILL.md +2 -2
- package/skills/pipeup-apply/SKILL.md +1 -1
- package/skills/pipeup-integrate/SKILL.md +4 -4
- package/skills/pipeup-send-to-git-setup/SKILL.md +1 -1
- package/skills/pipeup-summarise/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Comments and feedback right on any HTML page: documents, slide decks and whole sites.
|
|
4
4
|
|
|
5
|
-
> **Alpha (0.7.0
|
|
5
|
+
> **Alpha (0.7.0).** The API and the stored comment format may change before 1.0; pin an exact version.
|
|
6
6
|
> Website and Try pages: https://pipeup-ai.github.io/pipeup/ · Source: https://github.com/pipeup-ai/pipeup
|
|
7
7
|
|
|
8
8
|
Pipeup: signed comment operations, threads with one-level replies, anchors that survive page changes,
|
|
@@ -21,7 +21,7 @@ As a classic script (also works from `file://`), it mounts itself on pages that
|
|
|
21
21
|
```html
|
|
22
22
|
<html data-pipeup-doc="<id>:<key>">
|
|
23
23
|
...
|
|
24
|
-
<script src="https://cdn.jsdelivr.net/npm/pipeup@0.7.0
|
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/pipeup@0.7.0/dist/pipeup.min.js" integrity="sha384-…" crossorigin="anonymous" defer></script>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Each release's notes on GitHub give the `integrity` value
|
package/cli/check.mjs
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// pipeup check <page.html>: loads a page with and without Pipeup at phone, tablet and desktop widths and reports what
|
|
2
|
+
// Pipeup changes or covers, and what the page could do better for it. It uses Playwright (yours; Pipeup depends on none).
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const WIDTHS = [375, 768, 1440];
|
|
7
|
+
const SHIFT_PX = 0.5;
|
|
8
|
+
/** Requests that load Pipeup or one of its add-ons: blocked for the page as it is without Pipeup. */
|
|
9
|
+
const PIPEUP_SCRIPT = /(?:^|\/)pipeup[^/]*\.js(?:\?|$)|\/@pipeup\/|\/pipeup@/i;
|
|
10
|
+
|
|
11
|
+
async function playwright() {
|
|
12
|
+
for (const name of ["playwright", "@playwright/test"]) {
|
|
13
|
+
try {
|
|
14
|
+
return await import(name);
|
|
15
|
+
} catch {
|
|
16
|
+
// try the next
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw new Error(
|
|
20
|
+
"pipeup check needs Playwright and a browser: npm i -D playwright && npx playwright install chromium",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Every visible element's box (without Pipeup's own), in document order, so two loads can be compared. */
|
|
25
|
+
const boxes = () =>
|
|
26
|
+
[...document.body.querySelectorAll("*")]
|
|
27
|
+
.filter((e) => e.tagName !== "PIPEUP-ROOT" && !e.closest("pipeup-root"))
|
|
28
|
+
.slice(0, 4000)
|
|
29
|
+
.map((e) => {
|
|
30
|
+
const r = e.getBoundingClientRect();
|
|
31
|
+
const label = e.tagName.toLowerCase() + (e.id ? `#${e.id}` : "");
|
|
32
|
+
return {
|
|
33
|
+
label,
|
|
34
|
+
x: r.x + scrollX,
|
|
35
|
+
y: r.y + scrollY,
|
|
36
|
+
w: r.width,
|
|
37
|
+
h: r.height,
|
|
38
|
+
shown: r.width > 0 && r.height > 0,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** What is checked once, with Pipeup running at the widest width. */
|
|
43
|
+
const inspect = async () => {
|
|
44
|
+
const w = window;
|
|
45
|
+
const out = {
|
|
46
|
+
scripts: 0,
|
|
47
|
+
roots: document.querySelectorAll("pipeup-root").length,
|
|
48
|
+
blocks: 0,
|
|
49
|
+
doc: document.documentElement.hasAttribute("data-pipeup-doc"),
|
|
50
|
+
addons: [],
|
|
51
|
+
appearance: null,
|
|
52
|
+
tabbable: false,
|
|
53
|
+
agents: document.documentElement.getAttribute("data-pipeup-agents") || "",
|
|
54
|
+
viewport: Boolean(document.querySelector('meta[name="viewport"]')),
|
|
55
|
+
};
|
|
56
|
+
out.scripts = [...document.querySelectorAll("script[src]")].filter(
|
|
57
|
+
(s) =>
|
|
58
|
+
/(?:^|\/)pipeup(?:\.min|\.esm)?(?:@[^/]*)?\.js|\/pipeup@[^/]*\//i.test(s.src) &&
|
|
59
|
+
!/@pipeup\//i.test(s.src),
|
|
60
|
+
).length;
|
|
61
|
+
const marked = (e) => e.closest("[data-pipeup-id]") !== null;
|
|
62
|
+
out.blocks = [...document.querySelectorAll("svg, canvas, img, figure, table, video")].filter(
|
|
63
|
+
(e) => !e.closest("pipeup-root") && !marked(e) && e.getBoundingClientRect().width > 40,
|
|
64
|
+
).length;
|
|
65
|
+
try {
|
|
66
|
+
const pu = await w.Pipeup.mount();
|
|
67
|
+
out.addons = (w.Pipeup.addons?.() ?? []).map((a) => ({
|
|
68
|
+
id: a.id,
|
|
69
|
+
state: a.state,
|
|
70
|
+
reason: a.reason ?? "",
|
|
71
|
+
says: a.network?.says ?? "",
|
|
72
|
+
}));
|
|
73
|
+
out.appearance = pu.appearance?.() ?? null;
|
|
74
|
+
} catch {
|
|
75
|
+
out.addons = null;
|
|
76
|
+
}
|
|
77
|
+
const control = document.querySelector("pipeup-root")?.shadowRoot?.querySelector(".launch .mode");
|
|
78
|
+
out.tabbable = Boolean(control) && control.tabIndex >= 0;
|
|
79
|
+
return out;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** The control's box and the page's interactive and text boxes it could cover, at the current width. */
|
|
83
|
+
const overlaps = () => {
|
|
84
|
+
const root = document.querySelector("pipeup-root");
|
|
85
|
+
const mode = root?.shadowRoot?.querySelector(".launch .mode");
|
|
86
|
+
if (!mode) return { control: null, hits: [] };
|
|
87
|
+
const c = mode.getBoundingClientRect();
|
|
88
|
+
const hit = (r) =>
|
|
89
|
+
r.width > 0 &&
|
|
90
|
+
r.height > 0 &&
|
|
91
|
+
r.left < c.right &&
|
|
92
|
+
r.right > c.left &&
|
|
93
|
+
r.top < c.bottom &&
|
|
94
|
+
r.bottom > c.top;
|
|
95
|
+
const hits = [];
|
|
96
|
+
for (const e of document.body.querySelectorAll("*")) {
|
|
97
|
+
if (e.closest("pipeup-root")) continue;
|
|
98
|
+
const r = e.getBoundingClientRect();
|
|
99
|
+
if (!hit(r)) continue;
|
|
100
|
+
const style = getComputedStyle(e);
|
|
101
|
+
if (style.visibility === "hidden" || style.display === "none" || style.pointerEvents === "none") continue;
|
|
102
|
+
const interactive = e.matches(
|
|
103
|
+
"a[href], button, input, select, textarea, summary, [role=button], [role=link], [tabindex]:not([tabindex='-1'])",
|
|
104
|
+
);
|
|
105
|
+
const text = !interactive && [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim());
|
|
106
|
+
if (interactive || text)
|
|
107
|
+
hits.push({ what: e.tagName.toLowerCase() + (e.id ? `#${e.id}` : ""), interactive });
|
|
108
|
+
}
|
|
109
|
+
return { control: { x: c.x, y: c.y, w: c.width, h: c.height }, hits: hits.slice(0, 5) };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Runs the check; resolves to { results: [{ status, what, detail }], ok }. */
|
|
113
|
+
export async function check({ page, widths = WIDTHS, log = () => {} }) {
|
|
114
|
+
const url = /^https?:|^file:/i.test(page) ? page : pathToFileURL(resolve(page)).href;
|
|
115
|
+
const { chromium } = await playwright();
|
|
116
|
+
const browser = await chromium.launch();
|
|
117
|
+
const results = [];
|
|
118
|
+
const add = (status, what, detail = "") => results.push({ status, what, detail });
|
|
119
|
+
try {
|
|
120
|
+
for (const width of widths) {
|
|
121
|
+
const without = await (await browser.newContext({ viewport: { width, height: 900 } })).newPage();
|
|
122
|
+
await without.route("**/*", (route) =>
|
|
123
|
+
PIPEUP_SCRIPT.test(route.request().url()) ? route.abort() : route.continue(),
|
|
124
|
+
);
|
|
125
|
+
await without.goto(url);
|
|
126
|
+
await without.waitForLoadState("load");
|
|
127
|
+
const before = await without.evaluate(boxes);
|
|
128
|
+
await without.context().close();
|
|
129
|
+
|
|
130
|
+
const withp = await (await browser.newContext({ viewport: { width, height: 900 } })).newPage();
|
|
131
|
+
const said = [];
|
|
132
|
+
withp.on("console", (m) => said.push(m.text()));
|
|
133
|
+
await withp.goto(url);
|
|
134
|
+
await withp.waitForLoadState("load");
|
|
135
|
+
let started = await withp.waitForSelector("pipeup-root", { state: "attached", timeout: 3000 }).then(
|
|
136
|
+
() => true,
|
|
137
|
+
() => false,
|
|
138
|
+
);
|
|
139
|
+
// A page that mounts Pipeup itself (data-pipeup-auto="off") is started the way its author would.
|
|
140
|
+
if (!started && (await withp.evaluate(() => typeof window.Pipeup?.mount === "function")))
|
|
141
|
+
started = await withp.evaluate(() =>
|
|
142
|
+
window.Pipeup.mount().then(
|
|
143
|
+
() => true,
|
|
144
|
+
() => false,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
if (!started) {
|
|
148
|
+
add(
|
|
149
|
+
"fail",
|
|
150
|
+
`Pipeup starts (${width}px)`,
|
|
151
|
+
"no Pipeup appeared on the page: is its script included, and does it need data-pipeup-doc?",
|
|
152
|
+
);
|
|
153
|
+
await withp.context().close();
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
await withp.waitForTimeout(400);
|
|
157
|
+
const after = await withp.evaluate(boxes);
|
|
158
|
+
const moved = [];
|
|
159
|
+
if (before.length !== after.length)
|
|
160
|
+
moved.push(`the page has ${after.length} elements with Pipeup and ${before.length} without`);
|
|
161
|
+
else
|
|
162
|
+
for (let i = 0; i < before.length; i++) {
|
|
163
|
+
const a = before[i];
|
|
164
|
+
const b = after[i];
|
|
165
|
+
if (
|
|
166
|
+
a.shown &&
|
|
167
|
+
b.shown &&
|
|
168
|
+
Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y), Math.abs(a.w - b.w), Math.abs(a.h - b.h)) >
|
|
169
|
+
SHIFT_PX
|
|
170
|
+
)
|
|
171
|
+
moved.push(
|
|
172
|
+
`${a.label} moved by ${Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y)).toFixed(1)}px`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (moved.length)
|
|
176
|
+
add(
|
|
177
|
+
"fail",
|
|
178
|
+
`Pipeup changes nothing on the page (${width}px)`,
|
|
179
|
+
`${moved.length} difference${moved.length === 1 ? "" : "s"}: ${moved.slice(0, 3).join("; ")}`,
|
|
180
|
+
);
|
|
181
|
+
else add("pass", `Pipeup changes nothing on the page (${width}px)`);
|
|
182
|
+
|
|
183
|
+
const covered = await withp.evaluate(overlaps);
|
|
184
|
+
const touching = covered.hits.filter((h) => h.interactive);
|
|
185
|
+
if (touching.length)
|
|
186
|
+
add(
|
|
187
|
+
"fail",
|
|
188
|
+
`Pipeup's button covers nothing you use (${width}px)`,
|
|
189
|
+
`it sits over ${touching.map((h) => h.what).join(", ")}`,
|
|
190
|
+
);
|
|
191
|
+
else if (covered.hits.length)
|
|
192
|
+
add(
|
|
193
|
+
"warn",
|
|
194
|
+
`Pipeup's button covers nothing you use (${width}px)`,
|
|
195
|
+
`it sits over text in ${covered.hits.map((h) => h.what).join(", ")}`,
|
|
196
|
+
);
|
|
197
|
+
else add("pass", `Pipeup's button covers nothing you use (${width}px)`);
|
|
198
|
+
|
|
199
|
+
if (width === widths[widths.length - 1]) {
|
|
200
|
+
const o = await withp.evaluate(inspect);
|
|
201
|
+
if (o.scripts > 1 || o.roots > 1)
|
|
202
|
+
add(
|
|
203
|
+
"fail",
|
|
204
|
+
"Pipeup is included once",
|
|
205
|
+
`the page loads Pipeup ${Math.max(o.scripts, o.roots)} times: remove the extra script`,
|
|
206
|
+
);
|
|
207
|
+
else add("pass", "Pipeup is included once");
|
|
208
|
+
add(
|
|
209
|
+
o.doc ? "pass" : "warn",
|
|
210
|
+
"The page has a document identity",
|
|
211
|
+
o.doc
|
|
212
|
+
? ""
|
|
213
|
+
: "no data-pipeup-doc on <html>: copies of this file on other computers won't share comments",
|
|
214
|
+
);
|
|
215
|
+
add(
|
|
216
|
+
o.blocks ? "warn" : "pass",
|
|
217
|
+
"Charts, images and tables have stable names",
|
|
218
|
+
o.blocks
|
|
219
|
+
? `${o.blocks} without data-pipeup-id: comments on them may lose their place when the page changes`
|
|
220
|
+
: "",
|
|
221
|
+
);
|
|
222
|
+
add(o.tabbable ? "pass" : "fail", "Pipeup's button can be reached with the keyboard");
|
|
223
|
+
add(
|
|
224
|
+
o.viewport ? "pass" : "warn",
|
|
225
|
+
"The page suits phones",
|
|
226
|
+
o.viewport
|
|
227
|
+
? ""
|
|
228
|
+
: 'no <meta name="viewport" content="width=device-width, initial-scale=1">: phones show the page zoomed out, and Pipeup with it',
|
|
229
|
+
);
|
|
230
|
+
if (o.addons)
|
|
231
|
+
for (const a of o.addons)
|
|
232
|
+
add(a.state === "on" ? "pass" : "warn", `Add-on ${a.id} is ${a.state}`, a.reason || a.says);
|
|
233
|
+
if (o.appearance) {
|
|
234
|
+
const e = o.appearance.effects;
|
|
235
|
+
add(
|
|
236
|
+
e.length || o.appearance.ratio < 3 ? "warn" : "pass",
|
|
237
|
+
`Pipeup looks right on this page (${o.appearance.look} look, from ${o.appearance.source})`,
|
|
238
|
+
e.length
|
|
239
|
+
? `the page's styles on <html> change it and can't be undone from inside: ${e.join("; ")}`
|
|
240
|
+
: "",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (o.agents && !(o.addons ?? []).some((a) => a.id === "agents"))
|
|
244
|
+
add(
|
|
245
|
+
"warn",
|
|
246
|
+
"A listed agent can answer",
|
|
247
|
+
"the page lists an agent (data-pipeup-agents) but has no agents add-on",
|
|
248
|
+
);
|
|
249
|
+
const unreadable = said.find((t) => /couldn't be read|could not be read/i.test(t));
|
|
250
|
+
add(unreadable ? "warn" : "pass", "Saved comments can be read", unreadable ?? "");
|
|
251
|
+
}
|
|
252
|
+
await withp.context().close();
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
await browser.close();
|
|
256
|
+
}
|
|
257
|
+
log(results);
|
|
258
|
+
return { results, ok: !results.some((r) => r.status === "fail") };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The report as a table of words for a person. */
|
|
262
|
+
export function table(results) {
|
|
263
|
+
const mark = { pass: "pass", warn: "WARN", fail: "FAIL" };
|
|
264
|
+
return results
|
|
265
|
+
.map((r) => `${mark[r.status].padEnd(5)} ${r.what}${r.detail ? `\n ${r.detail}` : ""}`)
|
|
266
|
+
.join("\n");
|
|
267
|
+
}
|
package/cli/pipeup.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { check, table } from "./check.mjs";
|
|
7
8
|
import { comments, watch } from "./link.mjs";
|
|
8
9
|
import * as skills from "./skills.mjs";
|
|
9
10
|
|
|
@@ -18,6 +19,10 @@ const USAGE = `pipeup skills install|remove|list [--project] [--agent claude|cod
|
|
|
18
19
|
project, or with --project for this folder only. It says what it will write, asks first (--yes to go ahead without
|
|
19
20
|
asking), and never replaces a skill you changed unless you say --replace.
|
|
20
21
|
|
|
22
|
+
pipeup check <page.html> [--json]
|
|
23
|
+
Loads the page with and without Pipeup at 375, 768 and 1440 px (with Playwright, which you install) and says what
|
|
24
|
+
Pipeup changes or covers, and what the page could do better: pass, WARN or FAIL for each. Exits 1 on any FAIL.
|
|
25
|
+
|
|
21
26
|
pipeup agent [--name <name>]
|
|
22
27
|
Makes this computer's agent key (once) and prints the public key to list on the page:
|
|
23
28
|
<html data-pipeup-agents="<key>">
|
|
@@ -178,6 +183,16 @@ else if (command === "skills") {
|
|
|
178
183
|
} catch (e) {
|
|
179
184
|
fail(e.message);
|
|
180
185
|
}
|
|
186
|
+
} else if (command === "check") {
|
|
187
|
+
const page = args._[0];
|
|
188
|
+
if (!page) fail(`missing the page\n\n${USAGE}`);
|
|
189
|
+
try {
|
|
190
|
+
const { results, ok } = await check({ page });
|
|
191
|
+
console.log(args.json === true ? JSON.stringify({ ok, results }, null, 2) : table(results));
|
|
192
|
+
process.exit(ok ? 0 : 1);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
fail(e.message);
|
|
195
|
+
}
|
|
181
196
|
} else if (command === "comments") {
|
|
182
197
|
const page = args._[0];
|
|
183
198
|
if (!page) fail(`missing the page\n\n${USAGE}`);
|
package/dist/pipeup.core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e){let t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function t(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r}function r(e){return(new TextEncoder).encode(e)}function n(e){if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(n).join(",")+"]";let t=e;return"{"+Object.keys(t).filter(e=>void 0!==t[e]).sort().map(e=>JSON.stringify(e)+":"+n(t[e])).join(",")+"}"}function i(e){return e.replace(/\s+/g," ").trim()}function o(e){let t=i(e),r=2166136261;for(let e=0;e<t.length;e++)r^=t.charCodeAt(e),r=Math.imul(r,16777619)>>>0;return r.toString(16).padStart(8,"0")}var a=/^[A-Za-z0-9_-]{43}$/,s=new Map;async function l(){let t=await crypto.subtle.generateKey({name:"Ed25519"},!1,["sign","verify"]);return{publicKey:e(new Uint8Array(await crypto.subtle.exportKey("raw",t.publicKey))),privateKey:t.privateKey}}async function d(t,r){let n=await crypto.subtle.sign({name:"Ed25519"},t.privateKey,r);return e(new Uint8Array(n))}var u="0123456789ABCDEFGHJKMNPQRSTVWXYZ";Uint8Array.from([48,46,2,1,0,48,5,6,3,43,101,112,4,34,4,32]);var c=["Otter","Fox","Owl","Bear","Rabbit"],h=["Red","Orange","Yellow","Green","Teal","Blue","Purple","Pink","Brown","Grey"];function p(e){let t=2166136261;for(let r=0;r<e.length;r++)t=Math.imul(t^e.charCodeAt(r),16777619);return t=Math.imul(t^t>>>16,2246822507),t=Math.imul(t^t>>>13,3266489909),(t^t>>>16)>>>0}var f=e=>`${h[(e=>Math.floor(p(e)/c.length)%h.length)(e)]} ${c[(e=>p(e)%c.length)(e)]}`,m=e=>{let t=e.name.replace(/^AI assistant · /,"")||f(e.author);return e.alike?`${t} · ${function(e){let t=p(`id:${e}`),r="";for(let e=0;e<4;e++,t>>>=5)r+="0123456789ABCDEFGHJKMNPQRSTVWXYZ"[31&t];return r}(e.author)}`:t},g="AI assistant",y=e=>e===g||e.startsWith(`${g} `),w=1e4,b=2**40,x=2**47,v=1e4,k=/^[0-9a-f]{8}$/,A=/^[A-Za-z0-9_-]{43}$/,E=/^(?:[^\s@<>"]{1,64}@[^\s@<>"]{1,190}\.[^\s@<>"]{2,24}|gh:[A-Za-z0-9-]{1,39})$/,$=/^(?:[A-Za-z0-9_-]{43}|(?:host|gh):[A-Za-z0-9._@-]{1,64})$/,M=/^[A-Za-z0-9_-]{43}$/,S=new Set(["create","reply","edit","delete","resolve","reopen","addressed","react","unreact","seen","contact","uncontact"]),C=/^(?:\p{Regional_Indicator}{2}|[#*0-9]\uFE0F?\u20E3|\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?)*)$/u,N=e=>"string"==typeof e&&C.test(e)&&[...e].length<=16;async function O(e,t){if(e.author!==t.publicKey)throw new Error("pipeup: an op must be signed by its author");return{body:e,sig:await d(t,r(n(e)))}}var P=(e,t)=>"string"==typeof e&&e.length<=t,q=e=>!!e&&"object"==typeof e,j=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0&&e<=1;function T(e){if(!q(e))return!1;let{id:t,path:r,fingerprint:n,snapshot:i,quote:o,point:a,view:s}=e;if(!(P(r,2e3)&&P(i,500)&&"string"==typeof n&&k.test(n)&&(void 0===t||P(t,200)&&""!==t)&&(void 0===o||q(o)&&P(o.exact,v)&&""!==o.exact&&P(o.prefix,64)&&P(o.suffix,64))&&(void 0===a||q(a)&&j(a.x)&&j(a.y))))return!1;if(void 0!==s){if(!q(s)||Array.isArray(s))return!1;let e=Object.entries(s);if(e.length>16||!e.every(([e,t])=>e.length<=64&&P(t,200)))return!1}return!0}function I(e){if(!q(e))return!1;let{body:t,sig:r}=e;if("string"!=typeof r||!q(t)||JSON.stringify({body:t,sig:r}).length>262144)return!1;let n=t;if(1!==n.v||"string"!=typeof n.id||!A.test(n.id)||!S.has(n.kind)||!P(n.doc,64)||""===n.doc||"string"!=typeof n.thread||!A.test(n.thread)||"string"!=typeof n.author||!M.test(n.author)||"string"!=typeof n.name||n.name.length>80||n.name!==n.name.trim()||"number"!=typeof n.clock||!Number.isSafeInteger(n.clock)||n.clock<1||n.clock>b||"number"!=typeof n.at||!Number.isSafeInteger(n.at)||n.at<0||n.at>x||void 0!==n.text&&(!P(n.text,w)||n.text!==n.text.trim())||void 0!==n.target&&("string"!=typeof n.target||!A.test(n.target))||("create"===n.kind||"reply"===n.kind||"edit"===n.kind||"addressed"===n.kind)&&!n.text||("reply"===n.kind||"edit"===n.kind||"delete"===n.kind)&&!n.target)return!1;if(void 0!==n.mentions){if("create"!==n.kind&&"reply"!==n.kind&&"edit"!==n.kind||!Array.isArray(n.mentions)||n.mentions.length>20)return!1;let e=n.text?.length??0;for(let t of n.mentions){if(!q(t)||!Number.isSafeInteger(t.start)||!Number.isSafeInteger(t.end))return!1;let{start:r,end:n,ref:i}=t;if(r<0||n<=r||n>e||"string"!=typeof i||!$.test(i))return!1}}return!(!("contact"!==n.kind||"string"==typeof n.text&&E.test(n.text))||"seen"===n.kind&&!n.target||!("react"!==n.kind&&"unreact"!==n.kind||n.target&&N(n.text))||!("create"!==n.kind||n.id===n.thread&&T(n.anchor)))}function F(i){return async function(r,n,i){if(!a.test(r))return!1;try{if(e(t(r))!==r)return!1;let o=s.get(r);if(!o){s.size>=512&&s.delete(s.keys().next().value),o=crypto.subtle.importKey("raw",t(r),{name:"Ed25519"},!0,["verify"]),s.set(r,o);let e=o;e.catch(()=>{s.get(r)===e&&s.delete(r)})}return await crypto.subtle.verify({name:"Ed25519"},await o,t(i),n)}catch{return!1}}(i.body.author,r(n(i.body)),i.sig)}function D(e,t){return e.body.clock!==t.body.clock?e.body.clock-t.body.clock:e.body.at!==t.body.at?e.body.at-t.body.at:e.body.author!==t.body.author?e.body.author<t.body.author?-1:1:e.body.id!==t.body.id?e.body.id<t.body.id?-1:1:0}function R(e,t,r,n,i,o){let a={id:e,author:t,name:r,text:n,at:i,edited:!1,deleted:!1,replies:[],reactions:[]};return o?.length&&(a.mentions=o),a}async function U(t){let i={...t,id:void 0,thread:"create"===t.kind?void 0:t.thread},o=await crypto.subtle.digest("SHA-256",r(n(i)));return e(new Uint8Array(o))}var K=class{constructor(e){this.doc=e}ops=new Map;clock=0;createAt=new Map;threadAt=new Map;get size(){return this.ops.size}all(){return[...this.ops.values()]}threads(){return function(e){let t=new Map,r=new Map,n=new Map;for(let{body:i}of[...e].sort(D)){if(i.name&&n.set(i.author,i.name),"create"===i.kind){if(t.has(i.id)||!i.anchor||void 0===i.text)continue;let e=R(i.id,i.author,i.name,i.text,i.at,i.mentions);t.set(i.id,{id:i.id,anchor:i.anchor,root:e,resolved:!1}),r.set(i.id,{comment:e,thread:i.id});continue}let e=t.get(i.thread);if(!e)continue;if("resolve"===i.kind||"reopen"===i.kind){e.resolved="resolve"===i.kind,"reopen"===i.kind&&delete e.addressed;continue}if("addressed"===i.kind){void 0!==i.text&&(e.addressed={text:i.text,author:i.author,name:i.name,at:i.at});continue}let o=i.target?r.get(i.target):void 0;if(o&&o.thread===i.thread){if("reply"===i.kind){if(r.has(i.id)||void 0===i.text)continue;let t=R(i.id,i.author,i.name,i.text,i.at,i.mentions);e.root.replies.push(t),r.set(i.id,{comment:t,thread:i.thread});continue}if("seen"===i.kind){let e=o.comment.seenBy??=[];e.includes(i.author)||e.push(i.author);continue}if("react"===i.kind||"unreact"===i.kind){if(void 0===i.text||o.comment.deleted)continue;let e=o.comment.reactions,t=e.find(e=>e.emoji===i.text);if("react"===i.kind){let r={author:i.author,name:i.name};t?t.by.some(e=>e.author===i.author)||t.by.push(r):e.length<12&&e.push({emoji:i.text,by:[r]})}else t&&(t.by=t.by.filter(e=>e.author!==i.author),t.by.length||e.splice(e.indexOf(t),1));continue}i.author!==o.comment.author||o.comment.deleted||("edit"===i.kind&&void 0!==i.text&&(o.comment.text=i.text,o.comment.edited=!0,i.mentions?o.comment.mentions=i.mentions:delete o.comment.mentions),"delete"===i.kind&&(o.comment.text="",o.comment.deleted=!0,o.comment.reactions=[],delete o.comment.mentions))}}for(let{comment:e}of r.values()){e.name||(e.unnamed=!0,e.name=n.get(e.author)??"");for(let t of e.reactions)for(let e of t.by)e.name||=n.get(e.author)??""}let i=new Map,o=(e,t)=>{let r=m({author:e,name:t}).toLowerCase();(i.get(r)??i.set(r,new Set).get(r)).add(e)};for(let{comment:e}of r.values()){o(e.author,e.name);for(let t of e.reactions)for(let e of t.by)o(e.author,e.name)}let a=(e,t)=>(i.get(m({author:e,name:t}).toLowerCase())?.size??0)>1;for(let{comment:e}of r.values()){a(e.author,e.name)&&(e.alike=!0);for(let t of e.reactions)for(let e of t.by)a(e.author,e.name)&&(e.alike=!0)}return[...t.values()]}(this.ops.values())}async add(e){let t=[];for(let r of e){if(!I(r)||r.body.doc!==this.doc||this.ops.has(r.body.id)||r.body.id!==await U(r.body)||!await F(r))continue;let e={body:r.body,sig:r.sig};this.ops.set(e.body.id,e),this.clock=Math.max(this.clock,e.body.clock),"create"===e.body.kind&&this.createAt.set(e.body.author,Math.max(this.createAt.get(e.body.author)??0,e.body.at)),this.threadAt.set(e.body.thread,Math.max(this.threadAt.get(e.body.thread)??0,e.body.at)),t.push(e)}return t}async append(e,t,r){let n={v:1,...r,id:"",thread:"create"===r.kind?"":r.thread,doc:this.doc,author:e.publicKey,name:t,clock:Math.min(this.clock+1,b),at:Math.min(Math.max(Date.now(),"create"===r.kind?(this.createAt.get(e.publicKey)??0)+1:(this.threadAt.get(r.thread)??0)+1),x)};n.id=await U(n);let i=this.ops.get(n.id);if(i)return i;"create"===n.kind&&(n.thread=n.id);let[o]=await this.add([await O(n,e)]);if(!o)throw new Error("pipeup: could not record this change");return o}},H=/^([A-Za-z0-9_-]{16}):([A-Za-z0-9_-]{43})$/;function L(){return`${function(t=16){return e(crypto.getRandomValues(new Uint8Array(t)))}(12)}:${e(crypto.getRandomValues(new Uint8Array(32)))}`}async function _(e){let r=H.exec(e.trim()),n=r?.[1],i=r?.[2];if(!n||!i)throw new Error('pipeup: data-pipeup-doc must look like "<id>:<key>", as made by newDocumentAttribute()');return{id:n,key:await crypto.subtle.importKey("raw",t(i),{name:"AES-GCM"},!1,["encrypt","decrypt"])}}async function z(t,n,i){let o=crypto.getRandomValues(new Uint8Array(12)),a=await crypto.subtle.encrypt({name:"AES-GCM",iv:o,additionalData:r(i)},t,r(n));return{iv:e(o),data:e(new Uint8Array(a))}}var B=e=>`pipeup-file:${e}`;var G=/^[a-z][a-z0-9-]{1,23}\/[a-z][a-z0-9-]{0,31}$/,Z=class extends Error{constructor(e,t,r=1){super("pipeup: shown here, but not saved yet"),this.id=e,this.cause=t,this.count=r,this.name="UnsavedChangeError"}},J=class e{constructor(e,t){this.log=e,this.options=t,this.displayName=t.name.trim()?Q(t.name):""}listeners=new Set;cache=[];comments=new Map;displayName;e=Promise.resolve();unsaved=[];static async open(t){if("string"!=typeof t.doc||""===t.doc||t.doc.length>64)throw new Error("pipeup: the document id must be 1 to 64 characters");let r=new K(t.doc),n=await t.store.load(t.doc),i=n.length-(await r.add(n)).length;i&&console.warn(`pipeup: ${i} saved change${1===i?"":"s"} on this page couldn't be read; kept as saved`);let o=new e(r,t);return o.refresh(!1),o}get id(){return this.options.doc}get me(){return this.options.identity.publicKey}get name(){return this.displayName}set name(e){this.displayName=Q(e),this.refresh(!0,[],"")}threads(){return this.cache}ops(){return this.log.all()}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}comment(e,t,r){return this.enqueue(()=>this.write({kind:"create",anchor:e,text:V(t),...r?.length?{mentions:r}:{}}))}reply(e,t,r){return this.enqueue(()=>this.write({kind:"reply",thread:this.find(e).thread,target:e,text:V(t),...r?.length?{mentions:r}:{}}))}edit(e,t,r){return this.enqueue(async()=>{let{thread:n}=this.mine(e);await this.write({kind:"edit",thread:n,target:e,text:V(t),...r?.length?{mentions:r}:{}})})}remove(e){return this.enqueue(async()=>{let{thread:t}=this.mine(e);await this.write({kind:"delete",thread:t,target:e})})}contact(e){let t=e.trim();return E.test(t)?this.enqueue(async()=>{await this.write({kind:"contact",thread:this.me,text:t})}):Promise.reject(new Error("pipeup: that isn't an email address or a GitHub name"))}uncontact(){return this.enqueue(async()=>{await this.write({kind:"uncontact",thread:this.me})})}contacts(){let e=new Map;for(let{body:t}of[...this.log.all()].sort(D))"contact"===t.kind&&t.text?e.set(t.author,t.text):"uncontact"===t.kind&&e.delete(t.author);return e}get portable(){return this.options.identity.privateKey.extractable}identityCode(){return async function(e){if(!e.privateKey.extractable)return null;let r=await crypto.subtle.exportKey("jwk",e.privateKey);return r.d?function(e){let t=(function(e){let t=0,r=0,n="";for(let i of e)for(r=r<<8|i,t+=8;t>=5;)n+=u[r>>>t-5&31],t-=5;return t&&(n+=u[r<<5-t&31]),n}(e)+function(e){let t=2166136261;for(let r of e)t=Math.imul(t^r,16777619);t=Math.imul(t^t>>>15,739982445);let r="";for(let e=0;e<4;e++,t>>>=5)r+=u[31&t];return r}(e)).match(/.{1,4}/g).join("-");return`PU1-${t}`}(t(r.d)):null}(this.options.identity)}seen(e){return this.enqueue(async()=>{for(let t of e){let e=this.comments.get(t);e&&!e.comment.seenBy?.includes(this.me)&&await this.write({kind:"seen",thread:e.thread,target:t})}})}react(e,t){return this.enqueue(async()=>{await this.write({kind:"react",thread:this.find(e).thread,target:e,text:W(t)})})}unreact(e,t){return this.enqueue(async()=>{await this.write({kind:"unreact",thread:this.find(e).thread,target:e,text:W(t)})})}resolve(e){return this.enqueue(async()=>{await this.write({kind:"resolve",thread:this.thread(e)})})}reopen(e){return this.enqueue(async()=>{await this.write({kind:"reopen",thread:this.thread(e)})})}sign(e,t){if(!G.test(e))throw new Error('pipeup: a signing purpose looks like "<add-on id>/<name>"');return d(this.options.identity,r(`pipeup:${e}\n${t}`))}flush(){return this.enqueue(()=>this.persist())}exportFile(){return async function(e,t,r){let n=r?{pipeup:1,doc:e,sealed:!0,...await z(r,JSON.stringify(t),B(e))}:{pipeup:1,doc:e,sealed:!1,ops:t};return JSON.stringify(n,null,2)}(this.options.doc,this.log.all(),this.options.key)}async importFile(e){return this.merge(await async function(e,n,i){if(e.length>5e6)throw new Error("pipeup: this feedback file is too large");let o,a,s;try{o=JSON.parse(e)}catch{throw new Error("pipeup: this is not an Pipeup feedback file")}if(!o||"object"!=typeof o||1!==o.pipeup||"string"!=typeof o.doc)throw new Error("pipeup: this is not an Pipeup feedback file");if(o.doc!==n)throw new Error("pipeup: this feedback is for a different document");if(!o.sealed)return Array.isArray(o.ops)?o.ops:[];if(!i)throw new Error("pipeup: this feedback is sealed, and this page has no document key");if("string"!=typeof o.iv||"string"!=typeof o.data)throw new Error("pipeup: this is not an Pipeup feedback file");try{a=await async function(e,n,i){try{let o=await crypto.subtle.decrypt({name:"AES-GCM",iv:t(n.iv),additionalData:r(i)},e,t(n.data));return(new TextDecoder).decode(o)}catch{throw new Error("pipeup: this feedback could not be opened with this document")}}(i,{iv:o.iv,data:o.data},B(n))}catch{throw new Error("pipeup: this feedback could not be opened with this document's key")}try{s=JSON.parse(a)}catch{throw new Error("pipeup: this is not an Pipeup feedback file")}return Array.isArray(s)?s:[]}(e,this.options.doc,this.options.key),"file")}merge(e,t){return e.length>5e3?Promise.reject(new RangeError("pipeup: too many changes to merge at once")):this.enqueue(async()=>{let r=await this.log.add([...e]);this.unsaved.push(...r),r.length>0&&this.refresh(!0,r,t);try{await this.persist()}catch(e){throw r.length>0?new Z(r[0].body.id,e,r.length):e}return r.length})}enqueue(e){let t=this.e.then(e);return this.e=t.catch(()=>{}),t}async write(e){let t=await this.log.append(this.options.identity,this.displayName,e);this.unsaved.push(t),this.refresh(!0,[t],"local");try{await this.persist()}catch(e){throw new Z(t.body.id,e)}return t.body.id}async persist(){if(0===this.unsaved.length)return;let e=[...this.unsaved];await this.options.store.append(this.options.doc,e),this.unsaved=this.unsaved.filter(t=>!e.includes(t))}refresh(e,t=[],r=""){this.cache=this.log.threads(),this.comments.clear();let n=(e,t)=>{e.unnamed&&e.author===this.me&&this.displayName&&(e.name=this.displayName),this.comments.set(e.id,{comment:e,thread:t});for(let r of e.replies)n(r,t)};for(let e of this.cache)n(e.root,e.id);if(e)for(let e of this.listeners)try{e(this.cache,t,r)}catch(e){globalThis.reportError?.(e)}}find(e){let t=this.comments.get(e);if(!t)throw new Error("pipeup: there is no comment with that id");return t}mine(e){let t=this.find(e);if(t.comment.author!==this.me)throw new Error("pipeup: only the writer can change a comment");return t}thread(e){if(!this.cache.some(t=>t.id===e))throw new Error("pipeup: there is no thread with that id");return e}};function V(e){let t=e.trim();if(!t)throw new Error("pipeup: a comment needs some words");if(t.length>w)throw new Error(`pipeup: comments are limited to ${w} characters`);return t}function W(e){if(!N(e))throw new Error("pipeup: a reaction is one emoji");return e}function Q(e){let t=e.replace(/\s+/g," ").trim();if(!t||t.length>80)throw new Error("pipeup: choose a name of 1 to 80 characters");if(y(t))throw new Error("pipeup: that name is kept for AI replies; choose another");return t}var X=class{docs=new Map;profile=null;async load(e){return[...this.docs.get(e)?.values()??[]]}async append(e,t){let r=this.docs.get(e);r||this.docs.set(e,r=new Map);for(let e of t)r.set(e.body.id,e)}async loadProfile(){return this.profile}async saveProfile(e){this.profile=e}async saveProfileIfAbsent(e){return this.profile||(this.profile=e),this.profile}},Y="";async function ee(e,t=""){let r=await e.loadProfile();if(r)return"Reviewer"===r.name?{...r,name:""}:r;let n={identity:await l(),name:t};return e.saveProfileIfAbsent(n)}var te=class e{constructor(e){this.db=e}static open(t="pipeup"){return new Promise((r,n)=>{let i=indexedDB.open(t,1);i.onupgradeneeded=()=>{let e=i.result;e.createObjectStore("ops",{keyPath:["doc","id"]}).createIndex("doc","doc"),e.createObjectStore("meta")},i.onsuccess=()=>{let t=i.result;t.onversionchange=()=>t.close(),r(new e(t))},i.onerror=()=>n(i.error??new Error("pipeup: could not open local storage"))})}close(){this.db.close()}async load(e){return(await this.request("ops","readonly",t=>t.index("doc").getAll(e))).map(e=>e.op)}async append(e,t){if(0===t.length)return;let r=this.db.transaction("ops","readwrite"),n=r.objectStore("ops"),i=re(r);try{for(let r of t)n.put({doc:e,id:r.body.id,op:r})}catch(e){throw r.abort(),i.catch(()=>{}),e}await i}async loadProfile(){return await this.request("meta","readonly",e=>e.get("profile"))??null}async saveProfile(e){await this.request("meta","readwrite",t=>t.put(e,"profile"))}async saveProfileIfAbsent(e){let t=this.db.transaction("meta","readwrite"),r=t.objectStore("meta"),n=re(t),i=e,o=r.get("profile");return o.onsuccess=()=>{o.result?i=o.result:r.put(e,"profile")},await n,i}request(e,t,r){return new Promise((n,i)=>{let o=r(this.db.transaction(e,t).objectStore(e));o.onsuccess=()=>n(o.result),o.onerror=()=>i(o.error??new Error("pipeup: local storage request failed"))})}};function re(e){return new Promise((t,r)=>{e.oncomplete=()=>t(),e.onerror=()=>r(e.error??new Error("pipeup: local storage write failed")),e.onabort=()=>r(e.error??new Error("pipeup: local storage write aborted"))})}function ne(e){let t=(e.ownerDocument??e).createTreeWalker(e,NodeFilter.SHOW_TEXT),r=[],n="";for(let e=t.nextNode();e;e=t.nextNode())r.push({node:e,start:n.length}),n+=e.data;return{text:n,nodes:r}}function ie(e,t,r){let n=(e.ownerDocument??e).createRange();return n.setStart(e,0),n.setEnd(t,r),n.toString().length}function oe(e,t,r){let n=ae(e,t,!1),i=ae(e,r,!0);if(!n||!i||r<t)return null;let o=n.node.ownerDocument.createRange();return o.setStart(n.node,n.offset),o.setEnd(i.node,i.offset),o}function ae(e,t,r){let n=e.nodes.find(e=>t>=e.start&&t<e.start+e.node.data.length+Number(r));return n&&{node:n.node,offset:t-n.start}}function se(e){return e.nodeType===Node.ELEMENT_NODE?e:e.parentElement}function le(e,t){for(let r=se(e)?.closest("[data-pipeup-id]")??null;r;){if(!t.contains(r))return null;let e=r.getAttribute("data-pipeup-id")??"";if(""!==e&&e.length<=200)return r;r=r.parentElement?.closest("[data-pipeup-id]")??null}return null}function de(e,t,r){return e.slice(t,r).replace(/^[\uDC00-\uDFFF]|[\uD800-\uDBFF]$/g,"")}function ue(e,t){let r=function(e,t){if(e===t)return"";let r=[];for(let n=t;n&&n!==e;n=n.parentElement){let e=1;for(let t=n.previousElementSibling;t;t=t.previousElementSibling)t.tagName===n.tagName&&e++;r.unshift(`${n.tagName.toLowerCase()}:nth-of-type(${e})`)}return":scope > "+r.join(" > ")}(e,t);if(r.length>2e3)throw new Error("pipeup: this element is nested too deeply to comment on — give a nearby container a data-pipeup-id");return r}function ce(e){if(!T(e))throw new Error("pipeup: could not describe this place on the page");return e}function he(e,t){let r=se(e),n=r?.closest("p,li,h1,h2,h3,h4,h5,h6,td,th,blockquote,pre,figcaption,dt,dd,summary,caption")??null;return n&&t.contains(n)?n:r&&t.contains(r)?r:t}function pe(e,t,r){if(!t.contains(e.commonAncestorContainer))throw new Error("pipeup: the selection is outside the commentable area");let n=le(e.commonAncestorContainer,t),a=n??t,{text:s}=ne(a),l=ie(a,e.startContainer,e.startOffset),d=ie(a,e.endContainer,e.endOffset),u=s.slice(l,d);if(""===u)throw new Error("pipeup: select some text to comment on");if(u.length>v)throw new Error("pipeup: selections are limited to 10000 characters");let c=de(s,Math.max(0,l-32),l),h=de(s,d,d+32);return ce({...n?{id:n.getAttribute("data-pipeup-id")??""}:{},path:"",quote:{exact:u,prefix:c,suffix:h},...r?{view:r}:{},fingerprint:o(he(e.startContainer,t).textContent??""),snapshot:de(i(c+u+h),0,200)})}function fe(e,t,r,n){let a=le(e,t);return ce({...a?{id:a.getAttribute("data-pipeup-id")??""}:{},path:ue(a??t,e),...r?{point:{x:me(r.x),y:me(r.y)}}:{},...n?{view:n}:{},fingerprint:o(e.textContent??""),snapshot:de(i(e.textContent??""),0,200)})}function me(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):0}function ge(e,t,r){let n=Array.from({length:t.length+1},(e,t)=>t),i=[...n],o=[t.length];for(let a=1;a<=e.length;a++){i[0]=r?0:a;for(let r=1;r<=t.length;r++){let o=e.charCodeAt(a-1)===t.charCodeAt(r-1)?0:1;i[r]=Math.min(n[r]+1,i[r-1]+1,n[r-1]+o)}o.push(i[t.length]),[n,i]=[i,n]}return o}var ye=(e,t)=>ge(e,t,!1).pop(),we={state:"orphaned",element:null,range:null};function be(e,t,r={}){let n=e.id?function(e,t){for(let r of e.querySelectorAll("[data-pipeup-id]"))if(r.getAttribute("data-pipeup-id")===t)return r;return null}(t,e.id):t;return e.quote?function(e,t,r,n,i){let a=r??n,s=ne(a),l=function(e,t){if(!t.exact)return-1;let r=-1,n=-1;for(let i=e.indexOf(t.exact);-1!==i;i=e.indexOf(t.exact,i+1)){let o=e.slice(Math.max(0,i-t.prefix.length),i),a=e.slice(i+t.exact.length,i+t.exact.length+t.suffix.length),s=ve(o,t.prefix)+xe(a,t.suffix);s>n&&(r=i,n=s)}return r}(s.text,t);if(l>=0){let i=oe(s,l,l+t.exact.length);if(i){let t=he(i.startContainer,n);return{state:null!==r&&o(t.textContent??"")===e.fingerprint?"attached":"moved",element:t,range:i}}}if(i){let e=a===n?s:ne(n);if(e.text.length<=5e4&&t.exact.length>=12&&t.exact.length<=300){let r=function(e,t,r){let n=t.length;if(0===n)return null;let i=-1,o=1/0;if(ge(e,t,!0).forEach((e,t)=>{t&&e<o&&(o=e,i=t)}),o>r)return null;let a=Math.max(0,i-n),s=1/0;for(let o=Math.max(0,i-n-r);o<=Math.min(i,i-n+r);o++){let r=ye(e.slice(o,i),t);r<s&&(s=r,a=o)}return{start:a,end:i,distance:s}}(e.text,t.exact,Math.floor(t.exact.length/4)),i=r?oe(e,r.start,r.end):null;if(i)return{state:"moved",element:he(i.startContainer,n),range:i}}}return we}(e,e.quote,n,t,!1!==r.fuzzy):function(e,t){if(!t)return we;let r=t;if(e.path){if(!e.path.startsWith(":scope > "))return we;try{r=t.querySelector(e.path)}catch{return we}}return r?{state:o(r.textContent??"")===e.fingerprint?"attached":"moved",element:r,range:null}:we}(e,n)}function xe(e,t){let r=0;for(;r<e.length&&r<t.length&&e[r]===t[r];)r++;return r}function ve(e,t){let r=0;for(;r<e.length&&r<t.length&&e[e.length-1-r]===t[t.length-1-r];)r++;return r}var ke={A:"Link",BUTTON:"Button",P:"Paragraph",LI:"List item",IMG:"Image",TABLE:"Table",TD:"Cell",TH:"Cell",FIGURE:"Figure",SVG:"Graphic",CANVAS:"Graphic",LABEL:"Label",INPUT:"Field",SELECT:"Field",TEXTAREA:"Field",SUMMARY:"Question",BLOCKQUOTE:"Quote",SECTION:"Section",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading"};function Ae(e){return e.getAttribute("data-pipeup-label")||ke[e.tagName.toUpperCase()]||"Block"}function Ee(e){let t=e.getAttribute("data-pipeup-label");if(t)return t;let r=function(e){return Me(i(e.getAttribute("aria-label")??e.getAttribute("alt")??e.innerText??e.textContent??""),40)}(e);return r?`${Ae(e)} · ${r}`:Ae(e)}function $e(e,t,r){let n=e.id??null,o=e.quote?.exact??null,a=e.point?`${Math.round(100*e.point.x)}% across, ${Math.round(100*e.point.y)}% down the element`:null,s=t.element;if("orphaned"===t.state||!s)return{where:`No longer on the page (it read: "${e.snapshot}")`,element:null,id:n,quote:o,pin:a};let l=[],d=e.view?.slide;if(d){let e=s.closest("[data-pipeup-slide]")?.querySelector("h1,h2,h3")?.textContent;l.push(`Slide ${d}`+(e?` "${Me(i(e))}"`:""))}else{let t=function(e){return e?e.label||Object.entries(e).filter(([e,t])=>t&&"slide"!==e).map(([,e])=>e).join(" · "):""}(e.view);t&&l.push(t);let n=function(e,t){let r=null;for(let n of t.querySelectorAll("h1,h2,h3")){if(n===e||n.contains(e))break;if(!(4&n.compareDocumentPosition(e)))break;r=n}return r}(s,r);n&&l.push(`Section "${Me(i(n.textContent??""))}"`)}let u=Ee(s);return l.push(u),{where:l.join(" › "),element:u,id:n,quote:o,pin:a}}function Me(e,t=60){return e.length>t?e.slice(0,t-1)+"…":e}var Se="Each thread says where it is: the slide or section, the element (its data-pipeup-id attribute in the HTML), and the exact quoted text or pin position. Work through the open threads in order, and for each one say what you changed or why you did not. Answer in the thread (the Pipeup skill says how) and mark it addressed; never resolve it, the author does.";function Ce(e,t){let r=Math.max(0,Math.round((t-e)/1e3));if(r<60)return"just now";let n=Math.round(r/60);if(n<60)return`${n}m ago`;let i=Math.round(n/60);return i<48?`${i}h ago`:`${Math.round(i/24)}d ago`}function Ne(e,t,r,n){return"ai"===t?je(e,"## Comment thread",n,r):Te(e)}function Oe(e,t,r){let n=r.all?e:e.filter(e=>!e.thread.resolved);if("text"===t)return n.map(Te).join("\n");let i=r.all?0:e.length-n.length,o=[`# Review comments: ${qe(r.title)}`,"",`- **Page:** ${qe(r.url)}`,`- **Exported:** ${r.exportedAt.toISOString()}`,`- **Open threads:** ${e.length-e.filter(e=>e.thread.resolved).length}${i?` (${i} resolved, not included)`:r.all?" (resolved ones included)":""}`,"",Se,"",""].join("\n"),a=r.exportedAt.getTime();return o+n.map((e,t)=>je(e,`## Thread ${t+1}`,a)).join("\n")}function Pe(e){return e.reactions.map(e=>`${e.emoji} ${e.by.length} (${e.by.map(e=>qe(m(e))).join(", ")})`).join(" · ")}function qe(e){return e.replace(/\s+/g," ").trim()}function je(e,t,r,n){let{thread:i,location:o}=e,a=i.root.replies.length,s=[`${t} · ${i.resolved?"resolved":i.addressed?"open, addressed":"open"}`];if(s.push(`- **Thread:** ${i.id}`),n&&s.push(`- **Page:** ${qe(n)}`),s.push(`- **Where:** ${qe(o.where)}`),o.id){let e=qe(o.id.replace(/`/g,""));s.push(`- **Element:** \`[data-pipeup-id="${e}"]\`${o.element?` (${qe(o.element)})`:""}`)}o.quote&&s.push(`- **Quoted text:** "${qe(o.quote)}"`),o.pin&&s.push(`- **Pin:** ${qe(o.pin)}`),i.addressed&&!i.resolved&&s.push(`- **Addressed:** ${qe(i.addressed.text)} (${qe(m(i.addressed))}, ${Ce(i.addressed.at,r)})`),s.push(`- **Started by:** ${qe(m(i.root))}, ${Ce(i.root.at,r)}${a?` · ${a} ${1===a?"reply":"replies"}`:""}`,"",`**${qe(m(i.root))}** (${Ce(i.root.at,r)}): ${Ie(i.root," ")}`),i.root.reactions.length&&s.push(`Reactions: ${Pe(i.root)}`);for(let e of i.root.replies)s.push(`- **${qe(m(e))}** (${Ce(e.at,r)}): ${Ie(e," ")} \x3c!-- comment:${e.id} --\x3e`),e.reactions.length&&s.push(` - Reactions: ${Pe(e)}`);return s.join("\n")+"\n"}function Te(e){let{thread:t,location:r}=e,n=[qe(r.where)+(r.quote?`, on the text "${qe(r.quote)}"`:""),`${qe(m(t.root))}: ${Ie(t.root," ")}`];t.root.reactions.length&&n.push(` Reactions: ${Pe(t.root)}`);for(let e of t.root.replies)n.push(` ${qe(m(e))}: ${Ie(e," ")}`),e.reactions.length&&n.push(` Reactions: ${Pe(e)}`);return n.join("\n")+"\n"}function Ie(e,t){return e.deleted?"(deleted)":e.text.replace(/\r\n|\r|\n/g,"\n"+t)+(e.edited?" (edited)":"")}var Fe="0.7.0-beta.1",De="2026-10-13";export{g as AI_NAME,c as ANIMALS,De as BUILT,h as COLOURS,Y as DEFAULT_PROFILE_NAME,te as IndexedDbStore,X as MemoryStore,J as PipeupDocument,Z as UnsavedChangeError,Fe as VERSION,f as animalName,U as computeOpId,Oe as copyAll,Ne as copyThread,l as createIdentity,fe as describeElement,pe as describeRange,Ce as formatAgo,y as isAiName,Ee as labelOf,ee as loadOrCreateProfile,$e as locate,m as nameOf,L as newDocumentAttribute,_ as parseDocumentAttribute,be as resolveAnchor,O as signOp};
|
|
1
|
+
function e(e){let t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function t(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r}function r(e){return(new TextEncoder).encode(e)}function n(e){if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(n).join(",")+"]";let t=e;return"{"+Object.keys(t).filter(e=>void 0!==t[e]).sort().map(e=>JSON.stringify(e)+":"+n(t[e])).join(",")+"}"}function i(e){return e.replace(/\s+/g," ").trim()}function o(e){let t=i(e),r=2166136261;for(let e=0;e<t.length;e++)r^=t.charCodeAt(e),r=Math.imul(r,16777619)>>>0;return r.toString(16).padStart(8,"0")}var a=/^[A-Za-z0-9_-]{43}$/,s=new Map;async function l(){let t=await crypto.subtle.generateKey({name:"Ed25519"},!1,["sign","verify"]);return{publicKey:e(new Uint8Array(await crypto.subtle.exportKey("raw",t.publicKey))),privateKey:t.privateKey}}async function d(t,r){let n=await crypto.subtle.sign({name:"Ed25519"},t.privateKey,r);return e(new Uint8Array(n))}var u="0123456789ABCDEFGHJKMNPQRSTVWXYZ";Uint8Array.from([48,46,2,1,0,48,5,6,3,43,101,112,4,34,4,32]);var c=["Otter","Fox","Owl","Bear","Rabbit"],h=["Red","Orange","Yellow","Green","Teal","Blue","Purple","Pink","Brown","Grey"];function p(e){let t=2166136261;for(let r=0;r<e.length;r++)t=Math.imul(t^e.charCodeAt(r),16777619);return t=Math.imul(t^t>>>16,2246822507),t=Math.imul(t^t>>>13,3266489909),(t^t>>>16)>>>0}var f=e=>`${h[(e=>Math.floor(p(e)/c.length)%h.length)(e)]} ${c[(e=>p(e)%c.length)(e)]}`,m=e=>{let t=e.name.replace(/^AI assistant · /,"")||f(e.author);return e.alike?`${t} · ${function(e){let t=p(`id:${e}`),r="";for(let e=0;e<4;e++,t>>>=5)r+="0123456789ABCDEFGHJKMNPQRSTVWXYZ"[31&t];return r}(e.author)}`:t},g="AI assistant",y=e=>e===g||e.startsWith(`${g} `),w=1e4,b=2**40,x=2**47,v=1e4,k=/^[0-9a-f]{8}$/,A=/^[A-Za-z0-9_-]{43}$/,E=/^(?:[^\s@<>"]{1,64}@[^\s@<>"]{1,190}\.[^\s@<>"]{2,24}|gh:[A-Za-z0-9-]{1,39})$/,$=/^(?:[A-Za-z0-9_-]{43}|(?:host|gh):[A-Za-z0-9._@-]{1,64})$/,M=/^[A-Za-z0-9_-]{43}$/,S=new Set(["create","reply","edit","delete","resolve","reopen","addressed","react","unreact","seen","contact","uncontact"]),C=/^(?:\p{Regional_Indicator}{2}|[#*0-9]\uFE0F?\u20E3|\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})?)*)$/u,N=e=>"string"==typeof e&&C.test(e)&&[...e].length<=16;async function O(e,t){if(e.author!==t.publicKey)throw new Error("pipeup: an op must be signed by its author");return{body:e,sig:await d(t,r(n(e)))}}var P=(e,t)=>"string"==typeof e&&e.length<=t,q=e=>!!e&&"object"==typeof e,j=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0&&e<=1;function T(e){if(!q(e))return!1;let{id:t,path:r,fingerprint:n,snapshot:i,quote:o,point:a,view:s}=e;if(!(P(r,2e3)&&P(i,500)&&"string"==typeof n&&k.test(n)&&(void 0===t||P(t,200)&&""!==t)&&(void 0===o||q(o)&&P(o.exact,v)&&""!==o.exact&&P(o.prefix,64)&&P(o.suffix,64))&&(void 0===a||q(a)&&j(a.x)&&j(a.y))))return!1;if(void 0!==s){if(!q(s)||Array.isArray(s))return!1;let e=Object.entries(s);if(e.length>16||!e.every(([e,t])=>e.length<=64&&P(t,200)))return!1}return!0}function I(e){if(!q(e))return!1;let{body:t,sig:r}=e;if("string"!=typeof r||!q(t)||JSON.stringify({body:t,sig:r}).length>262144)return!1;let n=t;if(1!==n.v||"string"!=typeof n.id||!A.test(n.id)||!S.has(n.kind)||!P(n.doc,64)||""===n.doc||"string"!=typeof n.thread||!A.test(n.thread)||"string"!=typeof n.author||!M.test(n.author)||"string"!=typeof n.name||n.name.length>80||n.name!==n.name.trim()||"number"!=typeof n.clock||!Number.isSafeInteger(n.clock)||n.clock<1||n.clock>b||"number"!=typeof n.at||!Number.isSafeInteger(n.at)||n.at<0||n.at>x||void 0!==n.text&&(!P(n.text,w)||n.text!==n.text.trim())||void 0!==n.target&&("string"!=typeof n.target||!A.test(n.target))||("create"===n.kind||"reply"===n.kind||"edit"===n.kind||"addressed"===n.kind)&&!n.text||("reply"===n.kind||"edit"===n.kind||"delete"===n.kind)&&!n.target)return!1;if(void 0!==n.mentions){if("create"!==n.kind&&"reply"!==n.kind&&"edit"!==n.kind||!Array.isArray(n.mentions)||n.mentions.length>20)return!1;let e=n.text?.length??0;for(let t of n.mentions){if(!q(t)||!Number.isSafeInteger(t.start)||!Number.isSafeInteger(t.end))return!1;let{start:r,end:n,ref:i}=t;if(r<0||n<=r||n>e||"string"!=typeof i||!$.test(i))return!1}}return!(!("contact"!==n.kind||"string"==typeof n.text&&E.test(n.text))||"seen"===n.kind&&!n.target||!("react"!==n.kind&&"unreact"!==n.kind||n.target&&N(n.text))||!("create"!==n.kind||n.id===n.thread&&T(n.anchor)))}function F(i){return async function(r,n,i){if(!a.test(r))return!1;try{if(e(t(r))!==r)return!1;let o=s.get(r);if(!o){s.size>=512&&s.delete(s.keys().next().value),o=crypto.subtle.importKey("raw",t(r),{name:"Ed25519"},!0,["verify"]),s.set(r,o);let e=o;e.catch(()=>{s.get(r)===e&&s.delete(r)})}return await crypto.subtle.verify({name:"Ed25519"},await o,t(i),n)}catch{return!1}}(i.body.author,r(n(i.body)),i.sig)}function D(e,t){return e.body.clock!==t.body.clock?e.body.clock-t.body.clock:e.body.at!==t.body.at?e.body.at-t.body.at:e.body.author!==t.body.author?e.body.author<t.body.author?-1:1:e.body.id!==t.body.id?e.body.id<t.body.id?-1:1:0}function R(e,t,r,n,i,o){let a={id:e,author:t,name:r,text:n,at:i,edited:!1,deleted:!1,replies:[],reactions:[]};return o?.length&&(a.mentions=o),a}async function U(t){let i={...t,id:void 0,thread:"create"===t.kind?void 0:t.thread},o=await crypto.subtle.digest("SHA-256",r(n(i)));return e(new Uint8Array(o))}var K=class{constructor(e){this.doc=e}ops=new Map;clock=0;createAt=new Map;threadAt=new Map;get size(){return this.ops.size}all(){return[...this.ops.values()]}threads(){return function(e){let t=new Map,r=new Map,n=new Map;for(let{body:i}of[...e].sort(D)){if(i.name&&n.set(i.author,i.name),"create"===i.kind){if(t.has(i.id)||!i.anchor||void 0===i.text)continue;let e=R(i.id,i.author,i.name,i.text,i.at,i.mentions);t.set(i.id,{id:i.id,anchor:i.anchor,root:e,resolved:!1}),r.set(i.id,{comment:e,thread:i.id});continue}let e=t.get(i.thread);if(!e)continue;if("resolve"===i.kind||"reopen"===i.kind){e.resolved="resolve"===i.kind,"reopen"===i.kind&&delete e.addressed;continue}if("addressed"===i.kind){void 0!==i.text&&(e.addressed={text:i.text,author:i.author,name:i.name,at:i.at});continue}let o=i.target?r.get(i.target):void 0;if(o&&o.thread===i.thread){if("reply"===i.kind){if(r.has(i.id)||void 0===i.text)continue;let t=R(i.id,i.author,i.name,i.text,i.at,i.mentions);e.root.replies.push(t),r.set(i.id,{comment:t,thread:i.thread});continue}if("seen"===i.kind){let e=o.comment.seenBy??=[];e.includes(i.author)||e.push(i.author);continue}if("react"===i.kind||"unreact"===i.kind){if(void 0===i.text||o.comment.deleted)continue;let e=o.comment.reactions,t=e.find(e=>e.emoji===i.text);if("react"===i.kind){let r={author:i.author,name:i.name};t?t.by.some(e=>e.author===i.author)||t.by.push(r):e.length<12&&e.push({emoji:i.text,by:[r]})}else t&&(t.by=t.by.filter(e=>e.author!==i.author),t.by.length||e.splice(e.indexOf(t),1));continue}i.author!==o.comment.author||o.comment.deleted||("edit"===i.kind&&void 0!==i.text&&(o.comment.text=i.text,o.comment.edited=!0,i.mentions?o.comment.mentions=i.mentions:delete o.comment.mentions),"delete"===i.kind&&(o.comment.text="",o.comment.deleted=!0,o.comment.reactions=[],delete o.comment.mentions))}}for(let{comment:e}of r.values()){e.name||(e.unnamed=!0,e.name=n.get(e.author)??"");for(let t of e.reactions)for(let e of t.by)e.name||=n.get(e.author)??""}let i=new Map,o=(e,t)=>{let r=m({author:e,name:t}).toLowerCase();(i.get(r)??i.set(r,new Set).get(r)).add(e)};for(let{comment:e}of r.values()){o(e.author,e.name);for(let t of e.reactions)for(let e of t.by)o(e.author,e.name)}let a=(e,t)=>(i.get(m({author:e,name:t}).toLowerCase())?.size??0)>1;for(let{comment:e}of r.values()){a(e.author,e.name)&&(e.alike=!0);for(let t of e.reactions)for(let e of t.by)a(e.author,e.name)&&(e.alike=!0)}return[...t.values()]}(this.ops.values())}async add(e){let t=[];for(let r of e){if(!I(r)||r.body.doc!==this.doc||this.ops.has(r.body.id)||r.body.id!==await U(r.body)||!await F(r))continue;let e={body:r.body,sig:r.sig};this.ops.set(e.body.id,e),this.clock=Math.max(this.clock,e.body.clock),"create"===e.body.kind&&this.createAt.set(e.body.author,Math.max(this.createAt.get(e.body.author)??0,e.body.at)),this.threadAt.set(e.body.thread,Math.max(this.threadAt.get(e.body.thread)??0,e.body.at)),t.push(e)}return t}async append(e,t,r){let n={v:1,...r,id:"",thread:"create"===r.kind?"":r.thread,doc:this.doc,author:e.publicKey,name:t,clock:Math.min(this.clock+1,b),at:Math.min(Math.max(Date.now(),"create"===r.kind?(this.createAt.get(e.publicKey)??0)+1:(this.threadAt.get(r.thread)??0)+1),x)};n.id=await U(n);let i=this.ops.get(n.id);if(i)return i;"create"===n.kind&&(n.thread=n.id);let[o]=await this.add([await O(n,e)]);if(!o)throw new Error("pipeup: could not record this change");return o}},H=/^([A-Za-z0-9_-]{16}):([A-Za-z0-9_-]{43})$/;function L(){return`${function(t=16){return e(crypto.getRandomValues(new Uint8Array(t)))}(12)}:${e(crypto.getRandomValues(new Uint8Array(32)))}`}async function _(e){let r=H.exec(e.trim()),n=r?.[1],i=r?.[2];if(!n||!i)throw new Error('pipeup: data-pipeup-doc must look like "<id>:<key>", as made by newDocumentAttribute()');return{id:n,key:await crypto.subtle.importKey("raw",t(i),{name:"AES-GCM"},!1,["encrypt","decrypt"])}}async function z(t,n,i){let o=crypto.getRandomValues(new Uint8Array(12)),a=await crypto.subtle.encrypt({name:"AES-GCM",iv:o,additionalData:r(i)},t,r(n));return{iv:e(o),data:e(new Uint8Array(a))}}var B=e=>`pipeup-file:${e}`;var G=/^[a-z][a-z0-9-]{1,23}\/[a-z][a-z0-9-]{0,31}$/,Z=class extends Error{constructor(e,t,r=1){super("pipeup: shown here, but not saved yet"),this.id=e,this.cause=t,this.count=r,this.name="UnsavedChangeError"}},J=class e{constructor(e,t){this.log=e,this.options=t,this.displayName=t.name.trim()?Q(t.name):""}listeners=new Set;cache=[];comments=new Map;displayName;e=Promise.resolve();unsaved=[];static async open(t){if("string"!=typeof t.doc||""===t.doc||t.doc.length>64)throw new Error("pipeup: the document id must be 1 to 64 characters");let r=new K(t.doc),n=await t.store.load(t.doc),i=n.length-(await r.add(n)).length;i&&console.warn(`pipeup: ${i} saved change${1===i?"":"s"} on this page couldn't be read; kept as saved`);let o=new e(r,t);return o.refresh(!1),o}get id(){return this.options.doc}get me(){return this.options.identity.publicKey}get name(){return this.displayName}set name(e){this.displayName=Q(e),this.refresh(!0,[],"")}threads(){return this.cache}ops(){return this.log.all()}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}comment(e,t,r){return this.enqueue(()=>this.write({kind:"create",anchor:e,text:V(t),...r?.length?{mentions:r}:{}}))}reply(e,t,r){return this.enqueue(()=>this.write({kind:"reply",thread:this.find(e).thread,target:e,text:V(t),...r?.length?{mentions:r}:{}}))}edit(e,t,r){return this.enqueue(async()=>{let{thread:n}=this.mine(e);await this.write({kind:"edit",thread:n,target:e,text:V(t),...r?.length?{mentions:r}:{}})})}remove(e){return this.enqueue(async()=>{let{thread:t}=this.mine(e);await this.write({kind:"delete",thread:t,target:e})})}contact(e){let t=e.trim();return E.test(t)?this.enqueue(async()=>{await this.write({kind:"contact",thread:this.me,text:t})}):Promise.reject(new Error("pipeup: that isn't an email address or a GitHub name"))}uncontact(){return this.enqueue(async()=>{await this.write({kind:"uncontact",thread:this.me})})}contacts(){let e=new Map;for(let{body:t}of[...this.log.all()].sort(D))"contact"===t.kind&&t.text?e.set(t.author,t.text):"uncontact"===t.kind&&e.delete(t.author);return e}get portable(){return this.options.identity.privateKey.extractable}identityCode(){return async function(e){if(!e.privateKey.extractable)return null;let r=await crypto.subtle.exportKey("jwk",e.privateKey);return r.d?function(e){let t=(function(e){let t=0,r=0,n="";for(let i of e)for(r=r<<8|i,t+=8;t>=5;)n+=u[r>>>t-5&31],t-=5;return t&&(n+=u[r<<5-t&31]),n}(e)+function(e){let t=2166136261;for(let r of e)t=Math.imul(t^r,16777619);t=Math.imul(t^t>>>15,739982445);let r="";for(let e=0;e<4;e++,t>>>=5)r+=u[31&t];return r}(e)).match(/.{1,4}/g).join("-");return`PU1-${t}`}(t(r.d)):null}(this.options.identity)}seen(e){return this.enqueue(async()=>{for(let t of e){let e=this.comments.get(t);e&&!e.comment.seenBy?.includes(this.me)&&await this.write({kind:"seen",thread:e.thread,target:t})}})}react(e,t){return this.enqueue(async()=>{await this.write({kind:"react",thread:this.find(e).thread,target:e,text:W(t)})})}unreact(e,t){return this.enqueue(async()=>{await this.write({kind:"unreact",thread:this.find(e).thread,target:e,text:W(t)})})}resolve(e){return this.enqueue(async()=>{await this.write({kind:"resolve",thread:this.thread(e)})})}reopen(e){return this.enqueue(async()=>{await this.write({kind:"reopen",thread:this.thread(e)})})}sign(e,t){if(!G.test(e))throw new Error('pipeup: a signing purpose looks like "<add-on id>/<name>"');return d(this.options.identity,r(`pipeup:${e}\n${t}`))}flush(){return this.enqueue(()=>this.persist())}exportFile(){return async function(e,t,r){let n=r?{pipeup:1,doc:e,sealed:!0,...await z(r,JSON.stringify(t),B(e))}:{pipeup:1,doc:e,sealed:!1,ops:t};return JSON.stringify(n,null,2)}(this.options.doc,this.log.all(),this.options.key)}async importFile(e){return this.merge(await async function(e,n,i){if(e.length>5e6)throw new Error("pipeup: this feedback file is too large");let o,a,s;try{o=JSON.parse(e)}catch{throw new Error("pipeup: this is not an Pipeup feedback file")}if(!o||"object"!=typeof o||1!==o.pipeup||"string"!=typeof o.doc)throw new Error("pipeup: this is not an Pipeup feedback file");if(o.doc!==n)throw new Error("pipeup: this feedback is for a different document");if(!o.sealed)return Array.isArray(o.ops)?o.ops:[];if(!i)throw new Error("pipeup: this feedback is sealed, and this page has no document key");if("string"!=typeof o.iv||"string"!=typeof o.data)throw new Error("pipeup: this is not an Pipeup feedback file");try{a=await async function(e,n,i){try{let o=await crypto.subtle.decrypt({name:"AES-GCM",iv:t(n.iv),additionalData:r(i)},e,t(n.data));return(new TextDecoder).decode(o)}catch{throw new Error("pipeup: this feedback could not be opened with this document")}}(i,{iv:o.iv,data:o.data},B(n))}catch{throw new Error("pipeup: this feedback could not be opened with this document's key")}try{s=JSON.parse(a)}catch{throw new Error("pipeup: this is not an Pipeup feedback file")}return Array.isArray(s)?s:[]}(e,this.options.doc,this.options.key),"file")}merge(e,t){return e.length>5e3?Promise.reject(new RangeError("pipeup: too many changes to merge at once")):this.enqueue(async()=>{let r=await this.log.add([...e]);this.unsaved.push(...r),r.length>0&&this.refresh(!0,r,t);try{await this.persist()}catch(e){throw r.length>0?new Z(r[0].body.id,e,r.length):e}return r.length})}enqueue(e){let t=this.e.then(e);return this.e=t.catch(()=>{}),t}async write(e){let t=await this.log.append(this.options.identity,this.displayName,e);this.unsaved.push(t),this.refresh(!0,[t],"local");try{await this.persist()}catch(e){throw new Z(t.body.id,e)}return t.body.id}async persist(){if(0===this.unsaved.length)return;let e=[...this.unsaved];await this.options.store.append(this.options.doc,e),this.unsaved=this.unsaved.filter(t=>!e.includes(t))}refresh(e,t=[],r=""){this.cache=this.log.threads(),this.comments.clear();let n=(e,t)=>{e.unnamed&&e.author===this.me&&this.displayName&&(e.name=this.displayName),this.comments.set(e.id,{comment:e,thread:t});for(let r of e.replies)n(r,t)};for(let e of this.cache)n(e.root,e.id);if(e)for(let e of this.listeners)try{e(this.cache,t,r)}catch(e){globalThis.reportError?.(e)}}find(e){let t=this.comments.get(e);if(!t)throw new Error("pipeup: there is no comment with that id");return t}mine(e){let t=this.find(e);if(t.comment.author!==this.me)throw new Error("pipeup: only the writer can change a comment");return t}thread(e){if(!this.cache.some(t=>t.id===e))throw new Error("pipeup: there is no thread with that id");return e}};function V(e){let t=e.trim();if(!t)throw new Error("pipeup: a comment needs some words");if(t.length>w)throw new Error(`pipeup: comments are limited to ${w} characters`);return t}function W(e){if(!N(e))throw new Error("pipeup: a reaction is one emoji");return e}function Q(e){let t=e.replace(/\s+/g," ").trim();if(!t||t.length>80)throw new Error("pipeup: choose a name of 1 to 80 characters");if(y(t))throw new Error("pipeup: that name is kept for AI replies; choose another");return t}var X=class{docs=new Map;profile=null;async load(e){return[...this.docs.get(e)?.values()??[]]}async append(e,t){let r=this.docs.get(e);r||this.docs.set(e,r=new Map);for(let e of t)r.set(e.body.id,e)}async loadProfile(){return this.profile}async saveProfile(e){this.profile=e}async saveProfileIfAbsent(e){return this.profile||(this.profile=e),this.profile}},Y="";async function ee(e,t=""){let r=await e.loadProfile();if(r)return"Reviewer"===r.name?{...r,name:""}:r;let n={identity:await l(),name:t};return e.saveProfileIfAbsent(n)}var te=class e{constructor(e){this.db=e}static open(t="pipeup"){return new Promise((r,n)=>{let i=indexedDB.open(t,1);i.onupgradeneeded=()=>{let e=i.result;e.createObjectStore("ops",{keyPath:["doc","id"]}).createIndex("doc","doc"),e.createObjectStore("meta")},i.onsuccess=()=>{let t=i.result;t.onversionchange=()=>t.close(),r(new e(t))},i.onerror=()=>n(i.error??new Error("pipeup: could not open local storage"))})}close(){this.db.close()}async load(e){return(await this.request("ops","readonly",t=>t.index("doc").getAll(e))).map(e=>e.op)}async append(e,t){if(0===t.length)return;let r=this.db.transaction("ops","readwrite"),n=r.objectStore("ops"),i=re(r);try{for(let r of t)n.put({doc:e,id:r.body.id,op:r})}catch(e){throw r.abort(),i.catch(()=>{}),e}await i}async loadProfile(){return await this.request("meta","readonly",e=>e.get("profile"))??null}async saveProfile(e){await this.request("meta","readwrite",t=>t.put(e,"profile"))}async saveProfileIfAbsent(e){let t=this.db.transaction("meta","readwrite"),r=t.objectStore("meta"),n=re(t),i=e,o=r.get("profile");return o.onsuccess=()=>{o.result?i=o.result:r.put(e,"profile")},await n,i}request(e,t,r){return new Promise((n,i)=>{let o=r(this.db.transaction(e,t).objectStore(e));o.onsuccess=()=>n(o.result),o.onerror=()=>i(o.error??new Error("pipeup: local storage request failed"))})}};function re(e){return new Promise((t,r)=>{e.oncomplete=()=>t(),e.onerror=()=>r(e.error??new Error("pipeup: local storage write failed")),e.onabort=()=>r(e.error??new Error("pipeup: local storage write aborted"))})}function ne(e){let t=(e.ownerDocument??e).createTreeWalker(e,NodeFilter.SHOW_TEXT),r=[],n="";for(let e=t.nextNode();e;e=t.nextNode())r.push({node:e,start:n.length}),n+=e.data;return{text:n,nodes:r}}function ie(e,t,r){let n=(e.ownerDocument??e).createRange();return n.setStart(e,0),n.setEnd(t,r),n.toString().length}function oe(e,t,r){let n=ae(e,t,!1),i=ae(e,r,!0);if(!n||!i||r<t)return null;let o=n.node.ownerDocument.createRange();return o.setStart(n.node,n.offset),o.setEnd(i.node,i.offset),o}function ae(e,t,r){let n=e.nodes.find(e=>t>=e.start&&t<e.start+e.node.data.length+Number(r));return n&&{node:n.node,offset:t-n.start}}function se(e){return e.nodeType===Node.ELEMENT_NODE?e:e.parentElement}function le(e,t){for(let r=se(e)?.closest("[data-pipeup-id]")??null;r;){if(!t.contains(r))return null;let e=r.getAttribute("data-pipeup-id")??"";if(""!==e&&e.length<=200)return r;r=r.parentElement?.closest("[data-pipeup-id]")??null}return null}function de(e,t,r){return e.slice(t,r).replace(/^[\uDC00-\uDFFF]|[\uD800-\uDBFF]$/g,"")}function ue(e,t){let r=function(e,t){if(e===t)return"";let r=[];for(let n=t;n&&n!==e;n=n.parentElement){let e=1;for(let t=n.previousElementSibling;t;t=t.previousElementSibling)t.tagName===n.tagName&&e++;r.unshift(`${n.tagName.toLowerCase()}:nth-of-type(${e})`)}return":scope > "+r.join(" > ")}(e,t);if(r.length>2e3)throw new Error("pipeup: this element is nested too deeply to comment on — give a nearby container a data-pipeup-id");return r}function ce(e){if(!T(e))throw new Error("pipeup: could not describe this place on the page");return e}function he(e,t){let r=se(e),n=r?.closest("p,li,h1,h2,h3,h4,h5,h6,td,th,blockquote,pre,figcaption,dt,dd,summary,caption")??null;return n&&t.contains(n)?n:r&&t.contains(r)?r:t}function pe(e,t,r){if(!t.contains(e.commonAncestorContainer))throw new Error("pipeup: the selection is outside the commentable area");let n=le(e.commonAncestorContainer,t),a=n??t,{text:s}=ne(a),l=ie(a,e.startContainer,e.startOffset),d=ie(a,e.endContainer,e.endOffset),u=s.slice(l,d);if(""===u)throw new Error("pipeup: select some text to comment on");if(u.length>v)throw new Error("pipeup: selections are limited to 10000 characters");let c=de(s,Math.max(0,l-32),l),h=de(s,d,d+32);return ce({...n?{id:n.getAttribute("data-pipeup-id")??""}:{},path:"",quote:{exact:u,prefix:c,suffix:h},...r?{view:r}:{},fingerprint:o(he(e.startContainer,t).textContent??""),snapshot:de(i(c+u+h),0,200)})}function fe(e,t,r,n){let a=le(e,t);return ce({...a?{id:a.getAttribute("data-pipeup-id")??""}:{},path:ue(a??t,e),...r?{point:{x:me(r.x),y:me(r.y)}}:{},...n?{view:n}:{},fingerprint:o(e.textContent??""),snapshot:de(i(e.textContent??""),0,200)})}function me(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):0}function ge(e,t,r){let n=Array.from({length:t.length+1},(e,t)=>t),i=[...n],o=[t.length];for(let a=1;a<=e.length;a++){i[0]=r?0:a;for(let r=1;r<=t.length;r++){let o=e.charCodeAt(a-1)===t.charCodeAt(r-1)?0:1;i[r]=Math.min(n[r]+1,i[r-1]+1,n[r-1]+o)}o.push(i[t.length]),[n,i]=[i,n]}return o}var ye=(e,t)=>ge(e,t,!1).pop(),we={state:"orphaned",element:null,range:null};function be(e,t,r={}){let n=e.id?function(e,t){for(let r of e.querySelectorAll("[data-pipeup-id]"))if(r.getAttribute("data-pipeup-id")===t)return r;return null}(t,e.id):t;return e.quote?function(e,t,r,n,i){let a=r??n,s=ne(a),l=function(e,t){if(!t.exact)return-1;let r=-1,n=-1;for(let i=e.indexOf(t.exact);-1!==i;i=e.indexOf(t.exact,i+1)){let o=e.slice(Math.max(0,i-t.prefix.length),i),a=e.slice(i+t.exact.length,i+t.exact.length+t.suffix.length),s=ve(o,t.prefix)+xe(a,t.suffix);s>n&&(r=i,n=s)}return r}(s.text,t);if(l>=0){let i=oe(s,l,l+t.exact.length);if(i){let t=he(i.startContainer,n);return{state:null!==r&&o(t.textContent??"")===e.fingerprint?"attached":"moved",element:t,range:i}}}if(i){let e=a===n?s:ne(n);if(e.text.length<=5e4&&t.exact.length>=12&&t.exact.length<=300){let r=function(e,t,r){let n=t.length;if(0===n)return null;let i=-1,o=1/0;if(ge(e,t,!0).forEach((e,t)=>{t&&e<o&&(o=e,i=t)}),o>r)return null;let a=Math.max(0,i-n),s=1/0;for(let o=Math.max(0,i-n-r);o<=Math.min(i,i-n+r);o++){let r=ye(e.slice(o,i),t);r<s&&(s=r,a=o)}return{start:a,end:i,distance:s}}(e.text,t.exact,Math.floor(t.exact.length/4)),i=r?oe(e,r.start,r.end):null;if(i)return{state:"moved",element:he(i.startContainer,n),range:i}}}return we}(e,e.quote,n,t,!1!==r.fuzzy):function(e,t){if(!t)return we;let r=t;if(e.path){if(!e.path.startsWith(":scope > "))return we;try{r=t.querySelector(e.path)}catch{return we}}return r?{state:o(r.textContent??"")===e.fingerprint?"attached":"moved",element:r,range:null}:we}(e,n)}function xe(e,t){let r=0;for(;r<e.length&&r<t.length&&e[r]===t[r];)r++;return r}function ve(e,t){let r=0;for(;r<e.length&&r<t.length&&e[e.length-1-r]===t[t.length-1-r];)r++;return r}var ke={A:"Link",BUTTON:"Button",P:"Paragraph",LI:"List item",IMG:"Image",TABLE:"Table",TD:"Cell",TH:"Cell",FIGURE:"Figure",SVG:"Graphic",CANVAS:"Graphic",LABEL:"Label",INPUT:"Field",SELECT:"Field",TEXTAREA:"Field",SUMMARY:"Question",BLOCKQUOTE:"Quote",SECTION:"Section",H1:"Heading",H2:"Heading",H3:"Heading",H4:"Heading",H5:"Heading",H6:"Heading"};function Ae(e){return e.getAttribute("data-pipeup-label")||ke[e.tagName.toUpperCase()]||"Block"}function Ee(e){let t=e.getAttribute("data-pipeup-label");if(t)return t;let r=function(e){return Me(i(e.getAttribute("aria-label")??e.getAttribute("alt")??e.innerText??e.textContent??""),40)}(e);return r?`${Ae(e)} · ${r}`:Ae(e)}function $e(e,t,r){let n=e.id??null,o=e.quote?.exact??null,a=e.point?`${Math.round(100*e.point.x)}% across, ${Math.round(100*e.point.y)}% down the element`:null,s=t.element;if("orphaned"===t.state||!s)return{where:`No longer on the page (it read: "${e.snapshot}")`,element:null,id:n,quote:o,pin:a};let l=[],d=e.view?.slide;if(d){let e=s.closest("[data-pipeup-slide]")?.querySelector("h1,h2,h3")?.textContent;l.push(`Slide ${d}`+(e?` "${Me(i(e))}"`:""))}else{let t=function(e){return e?e.label||Object.entries(e).filter(([e,t])=>t&&"slide"!==e).map(([,e])=>e).join(" · "):""}(e.view);t&&l.push(t);let n=function(e,t){let r=null;for(let n of t.querySelectorAll("h1,h2,h3")){if(n===e||n.contains(e))break;if(!(4&n.compareDocumentPosition(e)))break;r=n}return r}(s,r);n&&l.push(`Section "${Me(i(n.textContent??""))}"`)}let u=Ee(s);return l.push(u),{where:l.join(" › "),element:u,id:n,quote:o,pin:a}}function Me(e,t=60){return e.length>t?e.slice(0,t-1)+"…":e}var Se="Each thread says where it is: the slide or section, the element (its data-pipeup-id attribute in the HTML), and the exact quoted text or pin position. Work through the open threads in order, and for each one say what you changed or why you did not. Answer in the thread (the Pipeup skill says how) and mark it addressed; never resolve it, the author does.";function Ce(e,t){let r=Math.max(0,Math.round((t-e)/1e3));if(r<60)return"just now";let n=Math.round(r/60);if(n<60)return`${n}m ago`;let i=Math.round(n/60);return i<48?`${i}h ago`:`${Math.round(i/24)}d ago`}function Ne(e,t,r,n){return"ai"===t?je(e,"## Comment thread",n,r):Te(e)}function Oe(e,t,r){let n=r.all?e:e.filter(e=>!e.thread.resolved);if("text"===t)return n.map(Te).join("\n");let i=r.all?0:e.length-n.length,o=[`# Review comments: ${qe(r.title)}`,"",`- **Page:** ${qe(r.url)}`,`- **Exported:** ${r.exportedAt.toISOString()}`,`- **Open threads:** ${e.length-e.filter(e=>e.thread.resolved).length}${i?` (${i} resolved, not included)`:r.all?" (resolved ones included)":""}`,"",Se,"",""].join("\n"),a=r.exportedAt.getTime();return o+n.map((e,t)=>je(e,`## Thread ${t+1}`,a)).join("\n")}function Pe(e){return e.reactions.map(e=>`${e.emoji} ${e.by.length} (${e.by.map(e=>qe(m(e))).join(", ")})`).join(" · ")}function qe(e){return e.replace(/\s+/g," ").trim()}function je(e,t,r,n){let{thread:i,location:o}=e,a=i.root.replies.length,s=[`${t} · ${i.resolved?"resolved":i.addressed?"open, addressed":"open"}`];if(s.push(`- **Thread:** ${i.id}`),n&&s.push(`- **Page:** ${qe(n)}`),s.push(`- **Where:** ${qe(o.where)}`),o.id){let e=qe(o.id.replace(/`/g,""));s.push(`- **Element:** \`[data-pipeup-id="${e}"]\`${o.element?` (${qe(o.element)})`:""}`)}o.quote&&s.push(`- **Quoted text:** "${qe(o.quote)}"`),o.pin&&s.push(`- **Pin:** ${qe(o.pin)}`),i.addressed&&!i.resolved&&s.push(`- **Addressed:** ${qe(i.addressed.text)} (${qe(m(i.addressed))}, ${Ce(i.addressed.at,r)})`),s.push(`- **Started by:** ${qe(m(i.root))}, ${Ce(i.root.at,r)}${a?` · ${a} ${1===a?"reply":"replies"}`:""}`,"",`**${qe(m(i.root))}** (${Ce(i.root.at,r)}): ${Ie(i.root," ")}`),i.root.reactions.length&&s.push(`Reactions: ${Pe(i.root)}`);for(let e of i.root.replies)s.push(`- **${qe(m(e))}** (${Ce(e.at,r)}): ${Ie(e," ")} \x3c!-- comment:${e.id} --\x3e`),e.reactions.length&&s.push(` - Reactions: ${Pe(e)}`);return s.join("\n")+"\n"}function Te(e){let{thread:t,location:r}=e,n=[qe(r.where)+(r.quote?`, on the text "${qe(r.quote)}"`:""),`${qe(m(t.root))}: ${Ie(t.root," ")}`];t.root.reactions.length&&n.push(` Reactions: ${Pe(t.root)}`);for(let e of t.root.replies)n.push(` ${qe(m(e))}: ${Ie(e," ")}`),e.reactions.length&&n.push(` Reactions: ${Pe(e)}`);return n.join("\n")+"\n"}function Ie(e,t){return e.deleted?"(deleted)":e.text.replace(/\r\n|\r|\n/g,"\n"+t)+(e.edited?" (edited)":"")}var Fe="0.7.0",De="2026-10-13";export{g as AI_NAME,c as ANIMALS,De as BUILT,h as COLOURS,Y as DEFAULT_PROFILE_NAME,te as IndexedDbStore,X as MemoryStore,J as PipeupDocument,Z as UnsavedChangeError,Fe as VERSION,f as animalName,U as computeOpId,Oe as copyAll,Ne as copyThread,l as createIdentity,fe as describeElement,pe as describeRange,Ce as formatAgo,y as isAiName,Ee as labelOf,ee as loadOrCreateProfile,$e as locate,m as nameOf,L as newDocumentAttribute,_ as parseDocumentAttribute,be as resolveAnchor,O as signOp};
|