@devaccel-labs/comment-service 0.7.0 → 0.9.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/dist/react.js CHANGED
@@ -1,3784 +1,86 @@
1
- // ../core/dist/resolve.js
2
- var norm = (value) => (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
3
- var labelOf = (value) => norm(value.accName) || norm(value.text);
4
- function isAncestorPath(ancestor, descendant) {
5
- if (!ancestor || ancestor === descendant)
6
- return false;
7
- return descendant.startsWith(`${ancestor}/`);
8
- }
9
- function dropAncestors(candidates) {
10
- return candidates.filter((candidate) => !candidates.some((other) => isAncestorPath(candidate.path, other.path)));
11
- }
12
- function finish(status, candidate, score, reason) {
13
- if (candidate && !candidate.visible && (status === "pinned" || status === "drifted")) {
14
- return { status: "hidden", key: candidate.key, score, reason: `hidden: ${reason}` };
15
- }
16
- return { status, key: candidate?.key ?? null, score, reason };
17
- }
18
- function sameExplicit(anchor, candidate) {
19
- return anchor.explicit.some((expected) => candidate.explicit.some((actual) => actual.attr === expected.attr && actual.value === expected.value));
20
- }
21
- function sameComponent(anchor, candidate, requireFile) {
22
- if (!anchor.component || !candidate.component)
23
- return false;
24
- if (anchor.component.name !== candidate.component.name)
25
- return false;
26
- if (requireFile) {
27
- return Boolean(anchor.component.file) && anchor.component.file === candidate.component.file;
28
- }
29
- return true;
30
- }
31
- function sameRole(anchor, candidate) {
32
- if (!anchor.role || !candidate.role)
33
- return !anchor.role;
34
- return anchor.role === candidate.role;
35
- }
36
- function sameLabel(anchor, candidate) {
37
- const expected = labelOf(anchor);
38
- if (!expected)
39
- return false;
40
- return expected === labelOf(candidate);
41
- }
42
- function unavailable(reason) {
43
- return { status: "unavailable", key: null, score: 0, reason };
44
- }
45
- function resolveAnchor(anchor, candidates) {
46
- const pool = candidates.filter((candidate) => candidate.route === anchor.route);
47
- if (anchor.explicit.length > 0) {
48
- const hits = dropAncestors(pool.filter((candidate) => sameExplicit(anchor, candidate)));
49
- if (hits.length === 1)
50
- return finish("pinned", hits[0], 100, "explicit id");
51
- if (hits.length > 1) {
52
- return { status: "ambiguous", key: null, score: 40, reason: "explicit id matches more than one element" };
53
- }
54
- }
55
- const componentScoped = pool.filter((candidate) => sameComponent(anchor, candidate, true) && sameRole(anchor, candidate));
56
- const labeled = dropAncestors(componentScoped.filter((candidate) => sameLabel(anchor, candidate)));
57
- if (labeled.length === 1)
58
- return finish("pinned", labeled[0], 100, "component file, role, and label");
59
- if (labeled.length > 1) {
60
- return {
61
- status: "ambiguous",
62
- key: null,
63
- score: 50,
64
- reason: "several elements in this component share the same label"
65
- };
66
- }
67
- if (anchor.component?.file && anchor.component.name) {
68
- const roots = pool.filter((candidate) => candidate.componentRoot && sameComponent(anchor, candidate, true));
69
- if (roots.length === 1) {
70
- const root = roots[0];
71
- const inside = dropAncestors(pool.filter((candidate) => (candidate.path === root.path || isAncestorPath(root.path, candidate.path)) && sameRole(anchor, candidate)));
72
- if (inside.length === 1) {
73
- return finish("drifted", inside[0], 70, "same component and role, label changed");
74
- }
75
- const where2 = anchor.overlay ? ` Last seen in ${anchor.overlay.role} \u201C${anchor.overlay.name}\u201D.` : "";
76
- return unavailable(`label no longer matches inside ${anchor.component.name}.${where2}`.trim());
77
- }
78
- if (roots.length === 0) {
79
- const where2 = anchor.overlay ? ` Last seen in ${anchor.overlay.role} \u201C${anchor.overlay.name}\u201D.` : "";
80
- return unavailable(`component ${anchor.component.name} is not on ${anchor.route}.${where2}`.trim());
81
- }
82
- return {
83
- status: "ambiguous",
84
- key: null,
85
- score: 45,
86
- reason: "component is on the page more than once and the label no longer matches"
87
- };
88
- }
89
- if (anchor.component?.name) {
90
- const named = dropAncestors(pool.filter((candidate) => sameComponent(anchor, candidate, false) && sameRole(anchor, candidate) && sameLabel(anchor, candidate)));
91
- if (named.length === 1)
92
- return finish("pinned", named[0], 80, "component name and label");
93
- if (named.length > 1) {
94
- return { status: "ambiguous", key: null, score: 40, reason: "component name and label match more than one element" };
95
- }
96
- }
97
- const where = anchor.overlay ? ` It was inside ${anchor.overlay.role} \u201C${anchor.overlay.name}\u201D.` : "";
98
- return unavailable(`no element on ${anchor.route} matches this comment.${where}`.trim());
99
- }
100
-
101
- // ../react/dist/capture.js
102
- var EXPLICIT = ["data-cs-anchor", "data-testid", "data-test", "id"];
103
- function clip(value, max = 160) {
104
- const text = (value ?? "").replace(/\s+/g, " ").trim();
105
- return text ? text.slice(0, max) : null;
106
- }
107
- function usableExplicit(attr, value) {
108
- if (!value || value.length > 200)
109
- return false;
110
- if (attr === "id" || attr === "data-testid" || attr === "data-test") {
111
- if (value.startsWith(":") || value.startsWith("radix-") || value.startsWith("headlessui-"))
112
- return false;
113
- }
114
- return true;
115
- }
116
- function domPath(element) {
117
- const parts = [];
118
- let current = element;
119
- while (current && current !== current.ownerDocument.body) {
120
- const parent = current.parentElement;
121
- if (!parent)
122
- break;
123
- parts.push([...parent.children].indexOf(current));
124
- current = parent;
125
- }
126
- return parts.reverse().join("/");
127
- }
128
- function intraPath(element, owner) {
129
- if (!owner || owner === element)
130
- return element === owner ? element.tagName.toLowerCase() : null;
131
- const parts = [];
132
- let current = element;
133
- while (current && current !== owner) {
134
- const parent = current.parentElement;
135
- if (!parent)
136
- break;
137
- const same = [...parent.children].filter((child) => child.tagName === current.tagName);
138
- const index = same.indexOf(current);
139
- const tag = current.tagName.toLowerCase();
140
- parts.push(same.length > 1 ? `${tag}[${index}]` : tag);
141
- current = parent;
142
- }
143
- return parts.reverse().join("/") || null;
144
- }
145
- function elementText(element) {
146
- let direct = "";
147
- for (const node of element.childNodes) {
148
- if (node.nodeType === Node.TEXT_NODE)
149
- direct += node.textContent ?? "";
150
- }
151
- const own = clip(direct);
152
- if (own)
153
- return own;
154
- const full = clip(element.textContent, 80);
155
- if (full && element.children.length <= 3)
156
- return full;
157
- return null;
158
- }
159
- function labelFor(element) {
160
- const aria = element.getAttribute("aria-label");
161
- if (aria)
162
- return clip(aria);
163
- const labelledby = element.getAttribute("aria-labelledby");
164
- if (labelledby) {
165
- const text = labelledby.split(/\s+/).map((id) => element.ownerDocument.getElementById(id)?.textContent ?? "").join(" ");
166
- const clipped = clip(text);
167
- if (clipped)
168
- return clipped;
169
- }
170
- if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
171
- if (element.labels?.[0]?.textContent)
172
- return clip(element.labels[0].textContent);
173
- if (element.placeholder)
174
- return clip(element.placeholder);
175
- }
176
- if (element instanceof HTMLImageElement && element.alt)
177
- return clip(element.alt);
178
- return elementText(element);
179
- }
180
- function roleFor(element) {
181
- const explicit = element.getAttribute("role");
182
- if (explicit)
183
- return explicit;
184
- const tag = element.tagName.toLowerCase();
185
- if (tag === "button")
186
- return "button";
187
- if (tag === "a" && element.hasAttribute("href"))
188
- return "link";
189
- if (tag === "textarea")
190
- return "textbox";
191
- if (tag === "select")
192
- return "combobox";
193
- if (tag === "dialog")
194
- return "dialog";
195
- if (/^h[1-6]$/.test(tag))
196
- return "heading";
197
- if (element instanceof HTMLInputElement)
198
- return element.type === "checkbox" ? "checkbox" : "textbox";
199
- return null;
200
- }
201
- function overlayFor(element) {
202
- const node = element.closest("dialog,[role='dialog'],[role='alertdialog']");
203
- if (!node)
204
- return null;
205
- const role = node.getAttribute("role") || (node.tagName === "DIALOG" ? "dialog" : "dialog");
206
- return { role, name: labelFor(node) || "dialog" };
207
- }
208
- function fiberNames(element) {
209
- const key2 = Object.keys(element).find((name) => name.startsWith("__reactFiber$") || name.startsWith("__reactInternalInstance$"));
210
- if (!key2)
211
- return [];
212
- const names2 = [];
213
- let fiber = element[key2];
214
- for (let guard = 0; fiber && guard < 40; guard++) {
215
- const type = fiber.type;
216
- if (typeof type === "function") {
217
- const fn = type;
218
- const name = fn.displayName || fn.name;
219
- if (name && !name.startsWith("_"))
220
- names2.push(name);
221
- }
222
- fiber = fiber.return;
223
- }
224
- return names2.reverse();
225
- }
226
- function obviouslyHidden(element) {
227
- const dialog = element.closest("dialog");
228
- if (dialog instanceof HTMLDialogElement && !dialog.open)
229
- return true;
230
- if (element.closest("[hidden]"))
231
- return true;
232
- let current = element;
233
- while (current) {
234
- if (current.getAttribute("aria-hidden") === "true")
235
- return true;
236
- current = current.parentElement;
237
- }
238
- return false;
239
- }
240
- function ownership(element) {
241
- const stack = [];
242
- let owner = null;
243
- let current = element;
244
- while (current) {
245
- const name2 = current.getAttribute("data-cs-cmp");
246
- if (name2) {
247
- if (!owner)
248
- owner = current;
249
- stack.push(name2);
250
- }
251
- current = current.parentElement;
252
- }
253
- if (owner) {
254
- return {
255
- component: { name: owner.getAttribute("data-cs-cmp") || "component", file: owner.getAttribute("data-cs-file") },
256
- stack: stack.reverse(),
257
- componentRoot: owner === element,
258
- owner
259
- };
260
- }
261
- const names2 = fiberNames(element);
262
- const name = names2.at(-1) ?? null;
263
- return {
264
- component: name ? { name, file: null } : null,
265
- stack: names2,
266
- componentRoot: false,
267
- owner: null
268
- };
269
- }
270
- function candidateFromElement(element, route, key2) {
271
- const owned = ownership(element);
272
- const label = labelFor(element);
273
- const explicit = EXPLICIT.flatMap((attr) => {
274
- const value = element.getAttribute(attr);
275
- return value && usableExplicit(attr, value) ? [{ attr, value }] : [];
276
- });
277
- return {
278
- key: key2,
279
- route,
280
- visible: !obviouslyHidden(element),
281
- path: domPath(element),
282
- componentRoot: owned.componentRoot,
283
- overlay: overlayFor(element),
284
- explicit,
285
- component: owned.component,
286
- stack: owned.stack,
287
- role: roleFor(element),
288
- accName: label,
289
- text: label,
290
- tag: element.tagName.toLowerCase(),
291
- intra: intraPath(element, owned.owner)
292
- };
293
- }
294
- function captureAnchor(element, route = window.location.pathname) {
295
- const candidate = candidateFromElement(element, route, "target");
296
- return {
297
- v: 1,
298
- route,
299
- overlay: candidate.overlay,
300
- explicit: candidate.explicit,
301
- component: candidate.component,
302
- stack: candidate.stack,
303
- role: candidate.role,
304
- accName: candidate.accName,
305
- text: candidate.text,
306
- tag: candidate.tag,
307
- intra: candidate.intra
308
- };
309
- }
310
- function collectCandidates(route = window.location.pathname) {
311
- const root = document.getElementById("cs-root");
312
- const found = [];
313
- const nodes = document.body?.querySelectorAll("*") ?? [];
314
- let index = 0;
315
- for (const element of nodes) {
316
- if (root && (element === root || root.contains(element)))
317
- continue;
318
- const tag = element.tagName;
319
- if (tag === "SCRIPT" || tag === "STYLE" || tag === "LINK" || tag === "META")
320
- continue;
321
- if (index > 8e3)
322
- break;
323
- found.push({ element, candidate: candidateFromElement(element, route, `e${index}`) });
324
- index += 1;
325
- }
326
- return found;
327
- }
328
- function resolveInDocument(anchor, route = window.location.pathname) {
329
- const found = collectCandidates(route);
330
- const resolution = resolveAnchor(anchor, found.map((item) => item.candidate));
331
- const element = found.find((item) => item.candidate.key === resolution.key)?.element ?? null;
332
- return { resolution, element };
333
- }
334
- function elementAt(x, y) {
335
- const root = document.getElementById("cs-root");
336
- for (const element of document.elementsFromPoint(x, y)) {
337
- if (element === document.body || element === document.documentElement)
338
- continue;
339
- if (root && (element === root || root.contains(element)))
340
- continue;
341
- const tag = element.tagName;
342
- if (tag === "SCRIPT" || tag === "STYLE")
343
- continue;
344
- return element;
345
- }
346
- return null;
347
- }
348
- function describeElement(element) {
349
- const name = element.closest("[data-cs-cmp]")?.getAttribute("data-cs-cmp");
350
- const label = labelFor(element);
351
- if (name && label)
352
- return `${name} \u2014 ${label}`;
353
- return name || label || element.tagName.toLowerCase();
354
- }
355
-
356
- // ../react/dist/toolbar.js
357
- import { jsx as _jsx6, jsxs as _jsxs6, Fragment as _Fragment3 } from "react/jsx-runtime";
358
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
359
- import { createPortal as createPortal2 } from "react-dom";
360
-
361
- // ../react/dist/client.js
362
- var ApiError = class extends Error {
363
- status;
364
- code;
365
- constructor(status, code, message) {
366
- super(message);
367
- this.status = status;
368
- this.code = code;
369
- }
370
- };
371
- function storageKey(session) {
372
- return `devaccel.comment.${session.apiUrl}.${session.project}.${session.environment}`;
373
- }
374
- function readJwt(token) {
375
- try {
376
- const part = token.split(".")[1];
377
- if (!part)
378
- return null;
379
- const pad = part.length % 4 === 0 ? "" : "=".repeat(4 - part.length % 4);
380
- return JSON.parse(atob(part.replaceAll("-", "+").replaceAll("_", "/") + pad));
381
- } catch {
382
- return null;
383
- }
384
- }
385
- function memberIsCurrent(token) {
386
- const claims = readJwt(token);
387
- if (!claims || claims.typ !== "member" || !claims.name || !claims.email)
388
- return false;
389
- return (claims.exp ?? 0) * 1e3 > Date.now() + 5e3;
390
- }
391
- async function request(url, token, init) {
392
- const response = await fetch(url, {
393
- ...init,
394
- headers: {
395
- authorization: `Bearer ${token}`,
396
- "content-type": "application/json",
397
- ...init?.headers ?? {}
398
- }
399
- });
400
- const payload = await response.json().catch(() => null);
401
- if (!response.ok) {
402
- throw new ApiError(response.status, payload?.error?.code ?? "request_failed", payload?.error?.message ?? "Request failed.");
403
- }
404
- return payload;
405
- }
406
- async function unlock(session, displayName, email, secret) {
407
- const payload = await request(`${session.apiUrl}/v1/auth`, session.hostToken, {
408
- method: "POST",
409
- body: JSON.stringify({ hostToken: session.hostToken, displayName, email, ...secret })
410
- });
411
- return payload.token;
412
- }
413
- async function createInvite(session, memberToken) {
414
- return request(`${session.apiUrl}/v1/invites`, memberToken, {
415
- method: "POST"
416
- });
417
- }
418
- async function getProfile(session, memberToken) {
419
- return request(`${session.apiUrl}/v1/me`, memberToken);
420
- }
421
- async function updateProfile(session, memberToken, patch) {
422
- return request(`${session.apiUrl}/v1/me`, memberToken, {
423
- method: "POST",
424
- body: JSON.stringify(patch)
425
- });
426
- }
427
- async function uploadAttachment(session, memberToken, file, name) {
428
- const response = await fetch(`${session.apiUrl}/v1/attachments`, {
429
- method: "POST",
430
- headers: {
431
- authorization: `Bearer ${memberToken}`,
432
- "content-type": file.type || "application/octet-stream",
433
- "x-file-name": encodeURIComponent(name)
434
- },
435
- body: file
436
- });
437
- const payload = await response.json().catch(() => null);
438
- if (!response.ok || !payload?.attachment) {
439
- throw new ApiError(response.status, payload?.error?.code ?? "upload_failed", payload?.error?.message ?? "Upload failed.");
440
- }
441
- return payload.attachment;
442
- }
443
- async function listMembers(session, memberToken) {
444
- const payload = await request(`${session.apiUrl}/v1/members`, memberToken);
445
- return payload.members;
446
- }
447
- async function listThreads(session, memberToken) {
448
- return request(`${session.apiUrl}/v1/threads`, memberToken);
449
- }
450
- async function saveSettings(session, memberToken, settings) {
451
- const payload = await request(`${session.apiUrl}/v1/settings`, memberToken, {
452
- method: "PUT",
453
- body: JSON.stringify({ settings })
454
- });
455
- return payload.settings;
456
- }
457
- async function createThread(session, memberToken, anchor, body, extras = {}) {
458
- return request(`${session.apiUrl}/v1/threads`, memberToken, {
459
- method: "POST",
460
- body: JSON.stringify({ anchor, body, ...extras })
461
- });
462
- }
463
- async function replyToThread(session, memberToken, threadId, body, extras = {}) {
464
- return request(`${session.apiUrl}/v1/threads/${threadId}/replies`, memberToken, {
465
- method: "POST",
466
- body: JSON.stringify({ body, ...extras })
467
- });
468
- }
469
- async function updateThread(session, memberToken, threadId, patch) {
470
- return request(`${session.apiUrl}/v1/threads/${threadId}`, memberToken, {
471
- method: "PATCH",
472
- body: JSON.stringify(patch)
473
- });
474
- }
475
-
476
- // ../react/dist/details.js
477
- import { jsx as _jsx2, jsxs as _jsxs2, Fragment as _Fragment } from "react/jsx-runtime";
478
- import { useState } from "react";
479
-
480
- // ../react/dist/icons.js
481
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
482
- function Icon({ children, size = 16 }) {
483
- return _jsx("svg", { className: "cs-icon", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
484
- }
485
- var AddComment = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }), _jsx("path", { d: "M12 7v6M9 10h6" })] });
486
- var Inbox = () => _jsxs(Icon, { size: 17, children: [_jsx("polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }), _jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] });
487
- var Eye = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] });
488
- var EyeOff = () => _jsxs(Icon, { size: 17, children: [_jsx("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }), _jsx("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }), _jsx("path", { d: "M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }), _jsx("path", { d: "M2 2l20 20" })] });
489
- var Lock = ({ size = 15 }) => _jsxs(Icon, { size, children: [_jsx("rect", { width: "16", height: "10", x: "4", y: "11", rx: "2" }), _jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })] });
490
- var Close = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
491
- var Send = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m5 12 7-7 7 7M12 19V5" }) });
492
- var Check = () => _jsx(Icon, { size: 14, children: _jsx("polyline", { points: "20 6 9 17 4 12" }) });
493
- var Target = () => _jsxs(Icon, { size: 14, children: [_jsx("circle", { cx: "12", cy: "12", r: "8" }), _jsx("circle", { cx: "12", cy: "12", r: "2.5" })] });
494
- var Relink = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 1 1 0 10h-2M8 12h8" }) });
495
- var Undo = () => _jsxs(Icon, { size: 14, children: [_jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), _jsx("path", { d: "M3 3v5h5" })] });
496
- var Pencil = () => _jsx(Icon, { size: 13, children: _jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
497
- var Cursor = () => _jsx(Icon, { size: 14, children: _jsx("path", { d: "m4 4 7.07 17 2.51-7.39L21 11.07z" }) });
498
- var Paperclip = () => _jsx(Icon, { size: 15, children: _jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
499
- var Camera = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" }), _jsx("circle", { cx: "12", cy: "13", r: "3" })] });
500
- var FileIcon = () => _jsxs(Icon, { size: 15, children: [_jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }), _jsx("polyline", { points: "14 2 14 8 20 8" })] });
501
- var Back = () => _jsx(Icon, { children: _jsx("path", { d: "m15 18-6-6 6-6" }) });
502
- var Table = () => _jsxs(Icon, { children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }), _jsx("path", { d: "M3 10h18M3 15h18M9 10v10" })] });
503
- var List = () => _jsx(Icon, { children: _jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01" }) });
504
-
505
- // ../react/dist/details.js
506
- var noDetails = { role: null, fields: {} };
507
- function detailsEnabled(settings) {
508
- if (!settings)
509
- return false;
510
- return settings.roles.enabled && settings.roles.options.length > 0 || settings.fields.enabled && settings.fields.items.length > 0;
511
- }
512
- function hasFieldValues(details) {
513
- return Object.values(details.fields).some((value) => value.trim());
514
- }
515
- function detailsPayload(settings, details) {
516
- if (!settings)
517
- return {};
518
- const payload = {};
519
- if (settings.roles.enabled && details.role)
520
- payload.role = details.role;
521
- if (settings.fields.enabled) {
522
- const fields = Object.fromEntries(Object.entries(details.fields).filter(([, value]) => value.trim()));
523
- if (Object.keys(fields).length)
524
- payload.fields = fields;
525
- }
526
- return payload;
527
- }
528
- function fieldId(label, taken) {
529
- const base = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "field";
530
- let id = base;
531
- for (let n = 2; taken.some((field) => field.id === id); n += 1)
532
- id = `${base}-${n}`;
533
- return id;
534
- }
535
- function DetailsInput({ settings, value, onChange, onKeyDown }) {
536
- if (!settings || !detailsEnabled(settings))
537
- return null;
538
- const roles = settings.roles.enabled ? settings.roles.options : [];
539
- const fields = settings.fields.enabled ? settings.fields.items : [];
540
- return _jsxs2("div", { className: "cs-details-input", "data-cs-ui": "details-input", children: [fields.map((field) => _jsxs2("label", { className: "cs-detail-field", children: [_jsx2("span", { children: field.label }), _jsx2("textarea", { "data-cs-ui": `field-${field.id}`, rows: 1, maxLength: 4e3, value: value.fields[field.id] ?? "", onChange: (event) => onChange({ ...value, fields: { ...value.fields, [field.id]: event.target.value } }), onKeyDown })] }, field.id)), roles.length ? _jsxs2("div", { className: "cs-roles", role: "radiogroup", "aria-label": "Role", children: [_jsx2("span", { className: "cs-roles-label", children: "Role" }), roles.map((role) => _jsx2("button", { type: "button", role: "radio", "data-cs-ui": "role-option", "aria-checked": value.role === role, className: value.role === role ? "cs-chip is-on" : "cs-chip", onClick: () => onChange({ ...value, role: value.role === role ? null : role }), children: role }, role))] }) : null] });
541
- }
542
- function RoleBadge({ role }) {
543
- return role ? _jsx2("span", { className: "cs-role", "data-cs-ui": "role", children: role }) : null;
544
- }
545
- function FieldValues({ fields, clamp = false }) {
546
- if (!fields.length)
547
- return null;
548
- return _jsx2("dl", { className: clamp ? "cs-detail-list is-clamped" : "cs-detail-list", "data-cs-ui": "fields", children: fields.map((field) => _jsxs2("div", { children: [_jsx2("dt", { children: field.label }), _jsx2("dd", { children: field.value })] }, field.id)) });
549
- }
550
- function DetailsEditor({ settings, thread, onSave, onClose }) {
551
- const [value, setValue] = useState(() => ({
552
- role: thread.role,
553
- fields: Object.fromEntries(thread.fields.map((field) => [field.id, field.value]))
554
- }));
555
- const [busy, setBusy] = useState(false);
556
- return _jsxs2("form", { className: "cs-details-edit", "data-cs-ui": "details-editor", onSubmit: (event) => {
557
- event.preventDefault();
558
- const patch = {};
559
- if (settings.roles.enabled && value.role !== thread.role)
560
- patch.role = value.role;
561
- if (settings.fields.enabled) {
562
- patch.fields = Object.fromEntries(settings.fields.items.map((field) => [field.id, value.fields[field.id] ?? ""]));
563
- }
564
- setBusy(true);
565
- onSave(patch).then(onClose).finally(() => setBusy(false));
566
- }, children: [_jsx2(DetailsInput, { settings, value, onChange: setValue }), _jsxs2("div", { className: "cs-row-end", children: [_jsx2("button", { className: "cs-button", type: "button", onClick: onClose, children: "Cancel" }), _jsx2("button", { className: "cs-button is-primary", "data-cs-ui": "details-save", type: "submit", disabled: busy, children: "Save" })] })] });
567
- }
568
- function AddRow({ label, hook, onAdd }) {
569
- const [text, setText] = useState("");
570
- return _jsxs2("form", { className: "cs-add-row", onSubmit: (event) => {
571
- event.preventDefault();
572
- const next = text.trim().replace(/\s+/g, " ");
573
- if (!next)
574
- return;
575
- onAdd(next);
576
- setText("");
577
- }, children: [_jsx2("input", { "data-cs-ui": hook, "aria-label": label, placeholder: label, value: text, maxLength: 60, onChange: (event) => setText(event.target.value) }), _jsx2("button", { className: "cs-icon-button", type: "submit", "aria-label": label, disabled: !text.trim(), children: _jsx2(Check, {}) })] });
578
- }
579
- function SettingsPanel({ settings, onChange, onBack, error }) {
580
- return _jsxs2("section", { className: "cs-card cs-menu cs-settings cs-hit", "data-cs-ui": "settings", "aria-label": "Comment fields", children: [_jsxs2("header", { className: "cs-settings-head", children: [_jsx2("button", { className: "cs-icon-button", type: "button", "aria-label": "Back", onClick: onBack, children: _jsx2(Back, {}) }), _jsx2("h2", { children: "Comment fields" })] }), !settings ? _jsx2("p", { className: "cs-hint", children: "Loading\u2026" }) : _jsxs2(_Fragment, { children: [_jsxs2("button", { className: "cs-item", "data-cs-ui": "roles-toggle", type: "button", role: "switch", "aria-checked": settings.roles.enabled, onClick: () => onChange({ ...settings, roles: { ...settings.roles, enabled: !settings.roles.enabled } }), children: [_jsx2("span", { children: "Role" }), _jsx2("span", { className: settings.roles.enabled ? "cs-switch is-on" : "cs-switch" })] }), settings.roles.enabled ? _jsxs2("div", { className: "cs-settings-body", children: [settings.roles.options.length ? _jsx2("div", { className: "cs-chips", children: settings.roles.options.map((role) => _jsxs2("span", { className: "cs-chip is-static", "data-cs-ui": "role-chip", children: [role, _jsx2("button", { type: "button", "aria-label": `Remove ${role}`, onClick: () => onChange({ ...settings, roles: { ...settings.roles, options: settings.roles.options.filter((item) => item !== role) } }), children: _jsx2(Close, {}) })] }, role)) }) : null, _jsx2(AddRow, { label: "Add a role", hook: "role-add", onAdd: (role) => {
581
- if (settings.roles.options.some((item) => item.toLowerCase() === role.toLowerCase()))
582
- return;
583
- onChange({ ...settings, roles: { ...settings.roles, options: [...settings.roles.options, role.slice(0, 40)] } });
584
- } })] }) : null, _jsx2("div", { className: "cs-sep" }), _jsxs2("button", { className: "cs-item", "data-cs-ui": "fields-toggle", type: "button", role: "switch", "aria-checked": settings.fields.enabled, onClick: () => onChange({ ...settings, fields: { ...settings.fields, enabled: !settings.fields.enabled } }), children: [_jsx2("span", { children: "Custom fields" }), _jsx2("span", { className: settings.fields.enabled ? "cs-switch is-on" : "cs-switch" })] }), settings.fields.enabled ? _jsxs2("div", { className: "cs-settings-body", children: [settings.fields.items.map((field) => _jsxs2("div", { className: "cs-field-row", "data-cs-ui": "field-def", children: [_jsx2("span", { children: field.label }), _jsx2("button", { className: "cs-icon-button", type: "button", "aria-label": `Remove ${field.label}`, onClick: () => onChange({ ...settings, fields: { ...settings.fields, items: settings.fields.items.filter((item) => item.id !== field.id) } }), children: _jsx2(Close, {}) })] }, field.id)), _jsx2(AddRow, { label: "Add a field", hook: "field-add", onAdd: (label) => onChange({
585
- ...settings,
586
- fields: { ...settings.fields, items: [...settings.fields.items, { id: fieldId(label, settings.fields.items), label }] }
587
- }) })] }) : null, error ? _jsx2("p", { className: "cs-error cs-pad", children: error }) : null, _jsx2("p", { className: "cs-hint", children: "Shared with everyone here. Turning off or removing only affects new comments." })] })] });
588
- }
589
-
590
- // ../react/dist/format.js
591
- var palette = ["#007aff", "#34c759", "#ff9500", "#af52de", "#ff2d55", "#5ac8fa", "#5856d6", "#a2845e"];
592
- function initials(name) {
593
- const parts = name.trim().split(/\s+/).filter(Boolean);
594
- const letters = parts.length > 1 ? parts[0][0] + parts[parts.length - 1][0] : (parts[0] ?? "?").slice(0, 2);
595
- return letters.toUpperCase();
596
- }
597
- function colorFor(name) {
598
- let hash = 0;
599
- for (const char of name)
600
- hash = hash * 31 + char.charCodeAt(0) | 0;
601
- return palette[Math.abs(hash) % palette.length];
602
- }
603
- function ago(iso) {
604
- const seconds = Math.max(0, (Date.now() - Date.parse(iso)) / 1e3);
605
- if (seconds < 60)
606
- return "now";
607
- if (seconds < 3600)
608
- return `${Math.floor(seconds / 60)}m`;
609
- if (seconds < 86400)
610
- return `${Math.floor(seconds / 3600)}h`;
611
- if (seconds < 86400 * 30)
612
- return `${Math.floor(seconds / 86400)}d`;
613
- return new Date(iso).toLocaleDateString();
614
- }
615
- function targetName(anchor) {
616
- const owner = anchor.component?.name ?? anchor.tag;
617
- const label = (anchor.accName || anchor.text || "").trim();
618
- return label ? `${owner} \u203A ${label.length > 40 ? `${label.slice(0, 40)}\u2026` : label}` : owner;
619
- }
620
- function statusNote(status, anchor, onThisRoute) {
621
- switch (status) {
622
- case "pinned":
623
- case "resolved":
624
- return null;
625
- case "drifted":
626
- return { text: "Text changed", tone: "warn" };
627
- case "hidden":
628
- return { text: anchor.overlay ? `Inside \u201C${anchor.overlay.name}\u201D` : "Not visible right now", tone: "quiet" };
629
- case "ambiguous":
630
- return { text: "Matches more than one element", tone: "warn" };
631
- case "unavailable":
632
- return onThisRoute ? { text: "No longer on this page", tone: "bad" } : null;
633
- default: {
634
- const never = status;
635
- return never;
636
- }
637
- }
638
- }
639
- function names(people) {
640
- const list = people.map((person) => person.name);
641
- return list.length > 1 ? `${list.slice(0, -1).join(", ")} and ${list.at(-1)}` : list[0] ?? "";
642
- }
643
- function deliveryText(delivery) {
644
- if (!delivery)
645
- return null;
646
- const parts = [];
647
- if (delivery.emailed.length)
648
- parts.push(`Emailed ${names(delivery.emailed)}`);
649
- const limited = delivery.skipped.filter((item) => item.reason === "rate_limited");
650
- if (limited.length)
651
- parts.push(`Hourly email limit reached, ${names(limited)} not emailed`);
652
- const muted = delivery.skipped.filter((item) => item.reason === "muted");
653
- if (muted.length)
654
- parts.push(`${names(muted)} turned off mention emails`);
655
- if (delivery.skipped.some((item) => item.reason === "disabled"))
656
- parts.push("Email isn\u2019t set up for this app");
657
- return parts.length ? parts.join(" \xB7 ") : null;
658
- }
659
-
660
- // ../react/dist/attachments.js
661
- import { jsx as _jsx3, jsxs as _jsxs3 } from "react/jsx-runtime";
662
- import { useCallback, useEffect, useRef, useState as useState2 } from "react";
663
- var MAX_BYTES = 10 * 1024 * 1024;
664
- var MAX_FILES = 5;
665
- var ACCEPT = "image/png,image/jpeg,image/gif,image/webp,application/pdf,text/plain,text/csv,application/json,application/zip,video/mp4,video/webm,video/quicktime";
666
- var ALLOWED = new Set(ACCEPT.split(","));
667
- function formatSize(bytes) {
668
- if (bytes < 1024)
669
- return `${bytes} B`;
670
- if (bytes < 1024 * 1024)
671
- return `${Math.round(bytes / 1024)} KB`;
672
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
673
- }
674
- function useAttachments(session, memberToken, onError) {
675
- const [items, setItems] = useState2([]);
676
- const itemsRef = useRef(items);
677
- itemsRef.current = items;
678
- const patch = (key2, next) => setItems((current) => current.map((item) => item.key === key2 ? { ...item, ...next } : item));
679
- const add = useCallback((files) => {
680
- if (!session || !memberToken)
681
- return;
682
- const room = MAX_FILES - itemsRef.current.length;
683
- if (files.length > room)
684
- onError(`Up to ${MAX_FILES} files per message.`);
685
- for (const { blob, name } of files.slice(0, Math.max(0, room))) {
686
- if (!ALLOWED.has(blob.type)) {
687
- onError(`${name}: that file type isn\u2019t supported.`);
688
- continue;
689
- }
690
- if (blob.size > MAX_BYTES) {
691
- onError(`${name} is over 10 MB.`);
692
- continue;
693
- }
694
- const key2 = crypto.randomUUID();
695
- const preview = blob.type.startsWith("image/") ? URL.createObjectURL(blob) : null;
696
- setItems((current) => [...current, { key: key2, name, size: blob.size, contentType: blob.type, preview, status: "uploading" }]);
697
- uploadAttachment(session, memberToken, blob, name).then((attachment) => patch(key2, { status: "ready", id: attachment.id })).catch((caught) => {
698
- const message = caught instanceof Error ? caught.message : "Upload failed.";
699
- patch(key2, { status: "failed", error: message });
700
- onError(`${name}: ${message}`);
701
- });
702
- }
703
- }, [memberToken, onError, session]);
704
- const remove = useCallback((key2) => {
705
- setItems((current) => {
706
- const item = current.find((candidate) => candidate.key === key2);
707
- if (item?.preview)
708
- URL.revokeObjectURL(item.preview);
709
- return current.filter((candidate) => candidate.key !== key2);
710
- });
711
- }, []);
712
- const reset = useCallback(() => {
713
- for (const item of itemsRef.current)
714
- if (item.preview)
715
- URL.revokeObjectURL(item.preview);
716
- setItems([]);
717
- }, []);
718
- useEffect(() => () => itemsRef.current.forEach((item) => item.preview && URL.revokeObjectURL(item.preview)), []);
719
- return {
720
- items,
721
- add,
722
- remove,
723
- reset,
724
- ids: items.filter((item) => item.status === "ready" && item.id).map((item) => item.id),
725
- uploading: items.some((item) => item.status === "uploading")
726
- };
727
- }
728
- function filesFromClipboard(event) {
729
- return [...event.clipboardData.files].map((file) => ({ blob: file, name: file.name || "pasted-image.png" }));
730
- }
731
- function AttachmentTray({ items, onRemove }) {
732
- if (!items.length)
733
- return null;
734
- return _jsx3("div", { className: "cs-tray", "data-cs-ui": "attachment-tray", children: items.map((item) => _jsxs3("div", { className: `cs-pending is-${item.status}`, "data-cs-ui": "pending-file", "data-status": item.status, title: item.error ?? item.name, children: [item.preview ? _jsx3("img", { src: item.preview, alt: "" }) : _jsx3(FileIcon, {}), _jsx3("span", { className: "cs-pending-name", children: item.name }), _jsx3("button", { type: "button", className: "cs-pending-remove", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx3(Close, {}) })] }, item.key)) });
735
- }
736
- function AttachButtons({ onFiles, onSnapshot, capturing }) {
737
- const input = useRef(null);
738
- return _jsxs3("span", { className: "cs-attach", children: [_jsx3("input", { ref: input, type: "file", multiple: true, accept: ACCEPT, hidden: true, "data-cs-ui": "file-input", onChange: (event) => {
739
- onFiles([...event.target.files ?? []].map((file) => ({ blob: file, name: file.name })));
740
- event.target.value = "";
741
- } }), _jsx3("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "attach", "aria-label": "Attach files", onClick: () => input.current?.click(), children: _jsx3(Paperclip, {}) }), _jsx3("button", { type: "button", className: "cs-icon-button", "data-cs-ui": "snapshot", "aria-label": "Attach a screenshot of the page", disabled: capturing, onClick: onSnapshot, children: _jsx3(Camera, {}) })] });
742
- }
743
- function MessageAttachments({ attachments }) {
744
- if (!attachments?.length)
745
- return null;
746
- const images = attachments.filter((file) => file.contentType.startsWith("image/"));
747
- const others = attachments.filter((file) => !file.contentType.startsWith("image/"));
748
- return _jsxs3("div", { className: "cs-files", "data-cs-ui": "attachments", children: [images.length ? _jsx3("div", { className: "cs-thumbs", children: images.map((file) => _jsx3("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-thumb", title: file.name, "data-cs-ui": "attachment", children: _jsx3("img", { src: file.url, alt: file.name, loading: "lazy" }) }, file.id)) }) : null, others.map((file) => _jsxs3("a", { href: file.url, target: "_blank", rel: "noreferrer noopener", className: "cs-file", "data-cs-ui": "attachment", children: [_jsx3(FileIcon, {}), _jsx3("span", { className: "cs-file-name", children: file.name }), _jsx3("span", { className: "cs-file-size", children: formatSize(file.size) })] }, file.id))] });
749
- }
750
-
751
- // ../react/dist/mentions.js
752
- import { Fragment as _Fragment2, jsx as _jsx4, jsxs as _jsxs4 } from "react/jsx-runtime";
753
- import { useMemo, useRef as useRef2, useState as useState3 } from "react";
754
- import { createPortal } from "react-dom";
755
- function queryAt(text, caret) {
756
- const match = text.slice(0, caret).match(/(^|\s)@([^\s@]{0,30})$/);
757
- return match ? { query: match[2], start: caret - match[2].length - 1 } : null;
758
- }
759
- function escapeRegex(value) {
760
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
761
- }
762
- function mentionsIn(text, picked) {
763
- return [...picked.values()].filter((member) => text.includes(`@${member.name}`));
764
- }
765
- function MessageText({ body, mentions }) {
766
- if (!mentions?.length)
767
- return _jsx4(_Fragment2, { children: body });
768
- const pattern = new RegExp(`(@(?:${mentions.map((mention) => escapeRegex(mention.name)).join("|")}))`, "g");
769
- const parts = body.split(pattern).map((part, index) => index % 2 === 1 ? _jsx4("span", { className: "cs-mention", children: part }, index) : part);
770
- return _jsx4(_Fragment2, { children: parts });
771
- }
772
- function MentionTextarea({ value, onValueChange, members, selfEmail, onMention, onKeyDown, ...rest }) {
773
- const ref = useRef2(null);
774
- const [caret, setCaret] = useState3(0);
775
- const [index, setIndex] = useState3(0);
776
- const [dismissed, setDismissed] = useState3(null);
777
- const active = queryAt(value, caret);
778
- const query = active?.query.toLowerCase() ?? null;
779
- const matches = useMemo(() => {
780
- if (query == null)
781
- return [];
782
- return members.filter((member) => member.email !== selfEmail).filter((member) => member.name.toLowerCase().includes(query) || member.email.toLowerCase().startsWith(query)).slice(0, 6);
783
- }, [members, query, selfEmail]);
784
- const open = active != null && matches.length > 0 && dismissed !== active.start;
785
- const selected = matches[Math.min(index, matches.length - 1)];
786
- const insert = (member) => {
787
- if (!active)
788
- return;
789
- const next = `${value.slice(0, active.start)}@${member.name} ${value.slice(caret)}`;
790
- const position = active.start + member.name.length + 2;
791
- onValueChange(next);
792
- onMention(member);
793
- setIndex(0);
794
- requestAnimationFrame(() => {
795
- ref.current?.focus();
796
- ref.current?.setSelectionRange(position, position);
797
- setCaret(position);
798
- });
799
- };
800
- const rect = open ? ref.current?.getBoundingClientRect() : null;
801
- const root = open ? ref.current?.closest("#cs-root") : null;
802
- const below = rect ? window.innerHeight - rect.bottom > 220 : true;
803
- return _jsxs4(_Fragment2, { children: [_jsx4("textarea", { ref, ...rest, value, "aria-autocomplete": "list", "aria-expanded": open, onChange: (event) => {
804
- onValueChange(event.target.value);
805
- setCaret(event.target.selectionStart ?? event.target.value.length);
806
- setIndex(0);
807
- setDismissed(null);
808
- }, onSelect: (event) => setCaret(event.currentTarget.selectionStart ?? 0), onKeyDown: (event) => {
809
- if (open && selected && !event.metaKey && !event.ctrlKey) {
810
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
811
- event.preventDefault();
812
- const step = event.key === "ArrowDown" ? 1 : -1;
813
- setIndex((current) => (current + step + matches.length) % matches.length);
814
- return;
815
- }
816
- if (event.key === "Enter" || event.key === "Tab") {
817
- event.preventDefault();
818
- insert(selected);
819
- return;
820
- }
821
- if (event.key === "Escape") {
822
- event.preventDefault();
823
- event.stopPropagation();
824
- setDismissed(active.start);
825
- return;
826
- }
827
- }
828
- onKeyDown?.(event);
829
- } }), open && rect && root ? createPortal(_jsx4("div", { className: "cs-people cs-hit", role: "listbox", "data-cs-ui": "mention-list", style: {
830
- left: Math.min(rect.left, window.innerWidth - 272),
831
- ...below ? { top: rect.bottom + 6 } : { bottom: window.innerHeight - rect.top + 6 }
832
- }, children: matches.map((member) => _jsxs4("div", { role: "option", "aria-selected": member === selected, "data-cs-ui": "mention-option", className: member === selected ? "cs-person is-on" : "cs-person", onMouseDown: (event) => {
833
- event.preventDefault();
834
- insert(member);
835
- }, onMouseEnter: () => setIndex(matches.indexOf(member)), children: [_jsx4("span", { className: "cs-avatar", style: { width: 24, height: 24, fontSize: 10, background: colorFor(member.name) }, children: initials(member.name) }), _jsxs4("span", { className: "cs-person-text", children: [_jsx4("strong", { children: member.name }), _jsx4("small", { children: member.email })] })] }, member.id)) }), root) : null] });
836
- }
1
+ import {
2
+ captureHandoff,
3
+ runtimeVersion
4
+ } from "./chunks/chunk-KKE43QYT.js";
837
5
 
838
- // ../../node_modules/modern-screenshot/dist/index.mjs
839
- var _P = "p".charCodeAt(0);
840
- var _H = "H".charCodeAt(0);
841
- var _Y = "Y".charCodeAt(0);
842
- var _S = "s".charCodeAt(0);
843
- var PREFIX = "[modern-screenshot]";
844
- var IN_BROWSER = typeof window !== "undefined";
845
- var SUPPORT_WEB_WORKER = IN_BROWSER && "Worker" in window;
846
- var SUPPORT_ATOB = IN_BROWSER && "atob" in window;
847
- var SUPPORT_BTOA = IN_BROWSER && "btoa" in window;
848
- var USER_AGENT = IN_BROWSER ? window.navigator?.userAgent : "";
849
- var IN_CHROME = USER_AGENT.includes("Chrome");
850
- var IN_SAFARI = USER_AGENT.includes("AppleWebKit") && !IN_CHROME;
851
- var IN_FIREFOX = USER_AGENT.includes("Firefox");
852
- var isContext = (value) => value && "__CONTEXT__" in value;
853
- var isCssFontFaceRule = (rule) => rule.constructor.name === "CSSFontFaceRule";
854
- var isCSSImportRule = (rule) => rule.constructor.name === "CSSImportRule";
855
- var isLayerBlockRule = (rule) => rule.constructor.name === "CSSLayerBlockRule";
856
- var isElementNode = (node) => node.nodeType === 1;
857
- var isSVGElementNode = (node) => typeof node.className === "object";
858
- var isSVGImageElementNode = (node) => node.tagName === "image";
859
- var isSVGUseElementNode = (node) => node.tagName === "use";
860
- var isHTMLElementNode = (node) => isElementNode(node) && typeof node.style !== "undefined" && !isSVGElementNode(node);
861
- var isCommentNode = (node) => node.nodeType === 8;
862
- var isTextNode = (node) => node.nodeType === 3;
863
- var isImageElement = (node) => node.tagName === "IMG";
864
- var isVideoElement = (node) => node.tagName === "VIDEO";
865
- var isCanvasElement = (node) => node.tagName === "CANVAS";
866
- var isTextareaElement = (node) => node.tagName === "TEXTAREA";
867
- var isInputElement = (node) => node.tagName === "INPUT";
868
- var isStyleElement = (node) => node.tagName === "STYLE";
869
- var isScriptElement = (node) => node.tagName === "SCRIPT";
870
- var isSelectElement = (node) => node.tagName === "SELECT";
871
- var isSlotElement = (node) => node.tagName === "SLOT";
872
- var isIFrameElement = (node) => node.tagName === "IFRAME";
873
- var consoleWarn = (...args) => console.warn(PREFIX, ...args);
874
- function supportWebp(ownerDocument) {
875
- const canvas = ownerDocument?.createElement?.("canvas");
876
- if (canvas) {
877
- canvas.height = canvas.width = 1;
878
- }
879
- return Boolean(canvas) && "toDataURL" in canvas && Boolean(canvas.toDataURL("image/webp").includes("image/webp"));
880
- }
881
- var isDataUrl = (url) => url.startsWith("data:");
882
- function resolveUrl(url, baseUrl) {
883
- if (url.match(/^[a-z]+:\/\//i))
884
- return url;
885
- if (IN_BROWSER && url.match(/^\/\//))
886
- return window.location.protocol + url;
887
- if (url.match(/^[a-z]+:/i))
888
- return url;
889
- if (!IN_BROWSER)
890
- return url;
891
- const doc = getDocument().implementation.createHTMLDocument();
892
- const base = doc.createElement("base");
893
- const a = doc.createElement("a");
894
- doc.head.appendChild(base);
895
- doc.body.appendChild(a);
896
- if (baseUrl)
897
- base.href = baseUrl;
898
- a.href = url;
899
- return a.href;
900
- }
901
- function getDocument(target) {
902
- return (target && isElementNode(target) ? target?.ownerDocument : target) ?? window.document;
903
- }
904
- var XMLNS = "http://www.w3.org/2000/svg";
905
- function createSvg(width, height, ownerDocument) {
906
- const svg = getDocument(ownerDocument).createElementNS(XMLNS, "svg");
907
- svg.setAttributeNS(null, "width", width.toString());
908
- svg.setAttributeNS(null, "height", height.toString());
909
- svg.setAttributeNS(null, "viewBox", `0 0 ${width} ${height}`);
910
- return svg;
911
- }
912
- function svgToDataUrl(svg, removeControlCharacter) {
913
- let xhtml = new XMLSerializer().serializeToString(svg);
914
- if (removeControlCharacter) {
915
- xhtml = xhtml.replace(/[\u0000-\u0008\v\f\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/gu, "");
916
- }
917
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xhtml)}`;
918
- }
919
- function readBlob(blob, type) {
920
- return new Promise((resolve, reject) => {
921
- const reader = new FileReader();
922
- reader.onload = () => resolve(reader.result);
923
- reader.onerror = () => reject(reader.error);
924
- reader.onabort = () => reject(new Error(`Failed read blob to ${type}`));
925
- if (type === "dataUrl") {
926
- reader.readAsDataURL(blob);
927
- } else if (type === "arrayBuffer") {
928
- reader.readAsArrayBuffer(blob);
929
- }
930
- });
931
- }
932
- var blobToDataUrl = (blob) => readBlob(blob, "dataUrl");
933
- function createImage(url, ownerDocument) {
934
- const img = getDocument(ownerDocument).createElement("img");
935
- img.decoding = "sync";
936
- img.loading = "eager";
937
- img.src = url;
938
- return img;
939
- }
940
- function loadMedia(media, options) {
941
- return new Promise((resolve) => {
942
- const { timeout, ownerDocument, onError: userOnError, onWarn } = options ?? {};
943
- const node = typeof media === "string" ? createImage(media, getDocument(ownerDocument)) : media;
944
- let timer = null;
945
- let removeEventListeners = null;
946
- function onResolve() {
947
- resolve(node);
948
- timer && clearTimeout(timer);
949
- removeEventListeners?.();
950
- }
951
- if (timeout) {
952
- timer = setTimeout(onResolve, timeout);
953
- }
954
- if (isVideoElement(node)) {
955
- const currentSrc = node.currentSrc || node.src;
956
- if (!currentSrc) {
957
- if (node.poster) {
958
- return loadMedia(node.poster, options).then(resolve);
959
- }
960
- return onResolve();
961
- }
962
- if (node.readyState >= 2) {
963
- return onResolve();
964
- }
965
- const onLoadeddata = onResolve;
966
- const onError = (error) => {
967
- onWarn?.(
968
- "Failed video load",
969
- currentSrc,
970
- error
971
- );
972
- userOnError?.(error);
973
- onResolve();
974
- };
975
- removeEventListeners = () => {
976
- node.removeEventListener("loadeddata", onLoadeddata);
977
- node.removeEventListener("error", onError);
978
- };
979
- node.addEventListener("loadeddata", onLoadeddata, { once: true });
980
- node.addEventListener("error", onError, { once: true });
981
- } else {
982
- const currentSrc = isSVGImageElementNode(node) ? node.href.baseVal : node.currentSrc || node.src;
983
- if (!currentSrc) {
984
- return onResolve();
985
- }
986
- const onLoad = async () => {
987
- if (isImageElement(node) && "decode" in node) {
988
- try {
989
- await node.decode();
990
- } catch (error) {
991
- onWarn?.(
992
- "Failed to decode image, trying to render anyway",
993
- node.dataset.originalSrc || currentSrc,
994
- error
995
- );
996
- }
997
- }
998
- onResolve();
999
- };
1000
- const onError = (error) => {
1001
- onWarn?.(
1002
- "Failed image load",
1003
- node.dataset.originalSrc || currentSrc,
1004
- error
1005
- );
1006
- onResolve();
1007
- };
1008
- if (isImageElement(node) && node.complete) {
1009
- return onLoad();
1010
- }
1011
- removeEventListeners = () => {
1012
- node.removeEventListener("load", onLoad);
1013
- node.removeEventListener("error", onError);
1014
- };
1015
- node.addEventListener("load", onLoad, { once: true });
1016
- node.addEventListener("error", onError, { once: true });
1017
- }
1018
- });
1019
- }
1020
- async function waitUntilLoad(node, options) {
1021
- if (isHTMLElementNode(node)) {
1022
- if (isImageElement(node) || isVideoElement(node)) {
1023
- await loadMedia(node, options);
1024
- } else {
1025
- await Promise.all(
1026
- ["img", "video"].flatMap((selectors) => {
1027
- return Array.from(node.querySelectorAll(selectors)).map((el) => loadMedia(el, options));
1028
- })
1029
- );
1030
- }
1031
- }
1032
- }
1033
- var uuid = /* @__PURE__ */ (function uuid2() {
1034
- let counter = 0;
1035
- const random = () => `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4);
1036
- return () => {
1037
- counter += 1;
1038
- return `u${random()}${counter}`;
1039
- };
1040
- })();
1041
- function splitFontFamily(fontFamily) {
1042
- return fontFamily?.split(",").map((val) => val.trim().replace(/"|'/g, "").toLowerCase()).filter(Boolean);
1043
- }
1044
- var uid = 0;
1045
- function createLogger(debug) {
1046
- const prefix = `${PREFIX}[#${uid}]`;
1047
- uid++;
1048
- return {
1049
- // eslint-disable-next-line no-console
1050
- time: (label) => debug && console.time(`${prefix} ${label}`),
1051
- // eslint-disable-next-line no-console
1052
- timeEnd: (label) => debug && console.timeEnd(`${prefix} ${label}`),
1053
- warn: (...args) => debug && consoleWarn(...args)
1054
- };
1055
- }
1056
- function getDefaultRequestInit(bypassingCache) {
1057
- return {
1058
- cache: bypassingCache ? "no-cache" : "force-cache"
1059
- };
1060
- }
1061
- async function orCreateContext(node, options) {
1062
- return isContext(node) ? node : createContext(node, { ...options, autoDestruct: true });
1063
- }
1064
- async function createContext(node, options) {
1065
- const { scale = 1, workerUrl, workerNumber = 1 } = options || {};
1066
- const debug = Boolean(options?.debug);
1067
- const features = options?.features ?? true;
1068
- const ownerDocument = node.ownerDocument ?? (IN_BROWSER ? window.document : void 0);
1069
- const ownerWindow = node.ownerDocument?.defaultView ?? (IN_BROWSER ? window : void 0);
1070
- const requests = /* @__PURE__ */ new Map();
1071
- const context = {
1072
- // Options
1073
- width: 0,
1074
- height: 0,
1075
- quality: 1,
1076
- type: "image/png",
1077
- scale,
1078
- backgroundColor: null,
1079
- style: null,
1080
- filter: null,
1081
- maximumCanvasSize: 0,
1082
- timeout: 3e4,
1083
- progress: null,
1084
- debug,
1085
- fetch: {
1086
- requestInit: getDefaultRequestInit(options?.fetch?.bypassingCache),
1087
- placeholderImage: "data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
1088
- bypassingCache: false,
1089
- ...options?.fetch
1090
- },
1091
- fetchFn: null,
1092
- font: {},
1093
- drawImageInterval: 100,
1094
- workerUrl: null,
1095
- workerNumber,
1096
- onCloneEachNode: null,
1097
- onCloneNode: null,
1098
- onEmbedNode: null,
1099
- onCreateForeignObjectSvg: null,
1100
- includeStyleProperties: null,
1101
- autoDestruct: false,
1102
- ...options,
1103
- // InternalContext
1104
- __CONTEXT__: true,
1105
- log: createLogger(debug),
1106
- node,
1107
- ownerDocument,
1108
- ownerWindow,
1109
- dpi: scale === 1 ? null : 96 * scale,
1110
- svgStyleElement: createStyleElement(ownerDocument),
1111
- svgDefsElement: ownerDocument?.createElementNS(XMLNS, "defs"),
1112
- svgStyles: /* @__PURE__ */ new Map(),
1113
- defaultComputedStyles: /* @__PURE__ */ new Map(),
1114
- workers: [
1115
- ...Array.from({
1116
- length: SUPPORT_WEB_WORKER && workerUrl && workerNumber ? workerNumber : 0
1117
- })
1118
- ].map(() => {
1119
- try {
1120
- const worker = new Worker(workerUrl);
1121
- worker.onmessage = async (event) => {
1122
- const { url, result } = event.data;
1123
- if (result) {
1124
- requests.get(url)?.resolve?.(result);
1125
- } else {
1126
- requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${url}`));
1127
- }
1128
- };
1129
- worker.onmessageerror = (event) => {
1130
- const { url } = event.data;
1131
- requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${url}`));
1132
- };
1133
- return worker;
1134
- } catch (error) {
1135
- context.log.warn("Failed to new Worker", error);
1136
- return null;
1137
- }
1138
- }).filter(Boolean),
1139
- fontFamilies: /* @__PURE__ */ new Map(),
1140
- fontCssTexts: /* @__PURE__ */ new Map(),
1141
- acceptOfImage: `${[
1142
- supportWebp(ownerDocument) && "image/webp",
1143
- "image/svg+xml",
1144
- "image/*",
1145
- "*/*"
1146
- ].filter(Boolean).join(",")};q=0.8`,
1147
- requests,
1148
- drawImageCount: 0,
1149
- tasks: [],
1150
- features,
1151
- isEnable: (key2) => {
1152
- if (key2 === "restoreScrollPosition") {
1153
- return typeof features === "boolean" ? false : features[key2] ?? false;
1154
- }
1155
- if (typeof features === "boolean") {
1156
- return features;
1157
- }
1158
- return features[key2] ?? true;
1159
- },
1160
- shadowRoots: []
1161
- };
1162
- context.log.time("wait until load");
1163
- await waitUntilLoad(node, { timeout: context.timeout, onWarn: context.log.warn });
1164
- context.log.timeEnd("wait until load");
1165
- const { width, height } = resolveBoundingBox(node, context);
1166
- context.width = width;
1167
- context.height = height;
1168
- return context;
1169
- }
1170
- function createStyleElement(ownerDocument) {
1171
- if (!ownerDocument)
1172
- return void 0;
1173
- const style = ownerDocument.createElement("style");
1174
- const cssText = style.ownerDocument.createTextNode(`
1175
- .______background-clip--text {
1176
- background-clip: text;
1177
- -webkit-background-clip: text;
1178
- }
1179
- `);
1180
- style.appendChild(cssText);
1181
- return style;
1182
- }
1183
- function resolveBoundingBox(node, context) {
1184
- let { width, height } = context;
1185
- if (isElementNode(node) && (!width || !height)) {
1186
- const box = node.getBoundingClientRect();
1187
- width = width || box.width || Number(node.getAttribute("width")) || 0;
1188
- height = height || box.height || Number(node.getAttribute("height")) || 0;
1189
- }
1190
- return { width, height };
1191
- }
1192
- async function imageToCanvas(image, context) {
1193
- const {
1194
- log,
1195
- timeout,
1196
- drawImageCount,
1197
- drawImageInterval
1198
- } = context;
1199
- log.time("image to canvas");
1200
- const loaded = await loadMedia(image, { timeout, onWarn: context.log.warn });
1201
- const { canvas, context2d } = createCanvas(image.ownerDocument, context);
1202
- const drawImage = () => {
1203
- try {
1204
- context2d?.drawImage(loaded, 0, 0, canvas.width, canvas.height);
1205
- } catch (error) {
1206
- context.log.warn("Failed to drawImage", error);
1207
- }
1208
- };
1209
- drawImage();
1210
- if (context.isEnable("fixSvgXmlDecode")) {
1211
- for (let i = 0; i < drawImageCount; i++) {
1212
- await new Promise((resolve) => {
1213
- setTimeout(() => {
1214
- context2d?.clearRect(0, 0, canvas.width, canvas.height);
1215
- drawImage();
1216
- resolve();
1217
- }, i + drawImageInterval);
1218
- });
1219
- }
1220
- }
1221
- context.drawImageCount = 0;
1222
- log.timeEnd("image to canvas");
1223
- return canvas;
1224
- }
1225
- function createCanvas(ownerDocument, context) {
1226
- const { width, height, scale, backgroundColor, maximumCanvasSize: max } = context;
1227
- const canvas = ownerDocument.createElement("canvas");
1228
- canvas.width = Math.floor(width * scale);
1229
- canvas.height = Math.floor(height * scale);
1230
- canvas.style.width = `${width}px`;
1231
- canvas.style.height = `${height}px`;
1232
- if (max) {
1233
- if (canvas.width > max || canvas.height > max) {
1234
- if (canvas.width > max && canvas.height > max) {
1235
- if (canvas.width > canvas.height) {
1236
- canvas.height *= max / canvas.width;
1237
- canvas.width = max;
1238
- } else {
1239
- canvas.width *= max / canvas.height;
1240
- canvas.height = max;
1241
- }
1242
- } else if (canvas.width > max) {
1243
- canvas.height *= max / canvas.width;
1244
- canvas.width = max;
1245
- } else {
1246
- canvas.width *= max / canvas.height;
1247
- canvas.height = max;
1248
- }
1249
- }
1250
- }
1251
- const context2d = canvas.getContext("2d");
1252
- if (context2d && backgroundColor) {
1253
- context2d.fillStyle = backgroundColor;
1254
- context2d.fillRect(0, 0, canvas.width, canvas.height);
1255
- }
1256
- return { canvas, context2d };
1257
- }
1258
- function cloneCanvas(canvas, context) {
1259
- if (canvas.ownerDocument) {
1260
- try {
1261
- const dataURL = canvas.toDataURL();
1262
- if (dataURL !== "data:,") {
1263
- return createImage(dataURL, canvas.ownerDocument);
1264
- }
1265
- } catch (error) {
1266
- context.log.warn("Failed to clone canvas", error);
1267
- }
1268
- }
1269
- const cloned = canvas.cloneNode(false);
1270
- const ctx = canvas.getContext("2d");
1271
- const clonedCtx = cloned.getContext("2d");
1272
- try {
1273
- if (ctx && clonedCtx) {
1274
- clonedCtx.putImageData(
1275
- ctx.getImageData(0, 0, canvas.width, canvas.height),
1276
- 0,
1277
- 0
1278
- );
1279
- }
1280
- return cloned;
1281
- } catch (error) {
1282
- context.log.warn("Failed to clone canvas", error);
1283
- }
1284
- return cloned;
1285
- }
1286
- function cloneIframe(iframe, context) {
6
+ // ../react/dist/loader.js
7
+ import { useEffect, useRef } from "react";
8
+ captureHandoff();
9
+ var bundled = () => import("./chunks/runtime-2ZDPCFHS.js");
10
+ async function serviceUrl(sessionPath) {
1287
11
  try {
1288
- if (iframe?.contentDocument?.documentElement) {
1289
- return cloneNode(iframe.contentDocument.documentElement, context);
1290
- }
1291
- } catch (error) {
1292
- context.log.warn("Failed to clone iframe", error);
1293
- }
1294
- return iframe.cloneNode(false);
1295
- }
1296
- function cloneImage(image) {
1297
- const cloned = image.cloneNode(false);
1298
- if (image.currentSrc && image.currentSrc !== image.src) {
1299
- cloned.src = image.currentSrc;
1300
- cloned.srcset = "";
1301
- }
1302
- if (cloned.loading === "lazy") {
1303
- cloned.loading = "eager";
1304
- }
1305
- return cloned;
1306
- }
1307
- async function cloneVideo(video, context) {
1308
- if (video.ownerDocument && !video.currentSrc && video.poster) {
1309
- return createImage(video.poster, video.ownerDocument);
1310
- }
1311
- const cloned = video.cloneNode(false);
1312
- cloned.crossOrigin = "anonymous";
1313
- if (video.currentSrc && video.currentSrc !== video.src) {
1314
- cloned.src = video.currentSrc;
1315
- }
1316
- const ownerDocument = cloned.ownerDocument;
1317
- if (ownerDocument) {
1318
- let canPlay = true;
1319
- await loadMedia(cloned, { onError: () => canPlay = false, onWarn: context.log.warn });
1320
- if (!canPlay) {
1321
- if (video.poster) {
1322
- return createImage(video.poster, video.ownerDocument);
1323
- }
1324
- return cloned;
1325
- }
1326
- cloned.currentTime = video.currentTime;
1327
- await new Promise((resolve) => {
1328
- cloned.addEventListener("seeked", resolve, { once: true });
1329
- });
1330
- const canvas = ownerDocument.createElement("canvas");
1331
- canvas.width = video.offsetWidth;
1332
- canvas.height = video.offsetHeight;
1333
- try {
1334
- const ctx = canvas.getContext("2d");
1335
- if (ctx)
1336
- ctx.drawImage(cloned, 0, 0, canvas.width, canvas.height);
1337
- } catch (error) {
1338
- context.log.warn("Failed to clone video", error);
1339
- if (video.poster) {
1340
- return createImage(video.poster, video.ownerDocument);
1341
- }
1342
- return cloned;
1343
- }
1344
- return cloneCanvas(canvas, context);
1345
- }
1346
- return cloned;
1347
- }
1348
- function cloneElement(node, context) {
1349
- if (isCanvasElement(node)) {
1350
- return cloneCanvas(node, context);
1351
- }
1352
- if (isIFrameElement(node)) {
1353
- return cloneIframe(node, context);
1354
- }
1355
- if (isImageElement(node)) {
1356
- return cloneImage(node);
1357
- }
1358
- if (isVideoElement(node)) {
1359
- return cloneVideo(node, context);
1360
- }
1361
- return node.cloneNode(false);
1362
- }
1363
- function getSandBox(context) {
1364
- let sandbox = context.sandbox;
1365
- if (!sandbox) {
1366
- const { ownerDocument } = context;
1367
- try {
1368
- if (ownerDocument) {
1369
- sandbox = ownerDocument.createElement("iframe");
1370
- sandbox.id = `__SANDBOX__${uuid()}`;
1371
- sandbox.width = "0";
1372
- sandbox.height = "0";
1373
- sandbox.style.visibility = "hidden";
1374
- sandbox.style.position = "fixed";
1375
- ownerDocument.body.appendChild(sandbox);
1376
- sandbox.srcdoc = '<!DOCTYPE html><meta charset="UTF-8"><title></title><body>';
1377
- context.sandbox = sandbox;
1378
- }
1379
- } catch (error) {
1380
- context.log.warn("Failed to getSandBox", error);
1381
- }
1382
- }
1383
- return sandbox;
1384
- }
1385
- var ignoredStyles = [
1386
- "width",
1387
- "height",
1388
- "-webkit-text-fill-color"
1389
- ];
1390
- var includedAttributes = [
1391
- "stroke",
1392
- "fill"
1393
- ];
1394
- function getDefaultStyle(node, pseudoElement, context) {
1395
- const { defaultComputedStyles } = context;
1396
- const nodeName = node.nodeName.toLowerCase();
1397
- const isSvgNode = isSVGElementNode(node) && nodeName !== "svg";
1398
- const attributes = isSvgNode ? includedAttributes.map((name) => [name, node.getAttribute(name)]).filter(([, value]) => value !== null) : [];
1399
- const key2 = [
1400
- isSvgNode && "svg",
1401
- nodeName,
1402
- attributes.map((name, value) => `${name}=${value}`).join(","),
1403
- pseudoElement
1404
- ].filter(Boolean).join(":");
1405
- if (defaultComputedStyles.has(key2))
1406
- return defaultComputedStyles.get(key2);
1407
- const sandbox = getSandBox(context);
1408
- const sandboxWindow = sandbox?.contentWindow;
1409
- if (!sandboxWindow)
1410
- return /* @__PURE__ */ new Map();
1411
- const sandboxDocument = sandboxWindow?.document;
1412
- let root;
1413
- let el;
1414
- if (isSvgNode) {
1415
- root = sandboxDocument.createElementNS(XMLNS, "svg");
1416
- el = root.ownerDocument.createElementNS(root.namespaceURI, nodeName);
1417
- attributes.forEach(([name, value]) => {
1418
- el.setAttributeNS(null, name, value);
1419
- });
1420
- root.appendChild(el);
1421
- } else {
1422
- root = el = sandboxDocument.createElement(nodeName);
1423
- }
1424
- el.textContent = " ";
1425
- sandboxDocument.body.appendChild(root);
1426
- const computedStyle = sandboxWindow.getComputedStyle(el, pseudoElement);
1427
- const styles = /* @__PURE__ */ new Map();
1428
- for (let len = computedStyle.length, i = 0; i < len; i++) {
1429
- const name = computedStyle.item(i);
1430
- if (ignoredStyles.includes(name))
1431
- continue;
1432
- styles.set(name, computedStyle.getPropertyValue(name));
1433
- }
1434
- sandboxDocument.body.removeChild(root);
1435
- defaultComputedStyles.set(key2, styles);
1436
- return styles;
1437
- }
1438
- function getDiffStyle(style, defaultStyle, includeStyleProperties) {
1439
- const diffStyle = /* @__PURE__ */ new Map();
1440
- const prefixs = [];
1441
- const prefixTree = /* @__PURE__ */ new Map();
1442
- if (includeStyleProperties) {
1443
- for (const name of includeStyleProperties) {
1444
- applyTo(name);
1445
- }
1446
- } else {
1447
- for (let len = style.length, i = 0; i < len; i++) {
1448
- const name = style.item(i);
1449
- applyTo(name);
1450
- }
1451
- }
1452
- for (let len = prefixs.length, i = 0; i < len; i++) {
1453
- prefixTree.get(prefixs[i])?.forEach((value, name) => diffStyle.set(name, value));
1454
- }
1455
- function applyTo(name) {
1456
- const value = style.getPropertyValue(name);
1457
- const priority = style.getPropertyPriority(name);
1458
- const subIndex = name.lastIndexOf("-");
1459
- const prefix = subIndex > -1 ? name.substring(0, subIndex) : void 0;
1460
- if (prefix) {
1461
- let map = prefixTree.get(prefix);
1462
- if (!map) {
1463
- map = /* @__PURE__ */ new Map();
1464
- prefixTree.set(prefix, map);
1465
- }
1466
- map.set(name, [value, priority]);
1467
- }
1468
- if (defaultStyle.get(name) === value && !priority)
1469
- return;
1470
- if (prefix) {
1471
- prefixs.push(prefix);
1472
- } else {
1473
- diffStyle.set(name, [value, priority]);
1474
- }
1475
- }
1476
- return diffStyle;
1477
- }
1478
- function copyCssStyles(node, cloned, isRoot, context) {
1479
- const { ownerWindow, includeStyleProperties, currentParentNodeStyle } = context;
1480
- const clonedStyle = cloned.style;
1481
- const computedStyle = ownerWindow.getComputedStyle(node);
1482
- const defaultStyle = getDefaultStyle(node, null, context);
1483
- currentParentNodeStyle?.forEach((_, key2) => {
1484
- defaultStyle.delete(key2);
1485
- });
1486
- const style = getDiffStyle(computedStyle, defaultStyle, includeStyleProperties);
1487
- style.delete("transition-property");
1488
- style.delete("all");
1489
- style.delete("d");
1490
- style.delete("content");
1491
- if (isRoot) {
1492
- style.delete("position");
1493
- style.delete("margin-top");
1494
- style.delete("margin-right");
1495
- style.delete("margin-bottom");
1496
- style.delete("margin-left");
1497
- style.delete("margin-block-start");
1498
- style.delete("margin-block-end");
1499
- style.delete("margin-inline-start");
1500
- style.delete("margin-inline-end");
1501
- style.set("box-sizing", ["border-box", ""]);
1502
- }
1503
- if (style.get("background-clip")?.[0] === "text") {
1504
- cloned.classList.add("______background-clip--text");
1505
- }
1506
- if (IN_CHROME) {
1507
- if (!style.has("font-kerning"))
1508
- style.set("font-kerning", ["normal", ""]);
1509
- if ((style.get("overflow-x")?.[0] === "hidden" || style.get("overflow-y")?.[0] === "hidden") && style.get("text-overflow")?.[0] === "ellipsis" && node.scrollWidth === node.clientWidth) {
1510
- style.set("text-overflow", ["clip", ""]);
1511
- }
1512
- }
1513
- for (let len = clonedStyle.length, i = 0; i < len; i++) {
1514
- clonedStyle.removeProperty(clonedStyle.item(i));
1515
- }
1516
- style.forEach(([value, priority], name) => {
1517
- clonedStyle.setProperty(name, value, priority);
1518
- });
1519
- return style;
1520
- }
1521
- function copyInputValue(node, cloned) {
1522
- if (isTextareaElement(node) || isInputElement(node) || isSelectElement(node)) {
1523
- cloned.setAttribute("value", node.value);
1524
- }
1525
- }
1526
- var pseudoClasses = [
1527
- "::before",
1528
- "::after"
1529
- // '::placeholder', TODO
1530
- ];
1531
- var scrollbarPseudoClasses = [
1532
- "::-webkit-scrollbar",
1533
- "::-webkit-scrollbar-button",
1534
- // '::-webkit-scrollbar:horizontal', TODO
1535
- "::-webkit-scrollbar-thumb",
1536
- "::-webkit-scrollbar-track",
1537
- "::-webkit-scrollbar-track-piece",
1538
- // '::-webkit-scrollbar:vertical', TODO
1539
- "::-webkit-scrollbar-corner",
1540
- "::-webkit-resizer"
1541
- ];
1542
- function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFamilies) {
1543
- const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context;
1544
- if (!svgStyleElement || !ownerWindow)
1545
- return;
1546
- function copyBy(pseudoClass) {
1547
- const computedStyle = ownerWindow.getComputedStyle(node, pseudoClass);
1548
- let content = computedStyle.getPropertyValue("content");
1549
- if (!content || content === "none")
1550
- return;
1551
- addWordToFontFamilies?.(content);
1552
- content = content.replace(/(')|(")|(counter\(.+\))/g, "");
1553
- const klasses = [uuid()];
1554
- const defaultStyle = getDefaultStyle(node, pseudoClass, context);
1555
- currentNodeStyle?.forEach((_, key2) => {
1556
- defaultStyle.delete(key2);
12
+ const response = await fetch(sessionPath, {
13
+ headers: { accept: "application/json", "x-comment-service-origin": window.location.origin },
14
+ cache: "no-store"
1557
15
  });
1558
- const style = getDiffStyle(computedStyle, defaultStyle, context.includeStyleProperties);
1559
- style.delete("content");
1560
- style.delete("-webkit-locale");
1561
- if (style.get("background-clip")?.[0] === "text") {
1562
- cloned.classList.add("______background-clip--text");
1563
- }
1564
- const cloneStyle = [
1565
- `content: '${content}';`
1566
- ];
1567
- style.forEach(([value, priority], name) => {
1568
- cloneStyle.push(`${name}: ${value}${priority ? " !important" : ""};`);
1569
- });
1570
- if (cloneStyle.length === 1)
1571
- return;
1572
- try {
1573
- cloned.className = [cloned.className, ...klasses].join(" ");
1574
- } catch (err) {
1575
- context.log.warn("Failed to copyPseudoClass", err);
1576
- return;
1577
- }
1578
- const cssText = cloneStyle.join("\n ");
1579
- let allClasses = svgStyles.get(cssText);
1580
- if (!allClasses) {
1581
- allClasses = [];
1582
- svgStyles.set(cssText, allClasses);
1583
- }
1584
- allClasses.push(`.${klasses[0]}${pseudoClass}`);
1585
- }
1586
- pseudoClasses.forEach(copyBy);
1587
- if (copyScrollbar)
1588
- scrollbarPseudoClasses.forEach(copyBy);
1589
- }
1590
- var excludeParentNodes = /* @__PURE__ */ new Set([
1591
- "symbol"
1592
- // test/fixtures/svg.symbol.html
1593
- ]);
1594
- async function appendChildNode(node, cloned, child, context, addWordToFontFamilies) {
1595
- if (isElementNode(child) && (isStyleElement(child) || isScriptElement(child)))
1596
- return;
1597
- if (context.filter && !context.filter(child))
1598
- return;
1599
- if (excludeParentNodes.has(cloned.nodeName) || excludeParentNodes.has(child.nodeName)) {
1600
- context.currentParentNodeStyle = void 0;
1601
- } else {
1602
- context.currentParentNodeStyle = context.currentNodeStyle;
1603
- }
1604
- const childCloned = await cloneNode(child, context, false, addWordToFontFamilies);
1605
- if (context.isEnable("restoreScrollPosition")) {
1606
- restoreScrollPosition(node, childCloned);
1607
- }
1608
- cloned.appendChild(childCloned);
1609
- }
1610
- async function cloneChildNodes(node, cloned, context, addWordToFontFamilies) {
1611
- let firstChild = node.firstChild;
1612
- if (isElementNode(node)) {
1613
- if (node.shadowRoot) {
1614
- firstChild = node.shadowRoot?.firstChild;
1615
- context.shadowRoots.push(node.shadowRoot);
1616
- }
1617
- }
1618
- for (let child = firstChild; child; child = child.nextSibling) {
1619
- if (isCommentNode(child))
1620
- continue;
1621
- if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === "function") {
1622
- const nodes = child.assignedNodes();
1623
- for (let i = 0; i < nodes.length; i++) {
1624
- await appendChildNode(node, cloned, nodes[i], context, addWordToFontFamilies);
1625
- }
1626
- } else {
1627
- await appendChildNode(node, cloned, child, context, addWordToFontFamilies);
1628
- }
1629
- }
1630
- }
1631
- function restoreScrollPosition(node, chlidCloned) {
1632
- if (!isHTMLElementNode(node) || !isHTMLElementNode(chlidCloned))
1633
- return;
1634
- const { scrollTop, scrollLeft } = node;
1635
- if (!scrollTop && !scrollLeft) {
1636
- return;
1637
- }
1638
- const { transform } = chlidCloned.style;
1639
- const matrix = new DOMMatrix(transform);
1640
- const { a, b, c, d } = matrix;
1641
- matrix.a = 1;
1642
- matrix.b = 0;
1643
- matrix.c = 0;
1644
- matrix.d = 1;
1645
- matrix.translateSelf(-scrollLeft, -scrollTop);
1646
- matrix.a = a;
1647
- matrix.b = b;
1648
- matrix.c = c;
1649
- matrix.d = d;
1650
- chlidCloned.style.transform = matrix.toString();
1651
- }
1652
- function applyCssStyleWithOptions(cloned, context) {
1653
- const { backgroundColor, width, height, style: styles } = context;
1654
- const clonedStyle = cloned.style;
1655
- if (backgroundColor)
1656
- clonedStyle.setProperty("background-color", backgroundColor, "important");
1657
- if (width)
1658
- clonedStyle.setProperty("width", `${width}px`, "important");
1659
- if (height)
1660
- clonedStyle.setProperty("height", `${height}px`, "important");
1661
- if (styles) {
1662
- for (const name in styles) clonedStyle[name] = styles[name];
1663
- }
1664
- }
1665
- var NORMAL_ATTRIBUTE_RE = /^[\w-:]+$/;
1666
- async function cloneNode(node, context, isRoot = false, addWordToFontFamilies) {
1667
- const { ownerDocument, ownerWindow, fontFamilies, onCloneEachNode } = context;
1668
- if (ownerDocument && isTextNode(node)) {
1669
- if (addWordToFontFamilies && /\S/.test(node.data)) {
1670
- addWordToFontFamilies(node.data);
1671
- }
1672
- return ownerDocument.createTextNode(node.data);
1673
- }
1674
- if (ownerDocument && ownerWindow && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) {
1675
- const cloned2 = await cloneElement(node, context);
1676
- if (context.isEnable("removeAbnormalAttributes")) {
1677
- const names2 = cloned2.getAttributeNames();
1678
- for (let len = names2.length, i = 0; i < len; i++) {
1679
- const name = names2[i];
1680
- if (!NORMAL_ATTRIBUTE_RE.test(name)) {
1681
- cloned2.removeAttribute(name);
1682
- }
1683
- }
1684
- }
1685
- const style = context.currentNodeStyle = copyCssStyles(node, cloned2, isRoot, context);
1686
- if (isRoot)
1687
- applyCssStyleWithOptions(cloned2, context);
1688
- let copyScrollbar = false;
1689
- if (context.isEnable("copyScrollbar")) {
1690
- const overflow = [
1691
- style.get("overflow-x")?.[0],
1692
- style.get("overflow-y")?.[0]
1693
- ];
1694
- copyScrollbar = overflow.includes("scroll") || (overflow.includes("auto") || overflow.includes("overlay")) && (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth);
1695
- }
1696
- const textTransform = style.get("text-transform")?.[0];
1697
- const families = splitFontFamily(style.get("font-family")?.[0]);
1698
- const addWordToFontFamilies2 = families ? (word) => {
1699
- if (textTransform === "uppercase") {
1700
- word = word.toUpperCase();
1701
- } else if (textTransform === "lowercase") {
1702
- word = word.toLowerCase();
1703
- } else if (textTransform === "capitalize") {
1704
- word = word[0].toUpperCase() + word.substring(1);
1705
- }
1706
- families.forEach((family) => {
1707
- let fontFamily = fontFamilies.get(family);
1708
- if (!fontFamily) {
1709
- fontFamilies.set(family, fontFamily = /* @__PURE__ */ new Set());
1710
- }
1711
- word.split("").forEach((text) => fontFamily.add(text));
1712
- });
1713
- } : void 0;
1714
- copyPseudoClass(
1715
- node,
1716
- cloned2,
1717
- copyScrollbar,
1718
- context,
1719
- addWordToFontFamilies2
1720
- );
1721
- copyInputValue(node, cloned2);
1722
- if (!isVideoElement(node)) {
1723
- await cloneChildNodes(
1724
- node,
1725
- cloned2,
1726
- context,
1727
- addWordToFontFamilies2
1728
- );
1729
- }
1730
- await onCloneEachNode?.(cloned2);
1731
- return cloned2;
1732
- }
1733
- const cloned = node.cloneNode(false);
1734
- await cloneChildNodes(node, cloned, context);
1735
- await onCloneEachNode?.(cloned);
1736
- return cloned;
1737
- }
1738
- function destroyContext(context) {
1739
- context.ownerDocument = void 0;
1740
- context.ownerWindow = void 0;
1741
- context.svgStyleElement = void 0;
1742
- context.svgDefsElement = void 0;
1743
- context.svgStyles.clear();
1744
- context.defaultComputedStyles.clear();
1745
- if (context.sandbox) {
1746
- try {
1747
- context.sandbox.remove();
1748
- } catch (err) {
1749
- context.log.warn("Failed to destroyContext", err);
1750
- }
1751
- context.sandbox = void 0;
1752
- }
1753
- context.workers = [];
1754
- context.fontFamilies.clear();
1755
- context.fontCssTexts.clear();
1756
- context.requests.clear();
1757
- context.tasks = [];
1758
- context.shadowRoots = [];
1759
- }
1760
- function baseFetch(options) {
1761
- const { url, timeout, responseType, ...requestInit } = options;
1762
- const controller = new AbortController();
1763
- const timer = timeout ? setTimeout(() => controller.abort(), timeout) : void 0;
1764
- return fetch(url, { signal: controller.signal, ...requestInit }).then((response) => {
1765
- if (!response.ok) {
1766
- throw new Error("Failed fetch, not 2xx response", { cause: response });
1767
- }
1768
- switch (responseType) {
1769
- case "arrayBuffer":
1770
- return response.arrayBuffer();
1771
- case "dataUrl":
1772
- return response.blob().then(blobToDataUrl);
1773
- case "text":
1774
- default:
1775
- return response.text();
1776
- }
1777
- }).finally(() => clearTimeout(timer));
1778
- }
1779
- function contextFetch(context, options) {
1780
- const { url: rawUrl, requestType = "text", responseType = "text", imageDom } = options;
1781
- let url = rawUrl;
1782
- const {
1783
- timeout,
1784
- acceptOfImage,
1785
- requests,
1786
- fetchFn,
1787
- fetch: {
1788
- requestInit,
1789
- bypassingCache,
1790
- placeholderImage
1791
- },
1792
- font,
1793
- workers,
1794
- fontFamilies
1795
- } = context;
1796
- if (requestType === "image" && (IN_SAFARI || IN_FIREFOX)) {
1797
- context.drawImageCount++;
1798
- }
1799
- let request2 = requests.get(rawUrl);
1800
- if (!request2) {
1801
- if (bypassingCache) {
1802
- if (bypassingCache instanceof RegExp && bypassingCache.test(url)) {
1803
- url += (/\?/.test(url) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
1804
- }
1805
- }
1806
- const canFontMinify = requestType.startsWith("font") && font && font.minify;
1807
- const fontTexts = /* @__PURE__ */ new Set();
1808
- if (canFontMinify) {
1809
- const families = requestType.split(";")[1].split(",");
1810
- families.forEach((family) => {
1811
- if (!fontFamilies.has(family))
1812
- return;
1813
- fontFamilies.get(family).forEach((text) => fontTexts.add(text));
1814
- });
1815
- }
1816
- const needFontMinify = canFontMinify && fontTexts.size;
1817
- const baseFetchOptions = {
1818
- url,
1819
- timeout,
1820
- responseType: needFontMinify ? "arrayBuffer" : responseType,
1821
- headers: requestType === "image" ? { accept: acceptOfImage } : void 0,
1822
- ...requestInit
1823
- };
1824
- request2 = {
1825
- type: requestType,
1826
- resolve: void 0,
1827
- reject: void 0,
1828
- response: null
1829
- };
1830
- request2.response = (async () => {
1831
- if (fetchFn && requestType === "image") {
1832
- const result = await fetchFn(rawUrl);
1833
- if (result)
1834
- return result;
1835
- }
1836
- if (!IN_SAFARI && rawUrl.startsWith("http") && workers.length) {
1837
- return new Promise((resolve, reject) => {
1838
- const worker = workers[requests.size & workers.length - 1];
1839
- worker.postMessage({ rawUrl, ...baseFetchOptions });
1840
- request2.resolve = resolve;
1841
- request2.reject = reject;
1842
- });
1843
- }
1844
- return baseFetch(baseFetchOptions);
1845
- })().catch((error) => {
1846
- requests.delete(rawUrl);
1847
- if (requestType === "image" && placeholderImage) {
1848
- context.log.warn("Failed to fetch image base64, trying to use placeholder image", url);
1849
- return typeof placeholderImage === "string" ? placeholderImage : placeholderImage(imageDom);
1850
- }
1851
- throw error;
1852
- });
1853
- requests.set(rawUrl, request2);
1854
- }
1855
- return request2.response;
1856
- }
1857
- async function replaceCssUrlToDataUrl(cssText, baseUrl, context, isImage) {
1858
- if (!hasCssUrl(cssText))
1859
- return cssText;
1860
- for (const [rawUrl, url] of parseCssUrls(cssText, baseUrl)) {
1861
- try {
1862
- const dataUrl = await contextFetch(
1863
- context,
1864
- {
1865
- url,
1866
- requestType: isImage ? "image" : "text",
1867
- responseType: "dataUrl"
1868
- }
1869
- );
1870
- cssText = cssText.replace(toRE(rawUrl), `$1${dataUrl}$3`);
1871
- } catch (error) {
1872
- context.log.warn("Failed to fetch css data url", rawUrl, error);
1873
- }
1874
- }
1875
- return cssText;
1876
- }
1877
- function hasCssUrl(cssText) {
1878
- return /url\((['"]?)([^'"]+?)\1\)/.test(cssText);
1879
- }
1880
- var URL_RE = /url\((['"]?)([^'"]+?)\1\)/g;
1881
- function parseCssUrls(cssText, baseUrl) {
1882
- const result = [];
1883
- cssText.replace(URL_RE, (raw, quotation, url) => {
1884
- result.push([url, resolveUrl(url, baseUrl)]);
1885
- return raw;
1886
- });
1887
- return result.filter(([url]) => !isDataUrl(url));
1888
- }
1889
- function toRE(url) {
1890
- const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
1891
- return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
1892
- }
1893
- var properties = [
1894
- "background-image",
1895
- "border-image-source",
1896
- "-webkit-border-image",
1897
- "-webkit-mask-image",
1898
- "list-style-image"
1899
- ];
1900
- function embedCssStyleImage(style, context) {
1901
- return properties.map((property) => {
1902
- const value = style.getPropertyValue(property);
1903
- if (!value || value === "none") {
1904
- return null;
1905
- }
1906
- if (IN_SAFARI || IN_FIREFOX) {
1907
- context.drawImageCount++;
1908
- }
1909
- return replaceCssUrlToDataUrl(value, null, context, true).then((newValue) => {
1910
- if (!newValue || value === newValue)
1911
- return;
1912
- style.setProperty(
1913
- property,
1914
- newValue,
1915
- style.getPropertyPriority(property)
1916
- );
1917
- });
1918
- }).filter(Boolean);
1919
- }
1920
- function embedImageElement(cloned, context) {
1921
- if (isImageElement(cloned)) {
1922
- const originalSrc = cloned.currentSrc || cloned.src;
1923
- if (!isDataUrl(originalSrc)) {
1924
- return [
1925
- contextFetch(context, {
1926
- url: originalSrc,
1927
- imageDom: cloned,
1928
- requestType: "image",
1929
- responseType: "dataUrl"
1930
- }).then((url) => {
1931
- if (!url)
1932
- return;
1933
- cloned.srcset = "";
1934
- cloned.dataset.originalSrc = originalSrc;
1935
- cloned.src = url || "";
1936
- })
1937
- ];
1938
- }
1939
- if (IN_SAFARI || IN_FIREFOX) {
1940
- context.drawImageCount++;
1941
- }
1942
- } else if (isSVGElementNode(cloned) && !isDataUrl(cloned.href.baseVal)) {
1943
- const originalSrc = cloned.href.baseVal;
1944
- return [
1945
- contextFetch(context, {
1946
- url: originalSrc,
1947
- imageDom: cloned,
1948
- requestType: "image",
1949
- responseType: "dataUrl"
1950
- }).then((url) => {
1951
- if (!url)
1952
- return;
1953
- cloned.dataset.originalSrc = originalSrc;
1954
- cloned.href.baseVal = url || "";
1955
- })
1956
- ];
1957
- }
1958
- return [];
1959
- }
1960
- function embedSvgUse(cloned, context) {
1961
- const { ownerDocument, svgDefsElement } = context;
1962
- const href = cloned.getAttribute("href") ?? cloned.getAttribute("xlink:href");
1963
- if (!href)
1964
- return [];
1965
- const [svgUrl, id] = href.split("#");
1966
- if (id) {
1967
- const query = `#${id}`;
1968
- const definition = context.shadowRoots.reduce(
1969
- (res, root) => {
1970
- return res ?? root.querySelector(`svg ${query}`);
1971
- },
1972
- ownerDocument?.querySelector(`svg ${query}`)
1973
- );
1974
- if (svgUrl) {
1975
- cloned.setAttribute("href", query);
1976
- }
1977
- if (svgDefsElement?.querySelector(query))
1978
- return [];
1979
- if (definition) {
1980
- svgDefsElement?.appendChild(definition.cloneNode(true));
1981
- return [];
1982
- } else if (svgUrl) {
1983
- return [
1984
- contextFetch(context, {
1985
- url: svgUrl,
1986
- responseType: "text"
1987
- }).then((svgData) => {
1988
- svgDefsElement?.insertAdjacentHTML("beforeend", svgData);
1989
- })
1990
- ];
1991
- }
1992
- }
1993
- return [];
1994
- }
1995
- function embedNode(cloned, context) {
1996
- const { tasks } = context;
1997
- if (isElementNode(cloned)) {
1998
- if (isImageElement(cloned) || isSVGImageElementNode(cloned)) {
1999
- tasks.push(...embedImageElement(cloned, context));
2000
- }
2001
- if (isSVGUseElementNode(cloned)) {
2002
- tasks.push(...embedSvgUse(cloned, context));
2003
- }
2004
- }
2005
- if (isHTMLElementNode(cloned)) {
2006
- tasks.push(...embedCssStyleImage(cloned.style, context));
2007
- }
2008
- cloned.childNodes.forEach((child) => {
2009
- embedNode(child, context);
2010
- });
2011
- }
2012
- async function embedWebFont(clone, context) {
2013
- const {
2014
- ownerDocument,
2015
- svgStyleElement,
2016
- fontFamilies,
2017
- fontCssTexts,
2018
- tasks,
2019
- font
2020
- } = context;
2021
- if (!ownerDocument || !svgStyleElement || !fontFamilies.size) {
2022
- return;
2023
- }
2024
- if (font && font.cssText) {
2025
- const cssText = filterPreferredFormat(font.cssText, context);
2026
- svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText}
2027
- `));
2028
- } else {
2029
- const styleSheets = Array.from(ownerDocument.styleSheets).filter((styleSheet) => {
2030
- try {
2031
- return "cssRules" in styleSheet && Boolean(styleSheet.cssRules.length);
2032
- } catch (error) {
2033
- context.log.warn(`Error while reading CSS rules from ${styleSheet.href}`, error);
2034
- return false;
2035
- }
2036
- });
2037
- const tempDoc = ownerDocument.implementation.createHTMLDocument("");
2038
- const tempStyleEl = tempDoc.createElement("style");
2039
- tempDoc.head.appendChild(tempStyleEl);
2040
- const tempStyleSheet = tempStyleEl.sheet;
2041
- await Promise.all(
2042
- styleSheets.flatMap((styleSheet) => {
2043
- return Array.from(styleSheet.cssRules).map(async (cssRule) => {
2044
- if (isCSSImportRule(cssRule)) {
2045
- const baseUrl = cssRule.href;
2046
- let cssText = "";
2047
- try {
2048
- cssText = await contextFetch(context, {
2049
- url: baseUrl,
2050
- requestType: "text",
2051
- responseType: "text"
2052
- });
2053
- } catch (error) {
2054
- context.log.warn(`Error fetch remote css import from ${baseUrl}`, error);
2055
- }
2056
- const replacedCssText = cssText.replace(
2057
- URL_RE,
2058
- (raw, quotation, url) => raw.replace(url, resolveUrl(url, baseUrl))
2059
- );
2060
- for (const rule of parseCss(replacedCssText)) {
2061
- try {
2062
- tempStyleSheet.insertRule(rule, tempStyleSheet.cssRules.length);
2063
- } catch (error) {
2064
- context.log.warn("Error inserting rule from remote css import", { rule, error });
2065
- }
2066
- }
2067
- }
2068
- });
2069
- })
2070
- );
2071
- if (tempStyleSheet.cssRules.length)
2072
- styleSheets.push(tempStyleSheet);
2073
- const cssRules = [];
2074
- styleSheets.forEach((sheet) => {
2075
- unwrapCssLayers(sheet.cssRules, cssRules);
2076
- });
2077
- cssRules.filter((cssRule) => isCssFontFaceRule(cssRule) && hasCssUrl(cssRule.style.getPropertyValue("src")) && splitFontFamily(cssRule.style.getPropertyValue("font-family"))?.some((val) => fontFamilies.has(val))).forEach((value) => {
2078
- const rule = value;
2079
- const cssText = fontCssTexts.get(rule.cssText);
2080
- if (cssText) {
2081
- svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText}
2082
- `));
2083
- } else {
2084
- tasks.push(
2085
- replaceCssUrlToDataUrl(
2086
- rule.cssText,
2087
- rule.parentStyleSheet ? rule.parentStyleSheet.href : null,
2088
- context
2089
- ).then((cssText2) => {
2090
- cssText2 = filterPreferredFormat(cssText2, context);
2091
- fontCssTexts.set(rule.cssText, cssText2);
2092
- svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText2}
2093
- `));
2094
- })
2095
- );
2096
- }
2097
- });
2098
- }
2099
- }
2100
- var COMMENTS_RE = /(\/\*[\s\S]*?\*\/)/g;
2101
- var KEYFRAMES_RE = /((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi;
2102
- function parseCss(source) {
2103
- if (source == null)
2104
- return [];
2105
- const result = [];
2106
- let cssText = source.replace(COMMENTS_RE, "");
2107
- while (true) {
2108
- const matches = KEYFRAMES_RE.exec(cssText);
2109
- if (!matches)
2110
- break;
2111
- result.push(matches[0]);
2112
- }
2113
- cssText = cssText.replace(KEYFRAMES_RE, "");
2114
- const IMPORT_RE = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
2115
- const UNIFIED_RE = new RegExp(
2116
- // eslint-disable-next-line
2117
- "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})",
2118
- "gi"
2119
- );
2120
- while (true) {
2121
- let matches = IMPORT_RE.exec(cssText);
2122
- if (!matches) {
2123
- matches = UNIFIED_RE.exec(cssText);
2124
- if (!matches) {
2125
- break;
2126
- } else {
2127
- IMPORT_RE.lastIndex = UNIFIED_RE.lastIndex;
2128
- }
2129
- } else {
2130
- UNIFIED_RE.lastIndex = IMPORT_RE.lastIndex;
2131
- }
2132
- result.push(matches[0]);
2133
- }
2134
- return result;
2135
- }
2136
- var URL_WITH_FORMAT_RE = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
2137
- var FONT_SRC_RE = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
2138
- function filterPreferredFormat(str, context) {
2139
- const { font } = context;
2140
- const preferredFormat = font ? font?.preferredFormat : void 0;
2141
- return preferredFormat ? str.replace(FONT_SRC_RE, (match) => {
2142
- while (true) {
2143
- const [src, , format] = URL_WITH_FORMAT_RE.exec(match) || [];
2144
- if (!format)
2145
- return "";
2146
- if (format === preferredFormat)
2147
- return `src: ${src};`;
2148
- }
2149
- }) : str;
2150
- }
2151
- function unwrapCssLayers(rules, out = []) {
2152
- for (const rule of Array.from(rules)) {
2153
- if (isLayerBlockRule(rule)) {
2154
- out.push(...unwrapCssLayers(rule.cssRules));
2155
- } else if ("cssRules" in rule) {
2156
- unwrapCssLayers(rule.cssRules, out);
2157
- } else {
2158
- out.push(rule);
2159
- }
2160
- }
2161
- return out;
2162
- }
2163
- var SVG_EXTERNAL_RESOURCE_REGEX = /\bx?link:?href\s*=\s*["'](?!data:)[^"']+["']/i;
2164
- function svgHasExternalResources(svg) {
2165
- return SVG_EXTERNAL_RESOURCE_REGEX.test(svg.innerHTML);
2166
- }
2167
- async function domToForeignObjectSvg(node, options) {
2168
- const context = await orCreateContext(node, options);
2169
- if (isElementNode(context.node) && isSVGElementNode(context.node) && !svgHasExternalResources(context.node))
2170
- return context.node;
2171
- const {
2172
- ownerDocument,
2173
- log,
2174
- tasks,
2175
- svgStyleElement,
2176
- svgDefsElement,
2177
- svgStyles,
2178
- font,
2179
- progress,
2180
- autoDestruct,
2181
- onCloneNode,
2182
- onEmbedNode,
2183
- onCreateForeignObjectSvg
2184
- } = context;
2185
- log.time("clone node");
2186
- const clone = await cloneNode(context.node, context, true);
2187
- if (svgStyleElement && ownerDocument) {
2188
- let allCssText = "";
2189
- svgStyles.forEach((klasses, cssText) => {
2190
- allCssText += `${klasses.join(",\n")} {
2191
- ${cssText}
2192
- }
2193
- `;
2194
- });
2195
- svgStyleElement.appendChild(ownerDocument.createTextNode(allCssText));
2196
- }
2197
- log.timeEnd("clone node");
2198
- await onCloneNode?.(clone);
2199
- if (font !== false && isElementNode(clone)) {
2200
- log.time("embed web font");
2201
- await embedWebFont(clone, context);
2202
- log.timeEnd("embed web font");
2203
- }
2204
- log.time("embed node");
2205
- embedNode(clone, context);
2206
- const count = tasks.length;
2207
- let current = 0;
2208
- const runTask = async () => {
2209
- while (true) {
2210
- const task = tasks.pop();
2211
- if (!task)
2212
- break;
2213
- try {
2214
- await task;
2215
- } catch (error) {
2216
- context.log.warn("Failed to run task", error);
2217
- }
2218
- progress?.(++current, count);
2219
- }
2220
- };
2221
- progress?.(current, count);
2222
- await Promise.all([...Array.from({ length: 4 })].map(runTask));
2223
- log.timeEnd("embed node");
2224
- await onEmbedNode?.(clone);
2225
- const svg = createForeignObjectSvg(clone, context);
2226
- svgDefsElement && svg.insertBefore(svgDefsElement, svg.children[0]);
2227
- svgStyleElement && svg.insertBefore(svgStyleElement, svg.children[0]);
2228
- autoDestruct && destroyContext(context);
2229
- await onCreateForeignObjectSvg?.(svg);
2230
- return svg;
2231
- }
2232
- function createForeignObjectSvg(clone, context) {
2233
- const { width, height } = context;
2234
- const svg = createSvg(width, height, clone.ownerDocument);
2235
- const foreignObject = svg.ownerDocument.createElementNS(svg.namespaceURI, "foreignObject");
2236
- foreignObject.setAttributeNS(null, "x", "0%");
2237
- foreignObject.setAttributeNS(null, "y", "0%");
2238
- foreignObject.setAttributeNS(null, "width", "100%");
2239
- foreignObject.setAttributeNS(null, "height", "100%");
2240
- foreignObject.append(clone);
2241
- svg.appendChild(foreignObject);
2242
- return svg;
2243
- }
2244
- async function domToCanvas(node, options) {
2245
- const context = await orCreateContext(node, options);
2246
- const svg = await domToForeignObjectSvg(context);
2247
- const dataUrl = svgToDataUrl(svg, context.isEnable("removeControlCharacter"));
2248
- if (!context.autoDestruct) {
2249
- context.svgStyleElement = createStyleElement(context.ownerDocument);
2250
- context.svgDefsElement = context.ownerDocument?.createElementNS(XMLNS, "defs");
2251
- context.svgStyles.clear();
2252
- }
2253
- const image = createImage(dataUrl, svg.ownerDocument);
2254
- return await imageToCanvas(image, context);
2255
- }
2256
-
2257
- // ../react/dist/screenshot.js
2258
- var MAX_WIDTH = 1200;
2259
- function roundedRect(ctx, x, y, w, h, r) {
2260
- ctx.moveTo(x + r, y);
2261
- ctx.arcTo(x + w, y, x + w, y + h, r);
2262
- ctx.arcTo(x + w, y + h, x, y + h, r);
2263
- ctx.arcTo(x, y + h, x, y, r);
2264
- ctx.arcTo(x, y, x + w, y, r);
2265
- ctx.closePath();
2266
- }
2267
- async function renderViewport(scale) {
2268
- return domToCanvas(document.documentElement, {
2269
- width: document.documentElement.clientWidth,
2270
- height: window.innerHeight,
2271
- scale,
2272
- timeout: 6e3,
2273
- backgroundColor: getComputedStyle(document.body).backgroundColor || "#ffffff",
2274
- filter: (node) => !(node instanceof Element && node.id === "cs-root"),
2275
- style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)`, transformOrigin: "0 0" }
2276
- });
2277
- }
2278
- function outline(ctx, width, height, ring, scale) {
2279
- const radius = Math.min(12 * scale, ring.w / 2, ring.h / 2);
2280
- ctx.beginPath();
2281
- ctx.rect(0, 0, width, height);
2282
- roundedRect(ctx, ring.x, ring.y, ring.w, ring.h, radius);
2283
- ctx.fillStyle = "rgba(15, 15, 20, 0.22)";
2284
- ctx.fill("evenodd");
2285
- ctx.beginPath();
2286
- roundedRect(ctx, ring.x, ring.y, ring.w, ring.h, radius);
2287
- ctx.lineWidth = 3 * scale;
2288
- ctx.strokeStyle = "#007aff";
2289
- ctx.stroke();
2290
- }
2291
- async function capturePage(highlight) {
2292
- const scale = Math.min(window.devicePixelRatio || 1, 2);
2293
- let page;
2294
- try {
2295
- page = await renderViewport(scale);
16
+ const payload = await response.json();
17
+ return payload.enabled && payload.apiUrl ? payload.apiUrl.replace(/\/+$/, "") : null;
2296
18
  } catch {
2297
- return null;
2298
- }
2299
- const width = document.documentElement.clientWidth;
2300
- const outScale = Math.min(scale, 1600 / width);
2301
- const out = document.createElement("canvas");
2302
- out.width = Math.round(width * outScale);
2303
- out.height = Math.round(window.innerHeight * outScale);
2304
- const ctx = out.getContext("2d");
2305
- if (!ctx)
2306
- return null;
2307
- ctx.drawImage(page, 0, 0, out.width, out.height);
2308
- const rect = highlight?.getBoundingClientRect();
2309
- if (rect && rect.width > 0 && rect.bottom > 0 && rect.top < window.innerHeight) {
2310
- const pad = 6;
2311
- outline(ctx, out.width, out.height, { x: (rect.left - pad) * outScale, y: (rect.top - pad) * outScale, w: (rect.width + pad * 2) * outScale, h: (rect.height + pad * 2) * outScale }, outScale);
2312
- }
2313
- return new Promise((resolve) => out.toBlob((blob) => resolve(blob), "image/jpeg", 0.85));
2314
- }
2315
- async function captureAround(element) {
2316
- const rect = element.getBoundingClientRect();
2317
- const viewportWidth = document.documentElement.clientWidth;
2318
- const viewportHeight = window.innerHeight;
2319
- if (rect.width < 1 || rect.bottom <= 0 || rect.top >= viewportHeight || rect.right <= 0 || rect.left >= viewportWidth)
2320
- return null;
2321
- const scale = Math.min(window.devicePixelRatio || 1, 2);
2322
- let page;
2323
- try {
2324
- page = await renderViewport(scale);
2325
- } catch {
2326
- return null;
19
+ return void 0;
2327
20
  }
2328
- const width = Math.min(viewportWidth, Math.max(rect.width + 200, 760));
2329
- const height = Math.min(viewportHeight, Math.max(rect.height + 200, 420));
2330
- const x = Math.max(0, Math.min(rect.left + rect.width / 2 - width / 2, viewportWidth - width));
2331
- const y = Math.max(0, Math.min(rect.top + rect.height / 2 - height / 2, viewportHeight - height));
2332
- const out = document.createElement("canvas");
2333
- const outScale = Math.min(scale, MAX_WIDTH / width);
2334
- out.width = Math.round(width * outScale);
2335
- out.height = Math.round(height * outScale);
2336
- const ctx = out.getContext("2d");
2337
- if (!ctx)
2338
- return null;
2339
- ctx.drawImage(page, x * scale, y * scale, width * scale, height * scale, 0, 0, out.width, out.height);
2340
- const pad = 6;
2341
- outline(ctx, out.width, out.height, { x: (rect.left - x - pad) * outScale, y: (rect.top - y - pad) * outScale, w: (rect.width + pad * 2) * outScale, h: (rect.height + pad * 2) * outScale }, outScale);
2342
- return out.toDataURL("image/jpeg", 0.82);
2343
- }
2344
-
2345
- // ../react/dist/table.js
2346
- import { jsx as _jsx5, jsxs as _jsxs5 } from "react/jsx-runtime";
2347
- import { useMemo as useMemo2, useState as useState4 } from "react";
2348
- var columns = [
2349
- { key: "comment", label: "Comment" },
2350
- { key: "role", label: "Role" },
2351
- { key: "page", label: "Page" },
2352
- { key: "author", label: "Author" },
2353
- { key: "replies", label: "Replies" },
2354
- { key: "status", label: "Status" },
2355
- { key: "updated", label: "Updated" }
2356
- ];
2357
- function sortValue(thread, key2) {
2358
- if (key2 === "comment")
2359
- return thread.messages[0]?.body ?? "";
2360
- if (key2 === "role")
2361
- return thread.role ?? "";
2362
- if (key2 === "page")
2363
- return thread.url || thread.route;
2364
- if (key2 === "author")
2365
- return thread.createdBy;
2366
- if (key2 === "replies")
2367
- return thread.messages.length;
2368
- if (key2 === "status")
2369
- return thread.status;
2370
- return thread.updatedAt;
2371
- }
2372
- function searchText(thread) {
2373
- return [
2374
- thread.createdBy,
2375
- thread.role ?? "",
2376
- thread.url || thread.route,
2377
- targetName(thread.anchor),
2378
- ...thread.messages.map((message) => message.body),
2379
- ...thread.fields.map((field) => `${field.label} ${field.value}`)
2380
- ].join("\n").toLowerCase();
2381
- }
2382
- function CommentTable({ rows, route, activeId, tabs, onSelect, onList, onClose }) {
2383
- const [query, setQuery] = useState4("");
2384
- const [sort, setSort] = useState4({ key: "updated", desc: true });
2385
- const showRole = rows.some((row) => row.thread.role);
2386
- const fieldLabels = useMemo2(() => {
2387
- const labels = [];
2388
- for (const { thread } of rows)
2389
- for (const field of thread.fields)
2390
- if (!labels.includes(field.label))
2391
- labels.push(field.label);
2392
- return labels;
2393
- }, [rows]);
2394
- const shown = useMemo2(() => {
2395
- const needle = query.trim().toLowerCase();
2396
- const matched = needle ? rows.filter((row) => searchText(row.thread).includes(needle)) : rows;
2397
- return [...matched].sort((a, b) => {
2398
- const left = sortValue(a.thread, sort.key);
2399
- const right = sortValue(b.thread, sort.key);
2400
- const order = typeof left === "number" && typeof right === "number" ? left - right : String(left).localeCompare(String(right));
2401
- return sort.desc ? -order : order;
2402
- });
2403
- }, [query, rows, sort]);
2404
- const visible = columns.filter((column) => column.key !== "role" || showRole);
2405
- const header = (column) => _jsx5("th", { "aria-sort": sort.key === column.key ? sort.desc ? "descending" : "ascending" : "none", children: _jsxs5("button", { type: "button", onClick: () => setSort((current) => ({ key: column.key, desc: current.key === column.key ? !current.desc : column.key === "updated" })), children: [column.label, sort.key === column.key ? _jsx5("span", { className: "cs-sort", children: sort.desc ? "\u2193" : "\u2191" }) : null] }) }, column.key);
2406
- return _jsxs5("section", { className: "cs-card cs-table-sheet cs-hit", "data-cs-ui": "panel", "aria-label": "Comments table", children: [_jsxs5("header", { className: "cs-sheet-head", children: [_jsx5("h2", { children: "Comments" }), _jsx5("input", { className: "cs-table-search", "data-cs-ui": "table-search", type: "search", "aria-label": "Search comments", placeholder: "Search", value: query, onChange: (event) => setQuery(event.target.value) }), _jsx5("button", { className: "cs-icon-button", "data-cs-ui": "list-view", type: "button", "aria-label": "List view", onClick: onList, children: _jsx5(List, {}) }), _jsx5("button", { className: "cs-icon-button", type: "button", "aria-label": "Close comments", onClick: onClose, children: _jsx5(Close, {}) })] }), tabs, _jsx5("div", { className: "cs-table-wrap", children: shown.length === 0 ? _jsx5("p", { className: "cs-empty", children: query ? "No comments match." : "No comments here." }) : _jsxs5("table", { className: "cs-table", "data-cs-ui": "table", children: [_jsx5("thead", { children: _jsxs5("tr", { children: [visible.slice(0, showRole ? 2 : 1).map(header), fieldLabels.map((label) => _jsx5("th", { children: _jsx5("span", { children: label }) }, `field:${label}`)), visible.slice(showRole ? 2 : 1).map(header)] }) }), _jsx5("tbody", { children: shown.map((row) => {
2407
- const { thread } = row;
2408
- const first = thread.messages[0];
2409
- const note = statusNote(row.status, thread.anchor, thread.route === route);
2410
- return _jsxs5("tr", { className: activeId === thread.id ? "is-active" : void 0, "data-cs-ui": "table-row", "data-thread-id": thread.id, tabIndex: 0, onClick: () => onSelect(row), onKeyDown: (event) => {
2411
- if (event.key === "Enter") {
2412
- event.preventDefault();
2413
- onSelect(row);
2414
- }
2415
- }, children: [_jsxs5("td", { className: "cs-cell-comment", children: [_jsx5("span", { className: "cs-cell-clamp", children: first?.body || "\u2014" }), _jsx5("small", { children: targetName(thread.anchor) })] }), showRole ? _jsx5("td", { children: thread.role ? _jsx5("span", { className: "cs-role", children: thread.role }) : null }) : null, fieldLabels.map((label) => _jsx5("td", { children: _jsx5("span", { className: "cs-cell-clamp", children: thread.fields.find((field) => field.label === label)?.value ?? "" }) }, `field:${label}`)), _jsx5("td", { className: "cs-cell-page", title: thread.url || thread.route, children: thread.url || thread.route }), _jsx5("td", { className: "cs-cell-nowrap", children: thread.createdBy }), _jsx5("td", { className: "cs-cell-num", children: thread.messages.length - 1 || "" }), _jsxs5("td", { className: "cs-cell-nowrap", children: [_jsx5("span", { className: thread.status === "resolved" ? "cs-state is-done" : "cs-state", children: thread.status === "resolved" ? "Resolved" : "Open" }), note && thread.status === "open" ? _jsx5("small", { className: `cs-note is-${note.tone}`, children: note.text }) : null] }), _jsx5("td", { className: "cs-cell-nowrap", children: _jsx5("time", { dateTime: thread.updatedAt, children: ago(thread.updatedAt) }) })] }, thread.id);
2416
- }) })] }) })] });
2417
21
  }
2418
-
2419
- // ../react/dist/handoff.js
2420
- var key = "devaccel.comment.handoff";
2421
- var ttl = 30 * 60 * 1e3;
2422
- function readHandoff() {
2423
- try {
2424
- const value = JSON.parse(sessionStorage.getItem(key) ?? "null");
2425
- return value && Date.now() - value.at < ttl ? value : null;
2426
- } catch {
22
+ async function loadRuntime(sessionPath, source) {
23
+ if (source === "bundled")
24
+ return bundled();
25
+ const apiUrl = await serviceUrl(sessionPath);
26
+ if (apiUrl === null)
2427
27
  return null;
2428
- }
2429
- }
2430
- function write(value) {
28
+ if (apiUrl === void 0)
29
+ return bundled();
30
+ const url = `${apiUrl}/v1/toolbar/${runtimeVersion}.js`;
2431
31
  try {
2432
- if (value.invite || value.thread)
2433
- sessionStorage.setItem(key, JSON.stringify(value));
2434
- else
2435
- sessionStorage.removeItem(key);
2436
- } catch {
2437
- }
2438
- }
2439
- function linkParams() {
2440
- const url = new URL(window.location.href);
2441
- const hash = new URLSearchParams(url.hash.slice(1));
2442
- return {
2443
- url,
2444
- hash,
2445
- invite: url.searchParams.get("cs-invite") ?? hash.get("cs-invite"),
2446
- thread: url.searchParams.get("cs-thread") ?? hash.get("cs-thread")
2447
- };
2448
- }
2449
- function captureHandoff() {
2450
- if (typeof window === "undefined")
2451
- return;
2452
- const { invite, thread } = linkParams();
2453
- if (!invite && !thread)
2454
- return;
2455
- write({ ...readHandoff(), ...invite ? { invite } : {}, ...thread ? { thread } : {}, at: Date.now() });
2456
- }
2457
- function dropHandoff(part) {
2458
- const current = readHandoff();
2459
- if (!current)
2460
- return;
2461
- const { [part]: _dropped, ...rest } = current;
2462
- write(rest);
2463
- }
2464
- function stripHandoffFromUrl() {
2465
- const { url, hash } = linkParams();
2466
- const inQuery = url.searchParams.has("cs-invite") || url.searchParams.has("cs-thread");
2467
- const inHash = hash.has("cs-invite") || hash.has("cs-thread");
2468
- if (!inQuery && !inHash)
2469
- return;
2470
- url.searchParams.delete("cs-invite");
2471
- url.searchParams.delete("cs-thread");
2472
- if (inHash)
2473
- url.hash = "";
2474
- history.replaceState(history.state, "", url);
2475
- }
2476
- function linkWith(href, name, value) {
2477
- const url = new URL(href, window.location.origin);
2478
- url.searchParams.delete("cs-invite");
2479
- url.searchParams.delete("cs-thread");
2480
- url.hash = `${name}=${encodeURIComponent(value)}`;
2481
- return url.toString();
2482
- }
2483
-
2484
- // ../react/dist/styles.js
2485
- var toolbarCss = `
2486
- #cs-root {
2487
- --cs-text: #1d1d1f;
2488
- --cs-text-2: #6e6e73;
2489
- --cs-text-3: #a1a1a6;
2490
- --cs-glass: rgba(251, 251, 253, 0.8);
2491
- --cs-card: rgba(255, 255, 255, 0.94);
2492
- --cs-fill: rgba(0, 0, 0, 0.05);
2493
- --cs-fill-2: rgba(0, 0, 0, 0.08);
2494
- --cs-line: rgba(0, 0, 0, 0.08);
2495
- --cs-edge: rgba(0, 0, 0, 0.1);
2496
- --cs-accent: #007aff;
2497
- --cs-accent-soft: rgba(0, 122, 255, 0.12);
2498
- --cs-danger: #ff3b30;
2499
- --cs-warn: #ff9500;
2500
- --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);
2501
- position: fixed;
2502
- inset: 0;
2503
- z-index: 2147483000;
2504
- pointer-events: none;
2505
- margin: 0;
2506
- padding: 0;
2507
- border: 0;
2508
- background: transparent;
2509
- color: var(--cs-text);
2510
- font: 13px/1.4 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Inter, system-ui, sans-serif;
2511
- letter-spacing: -0.005em;
2512
- -webkit-font-smoothing: antialiased;
2513
- text-align: left;
2514
- }
2515
- #cs-root.cs-dark {
2516
- --cs-text: #f5f5f7;
2517
- --cs-text-2: #a1a1a6;
2518
- --cs-text-3: #6e6e73;
2519
- --cs-glass: rgba(40, 40, 44, 0.78);
2520
- --cs-card: rgba(36, 36, 40, 0.96);
2521
- --cs-fill: rgba(255, 255, 255, 0.07);
2522
- --cs-fill-2: rgba(255, 255, 255, 0.12);
2523
- --cs-line: rgba(255, 255, 255, 0.08);
2524
- --cs-edge: rgba(255, 255, 255, 0.12);
2525
- --cs-accent: #0a84ff;
2526
- --cs-accent-soft: rgba(10, 132, 255, 0.2);
2527
- --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.45);
2528
- }
2529
- #cs-root.cs-inside { position: absolute; }
2530
- #cs-root, #cs-root * { box-sizing: border-box; }
2531
- #cs-root h2, #cs-root p { margin: 0; }
2532
- #cs-root button, #cs-root input, #cs-root textarea {
2533
- font: inherit; color: inherit; letter-spacing: inherit; margin: 0;
2534
- }
2535
- #cs-root button { background: none; border: 0; padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
2536
- #cs-root button:disabled { cursor: default; opacity: 0.45; }
2537
- #cs-root button:focus-visible, #cs-root [role=button]:focus-visible { outline: 2px solid var(--cs-accent); outline-offset: 2px; }
2538
- #cs-root .cs-hit { pointer-events: auto; }
2539
- #cs-root .cs-icon { display: block; flex: none; }
2540
-
2541
- /* Bar */
2542
- #cs-root .cs-bar {
2543
- position: absolute;
2544
- bottom: 16px;
2545
- display: flex;
2546
- align-items: center;
2547
- gap: 2px;
2548
- padding: 4px;
2549
- border-radius: 999px;
2550
- background: var(--cs-glass);
2551
- -webkit-backdrop-filter: saturate(180%) blur(20px);
2552
- backdrop-filter: saturate(180%) blur(20px);
2553
- box-shadow: var(--cs-shadow);
2554
- }
2555
- #cs-root.cs-at-center .cs-bar { left: 50%; transform: translateX(-50%); }
2556
- #cs-root.cs-at-left .cs-bar { left: 16px; }
2557
- #cs-root.cs-at-right .cs-bar { right: 16px; }
2558
- #cs-root .cs-tool {
2559
- position: relative;
2560
- width: 34px;
2561
- height: 34px;
2562
- border-radius: 999px;
2563
- display: inline-flex;
2564
- align-items: center;
2565
- justify-content: center;
2566
- color: var(--cs-text);
2567
- transition: background-color 0.15s ease, color 0.15s ease;
2568
- }
2569
- #cs-root .cs-tool:hover { background: var(--cs-fill); }
2570
- #cs-root .cs-tool.is-on { background: var(--cs-fill-2); }
2571
- #cs-root .cs-tool.is-accent { background: var(--cs-accent); color: #fff; }
2572
- #cs-root .cs-tool[data-tip]::after {
2573
- content: attr(data-tip);
2574
- position: absolute;
2575
- bottom: calc(100% + 10px);
2576
- left: 50%;
2577
- transform: translate(-50%, 2px);
2578
- white-space: pre;
2579
- padding: 4px 8px;
2580
- border-radius: 7px;
2581
- background: rgba(29, 29, 31, 0.92);
2582
- color: #fff;
2583
- font-size: 11.5px;
2584
- font-weight: 500;
2585
- opacity: 0;
2586
- pointer-events: none;
2587
- transition: opacity 0.12s ease, transform 0.12s ease;
2588
- }
2589
- #cs-root .cs-tool[data-tip]:hover::after { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.35s; }
2590
- #cs-root .cs-me { width: 34px; }
2591
- #cs-root .cs-divider { width: 1px; height: 18px; background: var(--cs-line); margin: 0 3px; }
2592
- #cs-root .cs-badge {
2593
- position: absolute;
2594
- top: 3px;
2595
- right: 2px;
2596
- min-width: 16px;
2597
- height: 16px;
2598
- padding: 0 4px;
2599
- border-radius: 999px;
2600
- background: var(--cs-accent);
2601
- color: #fff;
2602
- font-size: 10px;
2603
- font-weight: 600;
2604
- line-height: 16px;
2605
- text-align: center;
2606
- box-shadow: 0 0 0 2px var(--cs-glass);
2607
- }
2608
- #cs-root .cs-bar-lock {
2609
- display: inline-flex;
2610
- align-items: center;
2611
- gap: 7px;
2612
- height: 34px;
2613
- padding: 0 14px 0 12px;
2614
- border-radius: 999px;
2615
- font-weight: 500;
2616
- }
2617
- #cs-root .cs-bar-lock:hover { background: var(--cs-fill); }
2618
- #cs-root .cs-avatar {
2619
- display: inline-flex;
2620
- align-items: center;
2621
- justify-content: center;
2622
- flex: none;
2623
- border-radius: 999px;
2624
- color: #fff;
2625
- font-weight: 600;
2626
- letter-spacing: 0;
2627
- line-height: 1;
2628
- user-select: none;
2629
- }
2630
-
2631
- /* Cards */
2632
- #cs-root .cs-card {
2633
- position: absolute;
2634
- background: var(--cs-card);
2635
- -webkit-backdrop-filter: saturate(180%) blur(24px);
2636
- backdrop-filter: saturate(180%) blur(24px);
2637
- border-radius: 14px;
2638
- box-shadow: var(--cs-shadow);
2639
- }
2640
- #cs-root .cs-unlock, #cs-root .cs-menu { bottom: 64px; }
2641
- #cs-root.cs-at-center .cs-unlock, #cs-root.cs-at-center .cs-menu { left: 50%; transform: translateX(-50%); }
2642
- #cs-root.cs-at-left .cs-unlock, #cs-root.cs-at-left .cs-menu { left: 16px; }
2643
- #cs-root.cs-at-right .cs-unlock, #cs-root.cs-at-right .cs-menu { right: 16px; }
2644
- #cs-root .cs-unlock { width: 280px; padding: 20px; text-align: center; }
2645
- #cs-root .cs-unlock-icon {
2646
- width: 40px; height: 40px; margin: 0 auto 10px; border-radius: 12px;
2647
- display: flex; align-items: center; justify-content: center; background: var(--cs-fill); color: var(--cs-text-2);
2648
- }
2649
- #cs-root .cs-unlock h2 { font-size: 15px; font-weight: 600; }
2650
- #cs-root .cs-sub { color: var(--cs-text-2); margin-top: 4px; }
2651
- #cs-root .cs-unlock form { margin-top: 14px; display: grid; gap: 8px; text-align: left; }
2652
- #cs-root .cs-field { display: grid; gap: 4px; }
2653
- #cs-root .cs-field span { font-size: 12px; color: var(--cs-text-2); padding-left: 2px; }
2654
- #cs-root input, #cs-root textarea {
2655
- width: 100%;
2656
- border: 0;
2657
- border-radius: 9px;
2658
- background: var(--cs-fill);
2659
- padding: 8px 10px;
2660
- outline: none;
2661
- transition: box-shadow 0.15s ease, background-color 0.15s ease;
2662
- }
2663
- #cs-root input:focus, #cs-root textarea:focus { background: transparent; box-shadow: 0 0 0 1px var(--cs-accent), 0 0 0 4px var(--cs-accent-soft); }
2664
- #cs-root textarea { resize: none; }
2665
- #cs-root .cs-error { color: var(--cs-danger); font-size: 12px; }
2666
- #cs-root .cs-pad { padding: 6px 14px 0; }
2667
- #cs-root .cs-hint { color: var(--cs-text-3); font-size: 11.5px; }
2668
-
2669
- /* Buttons */
2670
- #cs-root .cs-button {
2671
- display: inline-flex;
2672
- align-items: center;
2673
- justify-content: center;
2674
- gap: 5px;
2675
- height: 28px;
2676
- padding: 0 11px;
2677
- border-radius: 8px;
2678
- background: var(--cs-fill);
2679
- font-size: 12.5px;
2680
- font-weight: 500;
2681
- transition: background-color 0.15s ease;
2682
- }
2683
- #cs-root .cs-button:hover { background: var(--cs-fill-2); }
2684
- #cs-root .cs-button.is-primary { background: var(--cs-accent); color: #fff; }
2685
- #cs-root .cs-button.is-primary:hover { filter: brightness(1.06); }
2686
- #cs-root .cs-button.is-danger { background: var(--cs-danger); color: #fff; }
2687
- #cs-root .cs-button.is-wide { width: 100%; height: 32px; margin-top: 4px; }
2688
- #cs-root .cs-icon-button {
2689
- width: 26px; height: 26px; border-radius: 999px; flex: none;
2690
- display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-2);
2691
- }
2692
- #cs-root .cs-icon-button:hover { background: var(--cs-fill); color: var(--cs-text); }
2693
- #cs-root .cs-send {
2694
- width: 26px; height: 26px; border-radius: 999px; flex: none;
2695
- display: inline-flex; align-items: center; justify-content: center; background: var(--cs-accent); color: #fff;
2696
- }
2697
- #cs-root .cs-send:disabled { background: var(--cs-fill-2); color: var(--cs-text-3); opacity: 1; }
2698
-
2699
- /* Menu */
2700
- #cs-root .cs-menu { width: 264px; padding: 6px; }
2701
- #cs-root .cs-me-row { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }
2702
- #cs-root .cs-me-name { display: flex; align-items: center; gap: 4px; min-width: 0; }
2703
- #cs-root .cs-me-name strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2704
- #cs-root .cs-rename { display: flex; align-items: center; gap: 4px; flex: 1; }
2705
- #cs-root .cs-rename input { padding: 5px 8px; }
2706
- #cs-root .cs-sep { height: 1px; background: var(--cs-line); margin: 4px 8px; }
2707
- #cs-root .cs-item {
2708
- display: flex; align-items: center; justify-content: space-between; gap: 10px;
2709
- width: 100%; min-height: 32px; padding: 0 8px; border-radius: 8px; text-align: left;
2710
- }
2711
- #cs-root button.cs-item:hover { background: var(--cs-fill); }
2712
- #cs-root .cs-item.is-danger { color: var(--cs-danger); }
2713
- #cs-root .cs-switch { position: relative; width: 32px; height: 20px; border-radius: 999px; background: var(--cs-fill-2); transition: background-color 0.2s ease; flex: none; }
2714
- #cs-root .cs-switch::after {
2715
- content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 999px;
2716
- background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); transition: transform 0.2s ease;
2717
- }
2718
- #cs-root .cs-switch.is-on { background: #34c759; }
2719
- #cs-root .cs-switch.is-on::after { transform: translateX(12px); }
2720
- #cs-root .cs-segment { display: inline-flex; padding: 2px; border-radius: 8px; background: var(--cs-fill); }
2721
- #cs-root .cs-segment button { padding: 2px 8px; border-radius: 6px; font-size: 12px; color: var(--cs-text-2); }
2722
- #cs-root .cs-segment button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); font-weight: 500; }
2723
- #cs-root .cs-menu .cs-hint { padding: 6px 8px 4px; }
2724
- #cs-root .cs-invite { padding: 2px 8px 6px; }
2725
- #cs-root .cs-invite input { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; padding: 6px 8px; }
2726
- #cs-root .cs-invite .cs-hint { padding: 4px 0 0; }
2727
- #cs-root .cs-confirm { padding: 10px; }
2728
- #cs-root .cs-confirm h2 { font-size: 14px; font-weight: 600; }
2729
- #cs-root .cs-row-end { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; }
2730
-
2731
- /* Sheet */
2732
- #cs-root .cs-sheet {
2733
- top: 12px; right: 12px; max-height: calc(100% - 88px); width: min(360px, calc(100vw - 24px));
2734
- display: flex; flex-direction: column; overflow: hidden;
2735
- }
2736
- #cs-root .cs-sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 10px 4px 16px; }
2737
- #cs-root .cs-sheet-head .cs-push { margin-left: auto; }
2738
- #cs-root .cs-sheet-head h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
2739
- #cs-root .cs-tabs { display: flex; gap: 2px; margin: 4px 12px 6px; padding: 2px; border-radius: 9px; background: var(--cs-fill); }
2740
- #cs-root .cs-tabs button {
2741
- flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
2742
- height: 26px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--cs-text-2);
2743
- }
2744
- #cs-root .cs-tabs button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }
2745
- #cs-root .cs-count { font-size: 11px; color: var(--cs-text-3); font-variant-numeric: tabular-nums; }
2746
- #cs-root .cs-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 6px 8px; overscroll-behavior: contain; }
2747
- #cs-root .cs-group { padding: 10px 10px 4px; font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.04em; }
2748
- #cs-root .cs-empty { padding: 36px 16px; text-align: center; color: var(--cs-text-2); display: grid; gap: 12px; justify-items: center; }
2749
- #cs-root .cs-thread { border-radius: 10px; }
2750
- #cs-root .cs-thread + .cs-thread { margin-top: 1px; }
2751
- #cs-root .cs-thread.is-open { background: var(--cs-fill); }
2752
- #cs-root .cs-thread-main { display: flex; gap: 10px; padding: 10px; border-radius: 10px; cursor: pointer; }
2753
- #cs-root .cs-thread:not(.is-open) .cs-thread-main:hover { background: var(--cs-fill); }
2754
- #cs-root .cs-thread-body { min-width: 0; flex: 1; }
2755
- #cs-root .cs-meta { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
2756
- #cs-root .cs-meta strong { font-weight: 600; }
2757
- #cs-root .cs-meta time { color: var(--cs-text-3); font-size: 12px; }
2758
- #cs-root .cs-text { margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }
2759
- #cs-root .cs-target { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); }
2760
- #cs-root .cs-note { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }
2761
- #cs-root .cs-note::before { content: ""; width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
2762
- #cs-root .cs-note.is-quiet { color: var(--cs-text-2); }
2763
- #cs-root .cs-note.is-warn { color: var(--cs-warn); }
2764
- #cs-root .cs-note.is-bad { color: var(--cs-danger); }
2765
- #cs-root .cs-replies { margin-top: 4px; font-size: 12px; color: var(--cs-accent); font-weight: 500; }
2766
- #cs-root .cs-thread-more { padding: 0 10px 10px 44px; display: grid; gap: 10px; }
2767
- #cs-root .cs-message { display: flex; gap: 8px; }
2768
- #cs-root .cs-reply { display: flex; align-items: flex-end; gap: 6px; padding: 4px 4px 4px 0; border-radius: 10px; }
2769
- #cs-root .cs-reply textarea { min-height: 32px; max-height: 120px; field-sizing: content; }
2770
- #cs-root .cs-thread.is-open .cs-reply textarea { background: var(--cs-card); }
2771
- #cs-root .cs-actions { display: flex; flex-wrap: wrap; gap: 6px; }
2772
- #cs-root .cs-thread.is-open .cs-button { background: var(--cs-card); box-shadow: 0 0 0 0.5px var(--cs-edge); }
2773
- #cs-root .cs-thread.is-open .cs-button:hover { background: var(--cs-fill-2); }
2774
-
2775
- /* Mentions */
2776
- #cs-root .cs-mention { color: var(--cs-accent); background: var(--cs-accent-soft); border-radius: 4px; padding: 0 3px; font-weight: 500; }
2777
- #cs-root .cs-delivery { margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); display: flex; align-items: center; gap: 5px; }
2778
- #cs-root .cs-delivery::before { content: ""; width: 6px; height: 6px; border-radius: 999px; background: #34c759; }
2779
- #cs-root .cs-people {
2780
- position: fixed; z-index: 1; width: 264px; padding: 5px; border-radius: 12px;
2781
- background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: auto;
2782
- }
2783
- #cs-root .cs-person { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px; cursor: pointer; }
2784
- #cs-root .cs-person.is-on { background: var(--cs-accent); color: #fff; }
2785
- #cs-root .cs-person-text { display: grid; line-height: 1.25; min-width: 0; }
2786
- #cs-root .cs-person-text strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2787
- #cs-root .cs-person-text small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2788
- #cs-root .cs-person.is-on small { color: rgba(255, 255, 255, 0.8); }
2789
- #cs-root .cs-field small { font-size: 11.5px; color: var(--cs-text-3); padding-left: 2px; }
2790
- #cs-root .cs-me-lines { display: grid; line-height: 1.25; min-width: 0; }
2791
- #cs-root .cs-me-lines small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2792
-
2793
- /* Composer */
2794
- #cs-root .cs-composer { width: min(320px, calc(100% - 16px)); }
2795
- #cs-root .cs-composer-target {
2796
- display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 12px;
2797
- border-bottom: 1px solid var(--cs-line); font-size: 12px; color: var(--cs-text-2);
2798
- }
2799
- #cs-root .cs-composer-name { font-weight: 500; color: var(--cs-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2800
- #cs-root .cs-composer-target code {
2801
- margin-left: auto; font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--cs-text-3);
2802
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; min-width: 0; max-width: 45%;
2803
- }
2804
- #cs-root .cs-composer-body { display: flex; gap: 10px; padding: 12px 12px 4px; }
2805
- #cs-root .cs-composer-body textarea { background: transparent; padding: 3px 0; min-height: 56px; box-shadow: none; border-radius: 0; }
2806
- #cs-root .cs-composer-foot { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px 40px; }
2807
- #cs-root .cs-composer-foot .cs-hint { margin-left: auto; }
2808
-
2809
- /* Roles and fields */
2810
- #cs-root .cs-details-input { display: grid; gap: 8px; padding: 2px 12px 6px 46px; }
2811
- #cs-root .cs-details-edit .cs-details-input { padding: 0; }
2812
- #cs-root .cs-detail-field { display: grid; gap: 3px; }
2813
- #cs-root .cs-detail-field span, #cs-root .cs-roles-label, #cs-root .cs-detail-list dt {
2814
- font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.03em;
2815
- }
2816
- #cs-root .cs-detail-field textarea { padding: 6px 8px; min-height: 30px; max-height: 120px; field-sizing: content; }
2817
- #cs-root .cs-thread.is-open .cs-detail-field textarea { background: var(--cs-card); }
2818
- #cs-root .cs-roles { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
2819
- #cs-root .cs-roles-label { margin-right: 4px; }
2820
- #cs-root .cs-chip {
2821
- display: inline-flex; align-items: center; gap: 2px; height: 24px; padding: 0 9px; border-radius: 999px;
2822
- background: var(--cs-fill); font-size: 12px; font-weight: 500; color: var(--cs-text-2);
2823
- }
2824
- #cs-root button.cs-chip:hover { background: var(--cs-fill-2); }
2825
- #cs-root .cs-chip.is-on, #cs-root button.cs-chip.is-on:hover { background: var(--cs-accent); color: #fff; }
2826
- #cs-root .cs-chip.is-static { padding-right: 3px; color: var(--cs-text); }
2827
- #cs-root .cs-chip.is-static button {
2828
- width: 18px; height: 18px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);
2829
- }
2830
- #cs-root .cs-chip.is-static button:hover { background: var(--cs-fill-2); color: var(--cs-text); }
2831
- #cs-root .cs-chip.is-static .cs-icon { width: 11px; height: 11px; }
2832
- #cs-root .cs-chips { display: flex; flex-wrap: wrap; gap: 4px; }
2833
- #cs-root .cs-role {
2834
- display: inline-flex; align-items: center; height: 18px; padding: 0 7px; border-radius: 999px;
2835
- background: var(--cs-accent-soft); color: var(--cs-accent); font-size: 11px; font-weight: 600;
2836
- }
2837
- #cs-root .cs-detail-list { display: grid; gap: 6px; margin: 6px 0 0; }
2838
- #cs-root .cs-detail-list > div { padding: 6px 8px; border-radius: 8px; background: var(--cs-fill); }
2839
- #cs-root .cs-thread.is-open .cs-detail-list > div { background: var(--cs-card); }
2840
- #cs-root .cs-detail-list dd { margin: 1px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
2841
- #cs-root .cs-detail-list.is-clamped dd { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
2842
- #cs-root .cs-details-edit { display: grid; gap: 8px; }
2843
- #cs-root .cs-details-edit .cs-row-end { margin-top: 0; }
2844
- #cs-root .cs-settings { width: 280px; }
2845
- #cs-root .cs-settings-head { display: flex; align-items: center; gap: 4px; padding: 4px 4px 6px; }
2846
- #cs-root .cs-settings-head h2 { font-size: 14px; font-weight: 600; }
2847
- #cs-root .cs-settings-body { display: grid; gap: 6px; padding: 2px 8px 8px; }
2848
- #cs-root .cs-field-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-height: 28px; padding-left: 2px; }
2849
- #cs-root .cs-add-row { display: flex; align-items: center; gap: 4px; }
2850
- #cs-root .cs-add-row input { padding: 5px 8px; }
2851
- #cs-root .cs-settings .cs-hint { padding: 6px 8px 4px; }
2852
-
2853
- /* Table */
2854
- #cs-root .cs-table-sheet {
2855
- top: 12px; right: 12px; width: min(1100px, calc(100vw - 24px)); max-height: calc(100% - 88px);
2856
- display: flex; flex-direction: column; overflow: hidden;
2857
- }
2858
- #cs-root .cs-table-sheet .cs-sheet-head { gap: 6px; }
2859
- #cs-root .cs-table-sheet .cs-sheet-head h2 { margin-right: auto; }
2860
- #cs-root .cs-table-search { width: 220px; padding: 5px 10px; }
2861
- #cs-root .cs-table-sheet .cs-tabs { max-width: 420px; }
2862
- #cs-root .cs-table-wrap { flex: 1 1 auto; min-height: 0; overflow: auto; border-top: 1px solid var(--cs-line); overscroll-behavior: contain; }
2863
- #cs-root .cs-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
2864
- #cs-root .cs-table th {
2865
- position: sticky; top: 0; z-index: 1; background: var(--cs-card); text-align: left; font-weight: 600;
2866
- font-size: 11px; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.03em;
2867
- padding: 0; border-bottom: 1px solid var(--cs-line); white-space: nowrap;
2868
- }
2869
- #cs-root .cs-table th > button, #cs-root .cs-table th > span {
2870
- display: inline-flex; align-items: center; gap: 4px; padding: 8px 10px; font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
2871
- }
2872
- #cs-root .cs-table th > button:hover { color: var(--cs-text); }
2873
- #cs-root .cs-sort { color: var(--cs-accent); }
2874
- #cs-root .cs-table td { padding: 8px 10px; vertical-align: top; border-bottom: 1px solid var(--cs-line); max-width: 260px; }
2875
- #cs-root .cs-table tbody tr { cursor: pointer; }
2876
- #cs-root .cs-table tbody tr:hover { background: var(--cs-fill); }
2877
- #cs-root .cs-table tbody tr.is-active { background: var(--cs-accent-soft); }
2878
- #cs-root .cs-table tbody tr:focus-visible { outline: 2px solid var(--cs-accent); outline-offset: -2px; }
2879
- #cs-root .cs-cell-comment { min-width: 220px; }
2880
- #cs-root .cs-cell-comment small, #cs-root .cs-table td small { display: block; margin-top: 2px; font-size: 11.5px; color: var(--cs-text-3); }
2881
- #cs-root .cs-cell-clamp { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; white-space: pre-wrap; overflow-wrap: anywhere; }
2882
- #cs-root .cs-cell-page { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--cs-text-2); max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2883
- #cs-root .cs-cell-nowrap { white-space: nowrap; }
2884
- #cs-root .cs-cell-num { text-align: right; font-variant-numeric: tabular-nums; color: var(--cs-text-2); }
2885
- #cs-root .cs-state { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }
2886
- #cs-root .cs-state::before { content: ""; width: 6px; height: 6px; border-radius: 999px; background: #34c759; }
2887
- #cs-root .cs-state.is-done { color: var(--cs-text-3); }
2888
- #cs-root .cs-state.is-done::before { background: var(--cs-text-3); }
2889
- #cs-root .cs-table .cs-note { display: flex; margin-top: 2px; font-size: 11px; }
2890
-
2891
- /* Attachments */
2892
- #cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }
2893
- #cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 12px 2px 46px; }
2894
- #cs-root .cs-thread-more .cs-tray { padding: 0; }
2895
- #cs-root .cs-pending {
2896
- position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 170px; height: 34px;
2897
- padding: 0 24px 0 4px; border-radius: 8px; background: var(--cs-fill); font-size: 11.5px; color: var(--cs-text-2);
2898
- }
2899
- #cs-root .cs-pending img { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; flex: none; }
2900
- #cs-root .cs-pending .cs-icon { margin-left: 4px; }
2901
- #cs-root .cs-pending-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2902
- #cs-root .cs-pending.is-uploading { opacity: 0.6; }
2903
- #cs-root .cs-pending.is-failed { box-shadow: inset 0 0 0 1px var(--cs-danger); }
2904
- #cs-root .cs-pending-remove {
2905
- position: absolute; right: 3px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; border-radius: 999px;
2906
- display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);
2907
- }
2908
- #cs-root .cs-pending-remove:hover { background: var(--cs-fill-2); color: var(--cs-text); }
2909
- #cs-root .cs-pending-remove .cs-icon { width: 11px; height: 11px; margin: 0; }
2910
- #cs-root .cs-files { display: grid; gap: 6px; margin-top: 6px; }
2911
- #cs-root .cs-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
2912
- #cs-root .cs-thumb { display: block; width: 112px; height: 72px; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); }
2913
- #cs-root .cs-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
2914
- #cs-root .cs-file {
2915
- display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 6px 10px; border-radius: 8px;
2916
- background: var(--cs-fill); color: var(--cs-text); text-decoration: none; font-size: 12px; justify-self: start;
2917
- }
2918
- #cs-root .cs-file:hover { background: var(--cs-fill-2); }
2919
- #cs-root .cs-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2920
- #cs-root .cs-file-size { color: var(--cs-text-3); flex: none; }
2921
-
2922
- /* Page overlays */
2923
- #cs-root .cs-pin {
2924
- position: absolute;
2925
- transform: translate(-50%, -50%);
2926
- padding: 2px;
2927
- border-radius: 999px;
2928
- background: var(--cs-card);
2929
- box-shadow: 0 0 0 0.5px var(--cs-edge), 0 3px 10px rgba(0, 0, 0, 0.18);
2930
- pointer-events: auto;
2931
- transition: transform 0.15s ease;
2932
- }
2933
- #cs-root .cs-pin:hover { transform: translate(-50%, -50%) scale(1.08); }
2934
- #cs-root .cs-pin.is-active { box-shadow: 0 0 0 2px var(--cs-accent), 0 3px 10px rgba(0, 0, 0, 0.18); }
2935
- #cs-root .cs-pin.is-drifted { box-shadow: 0 0 0 1.5px var(--cs-warn), 0 3px 10px rgba(0, 0, 0, 0.18); }
2936
- #cs-root .cs-pin-count {
2937
- position: absolute; top: -5px; right: -6px; min-width: 15px; height: 15px; padding: 0 4px;
2938
- border-radius: 999px; background: var(--cs-text); color: var(--cs-card);
2939
- font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
2940
- }
2941
- #cs-root .cs-peek {
2942
- position: absolute; width: 240px; padding: 10px 12px; border-radius: 12px;
2943
- background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: none;
2944
- }
2945
- #cs-root .cs-peek .cs-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
2946
- #cs-root .cs-outline {
2947
- position: absolute; border-radius: 8px; pointer-events: none;
2948
- box-shadow: 0 0 0 2px var(--cs-accent); background: var(--cs-accent-soft);
2949
- }
2950
- #cs-root .cs-outline.is-flash { animation: cs-flash 1.4s ease forwards; }
2951
- @keyframes cs-flash { 0%, 60% { opacity: 1; } 100% { opacity: 0; } }
2952
- #cs-root .cs-outline-label {
2953
- position: absolute; max-width: 360px; padding: 3px 8px; border-radius: 6px; pointer-events: none;
2954
- background: var(--cs-accent); color: #fff; font-size: 11.5px; font-weight: 500;
2955
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2956
- }
2957
- #cs-root .cs-banner {
2958
- position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
2959
- display: flex; align-items: center; gap: 8px; padding: 5px 5px 5px 12px; border-radius: 999px;
2960
- background: var(--cs-glass); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
2961
- box-shadow: var(--cs-shadow); font-weight: 500; white-space: nowrap;
2962
- }
2963
- #cs-root .cs-banner button { padding: 3px 10px; border-radius: 999px; background: var(--cs-fill); font-size: 12px; }
2964
- #cs-root .cs-banner button:hover { background: var(--cs-fill-2); }
2965
- #cs-root .cs-toast {
2966
- position: absolute; top: 14px; left: 50%; transform: translateX(-50%); padding: 6px 12px; border-radius: 999px;
2967
- background: rgba(29, 29, 31, 0.9); color: #fff; font-size: 12px; font-weight: 500;
2968
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); pointer-events: none; white-space: nowrap;
2969
- }
2970
-
2971
- /* Docked inside an open <dialog> */
2972
- #cs-root.cs-inside .cs-bar { bottom: 8px; }
2973
- #cs-root.cs-inside.cs-at-left .cs-bar { left: 8px; }
2974
- #cs-root.cs-inside.cs-at-right .cs-bar { right: 8px; }
2975
- #cs-root.cs-inside .cs-sheet { top: 8px; right: 8px; bottom: auto; max-height: 46%; width: min(300px, calc(100% - 16px)); }
2976
- #cs-root.cs-inside .cs-unlock, #cs-root.cs-inside .cs-menu { bottom: 52px; }
2977
- #cs-root.cs-inside .cs-toast { top: 8px; }
2978
- `;
2979
-
2980
- // ../react/dist/toolbar.js
2981
- captureHandoff();
2982
- var historyFlag = "__devaccelCommentHistory";
2983
- var prefsKey = "devaccel.comment.prefs";
2984
- var positions = [
2985
- { value: "bottom-left", label: "Left" },
2986
- { value: "bottom-center", label: "Center" },
2987
- { value: "bottom-right", label: "Right" }
2988
- ];
2989
- function readPrefs() {
2990
- try {
2991
- return JSON.parse(localStorage.getItem(prefsKey) ?? "{}");
2992
- } catch {
2993
- return {};
2994
- }
2995
- }
2996
- function overlayHost() {
2997
- const dialogs = [...document.querySelectorAll("dialog")].filter((node) => node instanceof HTMLDialogElement && node.open && !node.closest("#cs-root"));
2998
- return dialogs.at(-1) ?? document.body;
2999
- }
3000
- function placeAgainst(element, host) {
3001
- const rect = element.getBoundingClientRect();
3002
- if (rect.width < 1 && rect.height < 1)
3003
- return null;
3004
- if (host === document.body)
3005
- return { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
3006
- const hostRect = host.getBoundingClientRect();
3007
- return {
3008
- top: rect.top - hostRect.top + host.scrollTop,
3009
- left: rect.left - hostRect.left + host.scrollLeft,
3010
- width: rect.width,
3011
- height: rect.height
3012
- };
3013
- }
3014
- function pageHref() {
3015
- const url = new URL(window.location.href);
3016
- url.searchParams.delete("cs-thread");
3017
- url.searchParams.delete("cs-invite");
3018
- const hash = url.hash.includes("cs-thread=") || url.hash.includes("cs-invite=") ? "" : url.hash;
3019
- return `${url.pathname}${url.search}${hash}`;
3020
- }
3021
- function isTyping(target) {
3022
- if (!(target instanceof HTMLElement))
3023
- return false;
3024
- return target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName);
3025
- }
3026
- function submitOnCmdEnter(event) {
3027
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
3028
- event.preventDefault();
3029
- event.currentTarget.form?.requestSubmit();
3030
- }
3031
- }
3032
- async function loadSession(sessionPath) {
3033
- const response = await fetch(sessionPath, {
3034
- headers: { accept: "application/json", "x-comment-service-origin": window.location.origin },
3035
- cache: "no-store"
3036
- });
3037
- const payload = await response.json();
3038
- if (!payload.enabled || !payload.apiUrl || !payload.project || !payload.environment || !payload.hostToken)
3039
- return null;
3040
- return { apiUrl: payload.apiUrl, project: payload.project, environment: payload.environment, hostToken: payload.hostToken };
3041
- }
3042
- function Avatar({ name, size = 24 }) {
3043
- return _jsx6("span", { className: "cs-avatar", style: { width: size, height: size, fontSize: size * 0.4, background: colorFor(name) }, children: initials(name) });
3044
- }
3045
- function CommentToolbar({ sessionPath = "/api/comment-service/session", pollMs = 4e3, position = "bottom-center", theme = "light", navigate }) {
3046
- const [phase, setPhase] = useState5("boot");
3047
- const [session, setSession] = useState5(null);
3048
- const [member, setMember] = useState5(null);
3049
- const [threads, setThreads] = useState5([]);
3050
- const [route, setRoute] = useState5("/");
3051
- const [surface, setSurface] = useState5("none");
3052
- const [tab, setTab] = useState5("page");
3053
- const [mode, setMode] = useState5("browse");
3054
- const [reattachId, setReattachId] = useState5(null);
3055
- const [activeId, setActiveId] = useState5(null);
3056
- const [peekId, setPeekId] = useState5(null);
3057
- const [draft, setDraft] = useState5(null);
3058
- const [body, setBody] = useState5("");
3059
- const [reply, setReply] = useState5("");
3060
- const [name, setName] = useState5("");
3061
- const [passphrase, setPassphrase] = useState5("");
3062
- const [email, setEmail] = useState5("");
3063
- const [members, setMembers] = useState5([]);
3064
- const [profile, setProfile] = useState5(null);
3065
- const [deliveries, setDeliveries] = useState5({});
3066
- const [invite, setInvite] = useState5(null);
3067
- const [capturing, setCapturing] = useState5(false);
3068
- const [inviteLink, setInviteLink] = useState5(null);
3069
- const [renaming, setRenaming] = useState5(null);
3070
- const [confirmReset, setConfirmReset] = useState5(false);
3071
- const [error, setError] = useState5("");
3072
- const [toast, setToast] = useState5("");
3073
- const [busy, setBusy] = useState5(false);
3074
- const [prefs, setPrefs] = useState5({});
3075
- const [portal, setPortal] = useState5(null);
3076
- const [mounted, setMounted] = useState5(false);
3077
- const [highlight, setHighlight] = useState5(null);
3078
- const [flash, setFlash] = useState5(null);
3079
- const [placed, setPlaced] = useState5([]);
3080
- const [systemDark, setSystemDark] = useState5(false);
3081
- const [settings, setSettings] = useState5(null);
3082
- const [draftDetails, setDraftDetails] = useState5(noDetails);
3083
- const [editingId, setEditingId] = useState5(null);
3084
- const [view, setView] = useState5("list");
3085
- const threadsRef = useRef3(threads);
3086
- threadsRef.current = threads;
3087
- const modeRef = useRef3(mode);
3088
- modeRef.current = mode;
3089
- const draftPicked = useRef3(/* @__PURE__ */ new Map());
3090
- const replyPicked = useRef3(/* @__PURE__ */ new Map());
3091
- const draftShot = useRef3(null);
3092
- const replyShot = useRef3(null);
3093
- const pendingThread = useRef3(null);
3094
- const arrival = useRef3(null);
3095
- const draftFiles = useAttachments(session, member, setError);
3096
- const replyFiles = useAttachments(session, member, setError);
3097
- const resetReplyFiles = replyFiles.reset;
3098
- useEffect2(() => resetReplyFiles(), [activeId, resetReplyFiles]);
3099
- const snapshot = useCallback2(async (target, add) => {
3100
- setCapturing(true);
3101
- const blob = await capturePage(target).catch(() => null);
3102
- setCapturing(false);
3103
- if (!blob) {
3104
- setError("Couldn\u2019t capture the page.");
3105
- return;
3106
- }
3107
- const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/g, "-");
3108
- add([{ blob, name: `screenshot-${stamp}.jpg` }]);
3109
- }, []);
3110
- const pinsHidden = Boolean(prefs.pinsHidden);
3111
- const barPosition = prefs.position ?? position;
3112
- const savePrefs = useCallback2((next) => {
3113
- setPrefs(next);
3114
- localStorage.setItem(prefsKey, JSON.stringify(next));
3115
- }, []);
3116
- const say = useCallback2((message) => setToast(message), []);
3117
- useEffect2(() => {
3118
- if (theme !== "system")
3119
- return;
3120
- const query = window.matchMedia("(prefers-color-scheme: dark)");
3121
- const sync = () => setSystemDark(query.matches);
3122
- sync();
3123
- query.addEventListener("change", sync);
3124
- return () => query.removeEventListener("change", sync);
3125
- }, [theme]);
3126
- useEffect2(() => {
3127
- if (!toast)
3128
- return;
3129
- const id = window.setTimeout(() => setToast(""), 1800);
3130
- return () => window.clearTimeout(id);
3131
- }, [toast]);
3132
- useEffect2(() => {
3133
- setMounted(true);
3134
- setPrefs(readPrefs());
3135
- if (!document.getElementById("cs-style")) {
3136
- const style = document.createElement("style");
3137
- style.id = "cs-style";
3138
- style.textContent = toolbarCss;
3139
- document.head.append(style);
3140
- }
3141
- }, []);
3142
- useEffect2(() => {
32
+ const remote = await import(
33
+ /* @vite-ignore */
34
+ /* webpackIgnore: true */
35
+ url
36
+ );
37
+ if (typeof remote.mount === "function")
38
+ return remote;
39
+ throw new Error(`${url} has no mount export`);
40
+ } catch (error) {
41
+ console.warn("[comment-service] Could not load the latest toolbar, using the copy bundled with the package.", error);
42
+ return bundled();
43
+ }
44
+ }
45
+ function CommentToolbar({ source = "latest", sessionPath = "/api/comment-service/session", pollMs, position, theme, navigate }) {
46
+ const handle = useRef(null);
47
+ const navigateRef = useRef(navigate);
48
+ navigateRef.current = navigate;
49
+ const hasNavigate = Boolean(navigate);
50
+ const props = useRef({});
51
+ props.current = {
52
+ sessionPath,
53
+ pollMs,
54
+ position,
55
+ theme,
56
+ navigate: hasNavigate ? (href) => navigateRef.current?.(href) : void 0
57
+ };
58
+ useEffect(() => {
3143
59
  let cancelled = false;
3144
- loadSession(sessionPath).then((next) => {
3145
- if (cancelled)
3146
- return;
3147
- if (!next) {
3148
- setPhase("off");
60
+ let mounted = null;
61
+ const container = document.createElement("div");
62
+ container.setAttribute("data-cs-runtime", source);
63
+ container.style.display = "contents";
64
+ document.body.append(container);
65
+ void loadRuntime(sessionPath, source).then((runtime) => {
66
+ if (cancelled || !runtime)
3149
67
  return;
3150
- }
3151
- setSession(next);
3152
- const stored = localStorage.getItem(storageKey(next));
3153
- if (stored && memberIsCurrent(stored)) {
3154
- setMember(stored);
3155
- setName(readJwt(stored)?.name ?? "");
3156
- setEmail(readJwt(stored)?.email ?? "");
3157
- }
3158
- setPhase("ready");
3159
- }).catch(() => {
3160
- if (!cancelled)
3161
- setPhase("off");
68
+ mounted = runtime.mount(container, props.current);
69
+ handle.current = mounted;
3162
70
  });
3163
71
  return () => {
3164
72
  cancelled = true;
3165
- };
3166
- }, [sessionPath]);
3167
- useEffect2(() => {
3168
- const sync = () => setRoute(window.location.pathname);
3169
- sync();
3170
- if (!Reflect.get(window, historyFlag)) {
3171
- const push = history.pushState.bind(history);
3172
- const replace = history.replaceState.bind(history);
3173
- const notify = () => window.dispatchEvent(new Event("cs-nav"));
3174
- history.pushState = ((...args) => {
3175
- push(...args);
3176
- notify();
3177
- });
3178
- history.replaceState = ((...args) => {
3179
- replace(...args);
3180
- notify();
3181
- });
3182
- Reflect.set(window, historyFlag, true);
3183
- }
3184
- window.addEventListener("popstate", sync);
3185
- window.addEventListener("cs-nav", sync);
3186
- return () => {
3187
- window.removeEventListener("popstate", sync);
3188
- window.removeEventListener("cs-nav", sync);
3189
- };
3190
- }, []);
3191
- const refresh = useCallback2(async () => {
3192
- if (!session || !member)
3193
- return;
3194
- const next = await listThreads(session, member);
3195
- setThreads(next.threads);
3196
- setSettings(next.settings);
3197
- }, [member, session]);
3198
- useEffect2(() => {
3199
- if (!session || !member)
3200
- return;
3201
- let stop = false;
3202
- const tick = () => {
3203
- listThreads(session, member).then((next) => {
3204
- if (stop)
3205
- return;
3206
- setThreads(next.threads);
3207
- setSettings(next.settings);
3208
- }).catch((caught) => {
3209
- if (caught instanceof ApiError && caught.status === 401) {
3210
- localStorage.removeItem(storageKey(session));
3211
- setMember(null);
3212
- }
73
+ handle.current = null;
74
+ queueMicrotask(() => {
75
+ mounted?.unmount();
76
+ container.remove();
3213
77
  });
3214
78
  };
3215
- tick();
3216
- const id = window.setInterval(tick, pollMs);
3217
- return () => {
3218
- stop = true;
3219
- window.clearInterval(id);
3220
- };
3221
- }, [member, pollMs, session]);
3222
- useEffect2(() => {
3223
- if (!session || !member) {
3224
- setMembers([]);
3225
- return;
3226
- }
3227
- let stop = false;
3228
- const load = () => listMembers(session, member).then((list) => {
3229
- if (!stop)
3230
- setMembers(list);
3231
- }).catch(() => void 0);
3232
- load();
3233
- const id = window.setInterval(load, 6e4);
3234
- return () => {
3235
- stop = true;
3236
- window.clearInterval(id);
3237
- };
3238
- }, [member, session]);
3239
- useEffect2(() => {
3240
- captureHandoff();
3241
- const handoff = readHandoff();
3242
- pendingThread.current = handoff?.thread ?? null;
3243
- if (handoff?.invite)
3244
- setInvite(handoff.invite);
3245
- stripHandoffFromUrl();
3246
- }, []);
3247
- const dropInvite = useCallback2(() => {
3248
- setInvite(null);
3249
- dropHandoff("invite");
3250
- }, []);
3251
- useEffect2(() => {
3252
- if (phase === "ready" && !member && (pendingThread.current || invite))
3253
- setSurface("unlock");
3254
- if (member && invite)
3255
- dropInvite();
3256
- }, [dropInvite, invite, member, phase]);
3257
- useEffect2(() => {
3258
- if (!mounted)
3259
- return;
3260
- const sync = () => setPortal(overlayHost());
3261
- sync();
3262
- const observer = new MutationObserver(sync);
3263
- observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["open"] });
3264
- return () => observer.disconnect();
3265
- }, [mounted]);
3266
- useEffect2(() => {
3267
- if (!mounted)
3268
- return;
3269
- let frame = 0;
3270
- const update = () => {
3271
- cancelAnimationFrame(frame);
3272
- frame = requestAnimationFrame(() => {
3273
- const host2 = portal ?? document.body;
3274
- const next = threads.map((thread) => {
3275
- if (thread.status === "resolved")
3276
- return { thread, status: "resolved" };
3277
- if (thread.route !== route)
3278
- return { thread, status: "unavailable" };
3279
- const { resolution, element } = resolveInDocument(thread.anchor, route);
3280
- if ((resolution.status === "pinned" || resolution.status === "drifted") && element) {
3281
- const box = placeAgainst(element, host2);
3282
- if (!box)
3283
- return { thread, status: "hidden" };
3284
- return { thread, status: resolution.status, top: box.top, left: box.left + box.width };
3285
- }
3286
- return { thread, status: resolution.status };
3287
- });
3288
- setPlaced(next);
3289
- });
3290
- };
3291
- update();
3292
- const observer = new MutationObserver((records) => {
3293
- const internal = records.every((record) => {
3294
- const node = record.target instanceof Element ? record.target : record.target.parentElement;
3295
- return Boolean(node?.closest("#cs-root"));
3296
- });
3297
- if (!internal)
3298
- update();
3299
- });
3300
- observer.observe(document.body, { childList: true, subtree: true, attributes: true });
3301
- window.addEventListener("scroll", update, true);
3302
- window.addEventListener("resize", update);
3303
- return () => {
3304
- cancelAnimationFrame(frame);
3305
- observer.disconnect();
3306
- window.removeEventListener("scroll", update, true);
3307
- window.removeEventListener("resize", update);
3308
- };
3309
- }, [mounted, portal, route, threads]);
3310
- useEffect2(() => {
3311
- if (!flash)
3312
- return;
3313
- const id = window.setTimeout(() => setFlash(null), 1400);
3314
- return () => window.clearTimeout(id);
3315
- }, [flash]);
3316
- const startComment = useCallback2(() => {
3317
- setDraft(null);
3318
- setReattachId(null);
3319
- setSurface("none");
3320
- setMode((current) => current === "select" ? "browse" : "select");
3321
- }, []);
3322
- const togglePins = useCallback2(() => {
3323
- const next = !pinsHidden;
3324
- savePrefs({ ...prefs, pinsHidden: next });
3325
- say(next ? "Pins hidden \xB7 H to show" : "Pins shown");
3326
- }, [pinsHidden, prefs, savePrefs, say]);
3327
- useEffect2(() => {
3328
- const onKey = (event) => {
3329
- if (event.defaultPrevented)
3330
- return;
3331
- if (event.key === "Escape") {
3332
- if (mode !== "browse" || draft || surface !== "none") {
3333
- setMode("browse");
3334
- setDraft(null);
3335
- setReattachId(null);
3336
- setSurface("none");
3337
- setConfirmReset(false);
3338
- setRenaming(null);
3339
- }
3340
- return;
3341
- }
3342
- if (!member || event.metaKey || event.ctrlKey || event.altKey || isTyping(event.target))
3343
- return;
3344
- const key2 = event.key.toLowerCase();
3345
- if (key2 === "c")
3346
- startComment();
3347
- else if (key2 === "i") {
3348
- setView(prefs.view ?? "list");
3349
- setSurface((current) => current === "inbox" ? "none" : "inbox");
3350
- } else if (key2 === "h")
3351
- togglePins();
3352
- else
3353
- return;
3354
- event.preventDefault();
3355
- };
3356
- document.addEventListener("keydown", onKey);
3357
- return () => document.removeEventListener("keydown", onKey);
3358
- }, [draft, member, mode, prefs.view, startComment, surface, togglePins]);
3359
- useEffect2(() => {
3360
- if (mode === "browse") {
3361
- setHighlight(null);
3362
- document.documentElement.style.cursor = "";
3363
- return;
3364
- }
3365
- document.documentElement.style.cursor = "crosshair";
3366
- const host2 = () => portal ?? document.body;
3367
- const onMove = (event) => {
3368
- if (event.target instanceof Element && event.target.closest("#cs-root")) {
3369
- setHighlight(null);
3370
- return;
3371
- }
3372
- const element = elementAt(event.clientX, event.clientY);
3373
- const box = element ? placeAgainst(element, host2()) : null;
3374
- setHighlight(element && box ? { ...box, label: describeElement(element) } : null);
3375
- };
3376
- const onClick = (event) => {
3377
- if (event.target instanceof Element && event.target.closest("#cs-root button, #cs-root input, #cs-root textarea, #cs-root form")) {
3378
- return;
3379
- }
3380
- const element = elementAt(event.clientX, event.clientY);
3381
- if (!element || !session)
3382
- return;
3383
- event.preventDefault();
3384
- event.stopPropagation();
3385
- const anchor = captureAnchor(element, window.location.pathname);
3386
- const target = host2();
3387
- const box = placeAgainst(element, target);
3388
- if (modeRef.current === "reattach" && reattachId && member) {
3389
- setBusy(true);
3390
- updateThread(session, member, reattachId, { anchor, url: pageHref() }).then(() => refresh()).then(() => {
3391
- setMode("browse");
3392
- setReattachId(null);
3393
- setSurface("inbox");
3394
- setActiveId(reattachId);
3395
- setError("");
3396
- say("Reattached");
3397
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Could not reattach.")).finally(() => setBusy(false));
3398
- return;
3399
- }
3400
- const width = target === document.body ? window.innerWidth : target.clientWidth;
3401
- const composerWidth = Math.min(320, width - 16);
3402
- let left = box ? box.left + box.width + 12 : event.clientX;
3403
- if (left + composerWidth > width - 8)
3404
- left = box ? box.left - composerWidth - 12 : width - composerWidth - 8;
3405
- left = Math.max(8, Math.min(left, width - composerWidth - 8));
3406
- setDraft({ anchor, top: Math.max(8, box ? box.top - 8 : event.clientY), left, element });
3407
- draftPicked.current = /* @__PURE__ */ new Map();
3408
- draftShot.current = null;
3409
- draftFiles.reset();
3410
- setBody("");
3411
- setDraftDetails(noDetails);
3412
- setMode("browse");
3413
- setError("");
3414
- };
3415
- document.addEventListener("mousemove", onMove, true);
3416
- document.addEventListener("click", onClick, true);
3417
- return () => {
3418
- document.documentElement.style.cursor = "";
3419
- document.removeEventListener("mousemove", onMove, true);
3420
- document.removeEventListener("click", onClick, true);
3421
- };
3422
- }, [member, mode, portal, reattachId, refresh, say, session]);
3423
- useEffect2(() => {
3424
- const api = {
3425
- version: 1,
3426
- project: session?.project ?? null,
3427
- environment: session?.environment ?? null,
3428
- list: () => threadsRef.current,
3429
- capture: (selector) => {
3430
- const element = document.querySelector(selector);
3431
- return element ? captureAnchor(element, window.location.pathname) : null;
3432
- },
3433
- resolve: (anchor) => resolveInDocument(anchor, window.location.pathname).resolution
3434
- };
3435
- Object.assign(window, { __COMMENT_SERVICE__: api });
3436
- }, [session]);
3437
- const open = placed.filter((item) => item.thread.status === "open");
3438
- const counts = {
3439
- page: open.filter((item) => item.thread.route === route).length,
3440
- all: open.length,
3441
- resolved: placed.length - open.length
3442
- };
3443
- const listed = useMemo3(() => {
3444
- if (tab === "resolved")
3445
- return placed.filter((item) => item.thread.status === "resolved");
3446
- const openOnly = placed.filter((item) => item.thread.status === "open");
3447
- return tab === "page" ? openOnly.filter((item) => item.thread.route === route) : openOnly;
3448
- }, [placed, route, tab]);
3449
- const groups = useMemo3(() => {
3450
- if (tab !== "all")
3451
- return [{ route: null, items: listed }];
3452
- const byRoute = /* @__PURE__ */ new Map();
3453
- for (const item of listed)
3454
- byRoute.set(item.thread.route, [...byRoute.get(item.thread.route) ?? [], item]);
3455
- return [...byRoute.entries()].sort(([a], [b]) => a === route ? -1 : b === route ? 1 : a.localeCompare(b)).map(([key2, items]) => ({ route: key2, items }));
3456
- }, [listed, route, tab]);
3457
- const showOnPage = useCallback2((thread) => {
3458
- const { element } = resolveInDocument(thread.anchor, window.location.pathname);
3459
- if (!element)
3460
- return;
3461
- element.scrollIntoView({ block: "center", behavior: "smooth" });
3462
- window.setTimeout(() => {
3463
- const box = placeAgainst(element, portal ?? document.body);
3464
- if (box)
3465
- setFlash(box);
3466
- }, 350);
3467
- }, [portal]);
3468
- useEffect2(() => {
3469
- const id = pendingThread.current;
3470
- if (!id || !member)
3471
- return;
3472
- const item = placed.find((candidate) => candidate.thread.id === id);
3473
- if (!item)
3474
- return;
3475
- pendingThread.current = null;
3476
- dropHandoff("thread");
3477
- setTab(item.thread.status === "resolved" ? "resolved" : item.thread.route === route ? "page" : "all");
3478
- setActiveId(id);
3479
- setSurface("inbox");
3480
- if (item.status === "pinned" || item.status === "drifted")
3481
- showOnPage(item.thread);
3482
- else
3483
- arrival.current = { id, until: Date.now() + 6e3 };
3484
- }, [member, placed, route, showOnPage]);
3485
- useEffect2(() => {
3486
- const pending = arrival.current;
3487
- if (!pending)
3488
- return;
3489
- if (Date.now() > pending.until) {
3490
- arrival.current = null;
3491
- return;
3492
- }
3493
- const item = placed.find((candidate) => candidate.thread.id === pending.id);
3494
- if (!item || item.status !== "pinned" && item.status !== "drifted")
3495
- return;
3496
- arrival.current = null;
3497
- showOnPage(item.thread);
3498
- }, [placed, showOnPage]);
3499
- const select = useCallback2((item, keepOpen = false) => {
3500
- const { thread } = item;
3501
- if (!keepOpen && activeId === thread.id) {
3502
- setActiveId(null);
3503
- return;
3504
- }
3505
- setActiveId(thread.id);
3506
- setEditingId(null);
3507
- const onPage = item.status === "pinned" || item.status === "drifted";
3508
- const target = thread.url || thread.route;
3509
- const legacyHere = thread.url === thread.route && thread.route === route;
3510
- if (target === pageHref() || onPage && legacyHere)
3511
- return;
3512
- if (navigate) {
3513
- arrival.current = { id: thread.id, until: Date.now() + 6e3 };
3514
- navigate(target);
3515
- return;
3516
- }
3517
- window.location.assign(linkWith(target, "cs-thread", thread.id));
3518
- }, [activeId, navigate, route]);
3519
- if (!mounted || phase !== "ready" || !session)
3520
- return null;
3521
- const memberName = member ? readJwt(member)?.name ?? name : "";
3522
- const host = portal ?? document.body;
3523
- const inside = host !== document.body;
3524
- const peek = peekId ? placed.find((item) => item.thread.id === peekId && item.top != null) : void 0;
3525
- const canPost = Boolean(body.trim() || draftFiles.ids.length || settings?.fields.enabled && hasFieldValues(draftDetails));
3526
- const changeSettings = (next) => {
3527
- if (!member)
3528
- return;
3529
- const previous = settings;
3530
- setSettings(next);
3531
- setError("");
3532
- saveSettings(session, member, next).then(setSettings).catch((caught) => {
3533
- setSettings(previous);
3534
- setError(caught instanceof Error ? caught.message : "Couldn\u2019t save.");
3535
- });
3536
- };
3537
- const lock = () => {
3538
- localStorage.removeItem(storageKey(session));
3539
- setMember(null);
3540
- setThreads([]);
3541
- setSurface("none");
3542
- setMode("browse");
3543
- setDraft(null);
3544
- };
3545
- const reset = () => {
3546
- localStorage.removeItem(storageKey(session));
3547
- localStorage.removeItem(prefsKey);
3548
- setPrefs({});
3549
- setMember(null);
3550
- setThreads([]);
3551
- setName("");
3552
- setEmail("");
3553
- setPassphrase("");
3554
- setProfile(null);
3555
- setDeliveries({});
3556
- setInviteLink(null);
3557
- setConfirmReset(false);
3558
- setMode("browse");
3559
- setDraft(null);
3560
- setSurface("unlock");
3561
- };
3562
- const bar = _jsx6("div", { className: "cs-bar cs-hit", "data-cs-ui": "bar", role: "toolbar", "aria-label": "Comments toolbar", children: !member ? _jsxs6("button", { className: "cs-bar-lock", "data-cs-ui": "launcher", type: "button", "aria-label": "Comments", onClick: () => {
3563
- setError("");
3564
- setSurface(surface === "unlock" ? "none" : "unlock");
3565
- }, children: [_jsx6(Lock, {}), _jsx6("span", { children: "Comments" })] }) : _jsxs6(_Fragment3, { children: [_jsx6("button", { className: mode === "select" ? "cs-tool is-on is-accent" : "cs-tool", "data-cs-ui": "comment-mode", "data-tip": "Comment C", type: "button", "aria-label": "Add comment", "aria-pressed": mode === "select", onClick: startComment, children: _jsx6(AddComment, {}) }), _jsxs6("button", { className: surface === "inbox" ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "launcher", "data-tip": "Comments I", type: "button", "aria-label": "Comments", "aria-expanded": surface === "inbox", onClick: () => {
3566
- setError("");
3567
- setMode("browse");
3568
- setView(prefs.view ?? "list");
3569
- setSurface(surface === "inbox" ? "none" : "inbox");
3570
- }, children: [_jsx6(Inbox, {}), counts.page > 0 ? _jsx6("span", { className: "cs-badge", children: counts.page }) : null] }), _jsx6("button", { className: pinsHidden ? "cs-tool is-on" : "cs-tool", "data-cs-ui": "pins-toggle", "data-tip": pinsHidden ? "Show pins H" : "Hide pins H", type: "button", "aria-label": pinsHidden ? "Show pins" : "Hide pins", "aria-pressed": pinsHidden, onClick: togglePins, children: pinsHidden ? _jsx6(EyeOff, {}) : _jsx6(Eye, {}) }), _jsx6("span", { className: "cs-divider" }), _jsx6("button", { className: surface === "menu" ? "cs-tool cs-me is-on" : "cs-tool cs-me", "data-cs-ui": "menu-button", "data-tip": memberName, type: "button", "aria-label": "You", "aria-expanded": surface === "menu", onClick: () => {
3571
- setConfirmReset(false);
3572
- setRenaming(null);
3573
- if (surface !== "menu" && member) {
3574
- getProfile(session, member).then(setProfile).catch(() => void 0);
3575
- }
3576
- setSurface(surface === "menu" ? "none" : "menu");
3577
- }, children: _jsx6(Avatar, { name: memberName, size: 24 }) })] }) });
3578
- const unlockCard = _jsxs6("section", { className: "cs-card cs-unlock cs-hit", "data-cs-ui": "panel", "aria-label": "Unlock comments", children: [_jsx6("div", { className: "cs-unlock-icon", children: _jsx6(Lock, { size: 18 }) }), _jsx6("h2", { children: "Comments" }), _jsx6("p", { className: "cs-sub", children: invite ? `${readJwt(invite)?.name ?? "A teammate"} invited you to comment. Add your name and email to join.` : "Enter the team passphrase to see and add comments." }), _jsxs6("form", { onSubmit: (event) => {
3579
- event.preventDefault();
3580
- setBusy(true);
3581
- setError("");
3582
- loadSession(sessionPath).then((fresh) => {
3583
- if (!fresh)
3584
- throw new Error("Comments aren\u2019t enabled here.");
3585
- setSession(fresh);
3586
- return unlock(fresh, name, email, invite ? { invite } : { passphrase }).then((token) => ({ fresh, token }));
3587
- }).then(({ fresh, token }) => {
3588
- localStorage.setItem(storageKey(fresh), token);
3589
- setMember(token);
3590
- setPassphrase("");
3591
- dropInvite();
3592
- setTab("page");
3593
- setSurface("inbox");
3594
- }).catch((caught) => {
3595
- if (caught instanceof ApiError && caught.code === "invalid_invite")
3596
- dropInvite();
3597
- setError(caught instanceof Error ? caught.message : "Couldn\u2019t unlock.");
3598
- }).finally(() => setBusy(false));
3599
- }, children: [_jsxs6("label", { className: "cs-field", children: [_jsx6("span", { children: "Your name" }), _jsx6("input", { "data-cs-ui": "unlock-name", value: name, onChange: (event) => setName(event.target.value), autoComplete: "name", required: true })] }), invite ? null : _jsxs6("label", { className: "cs-field", children: [_jsx6("span", { children: "Passphrase" }), _jsx6("input", { "data-cs-ui": "unlock-passphrase", type: "password", value: passphrase, onChange: (event) => setPassphrase(event.target.value), autoComplete: "current-password", required: true })] }), _jsxs6("label", { className: "cs-field", children: [_jsx6("span", { children: "Email" }), _jsx6("input", { "data-cs-ui": "unlock-email", type: "email", value: email, onChange: (event) => setEmail(event.target.value), autoComplete: "email", required: true }), _jsx6("small", { children: "So teammates can @mention you." })] }), error ? _jsx6("p", { className: "cs-error", "data-cs-ui": "error", children: error }) : null, _jsx6("button", { className: "cs-button is-primary is-wide", "data-cs-ui": "unlock-submit", type: "submit", disabled: busy, children: busy ? "Unlocking\u2026" : invite ? "Join" : "Unlock" })] })] });
3600
- const menuCard = _jsx6("section", { className: "cs-card cs-menu cs-hit", "data-cs-ui": "menu", "aria-label": "Your comment settings", children: confirmReset ? _jsxs6("div", { className: "cs-confirm", "data-cs-ui": "reset-confirm", children: [_jsx6("h2", { children: "Reset on this device?" }), _jsx6("p", { className: "cs-sub", children: "You\u2019ll be signed out and your name and preferences are cleared. Comments aren\u2019t affected." }), _jsxs6("div", { className: "cs-row-end", children: [_jsx6("button", { className: "cs-button", type: "button", onClick: () => setConfirmReset(false), children: "Cancel" }), _jsx6("button", { className: "cs-button is-danger", "data-cs-ui": "reset-submit", type: "button", onClick: reset, children: "Reset" })] })] }) : _jsxs6(_Fragment3, { children: [_jsxs6("div", { className: "cs-me-row", children: [_jsx6(Avatar, { name: renaming ?? memberName, size: 36 }), renaming != null ? _jsxs6("form", { className: "cs-rename", onSubmit: (event) => {
3601
- event.preventDefault();
3602
- const next = renaming.trim();
3603
- if (!next || next === memberName || !member) {
3604
- setRenaming(null);
3605
- return;
3606
- }
3607
- setBusy(true);
3608
- updateProfile(session, member, { displayName: next }).then((updated) => {
3609
- localStorage.setItem(storageKey(session), updated.token);
3610
- setMember(updated.token);
3611
- setProfile(updated);
3612
- setName(next);
3613
- setRenaming(null);
3614
- say("Name updated");
3615
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t rename.")).finally(() => setBusy(false));
3616
- }, children: [_jsx6("input", { "data-cs-ui": "rename-input", "aria-label": "Your name", value: renaming, maxLength: 40, autoFocus: true, onChange: (event) => setRenaming(event.target.value), onKeyDown: (event) => {
3617
- if (event.key === "Escape") {
3618
- event.preventDefault();
3619
- setRenaming(null);
3620
- }
3621
- } }), _jsx6("button", { className: "cs-icon-button", type: "submit", "aria-label": "Save name", disabled: busy, children: _jsx6(Check, {}) })] }) : _jsxs6("div", { className: "cs-me-name", children: [_jsxs6("span", { className: "cs-me-lines", children: [_jsx6("strong", { children: memberName }), _jsx6("small", { children: profile?.email ?? readJwt(member ?? "")?.email ?? "" })] }), _jsx6("button", { className: "cs-icon-button", "data-cs-ui": "rename", type: "button", "aria-label": "Change name", onClick: () => {
3622
- setError("");
3623
- setRenaming(memberName);
3624
- }, children: _jsx6(Pencil, {}) })] })] }), error ? _jsx6("p", { className: "cs-error", children: error }) : null, _jsx6("div", { className: "cs-sep" }), _jsxs6("button", { className: "cs-item", type: "button", role: "switch", "aria-checked": !pinsHidden, onClick: togglePins, children: [_jsx6("span", { children: "Show pins" }), _jsx6("span", { className: pinsHidden ? "cs-switch" : "cs-switch is-on" })] }), _jsxs6("button", { className: "cs-item", "data-cs-ui": "mention-emails", type: "button", role: "switch", "aria-checked": profile?.mentionEmails ?? true, disabled: !profile, onClick: () => {
3625
- if (!member || !profile)
3626
- return;
3627
- updateProfile(session, member, { mentionEmails: !profile.mentionEmails }).then((updated) => {
3628
- localStorage.setItem(storageKey(session), updated.token);
3629
- setMember(updated.token);
3630
- setProfile(updated);
3631
- say(updated.mentionEmails ? "Mention emails on" : "Mention emails off");
3632
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t update."));
3633
- }, children: [_jsx6("span", { children: "Email me when mentioned" }), _jsx6("span", { className: profile?.mentionEmails ?? true ? "cs-switch is-on" : "cs-switch" })] }), _jsxs6("div", { className: "cs-item is-static", children: [_jsx6("span", { children: "Position" }), _jsx6("span", { className: "cs-segment", role: "radiogroup", "aria-label": "Toolbar position", children: positions.map((option) => _jsx6("button", { type: "button", role: "radio", "aria-checked": barPosition === option.value, className: barPosition === option.value ? "is-on" : "", onClick: () => savePrefs({ ...prefs, position: option.value }), children: option.label }, option.value)) })] }), _jsxs6("button", { className: "cs-item", "data-cs-ui": "settings-open", type: "button", onClick: () => {
3634
- setError("");
3635
- setSurface("settings");
3636
- }, children: [_jsx6("span", { children: "Comment fields" }), _jsx6("span", { className: "cs-hint", children: detailsEnabled(settings) ? "On" : "Off" })] }), _jsx6("div", { className: "cs-sep" }), _jsx6("button", { className: "cs-item", "data-cs-ui": "invite", type: "button", disabled: busy, onClick: () => {
3637
- if (!member)
3638
- return;
3639
- setBusy(true);
3640
- createInvite(session, member).then(({ invite: code }) => {
3641
- const link = linkWith(pageHref(), "cs-invite", code);
3642
- setInviteLink(link);
3643
- return navigator.clipboard.writeText(link).then(() => say("Invite link copied")).catch(() => say("Invite link ready"));
3644
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t create an invite.")).finally(() => setBusy(false));
3645
- }, children: _jsx6("span", { children: "Invite someone" }) }), inviteLink ? _jsxs6("div", { className: "cs-invite", children: [_jsx6("input", { "data-cs-ui": "invite-link", "aria-label": "Invite link", readOnly: true, value: inviteLink, onFocus: (event) => event.currentTarget.select() }), _jsx6("p", { className: "cs-hint", children: "Anyone with this link can join for 7 days. Changing the passphrase cancels it." })] }) : null, _jsx6("button", { className: "cs-item", "data-cs-ui": "lock", type: "button", onClick: lock, children: _jsx6("span", { children: "Lock" }) }), _jsx6("button", { className: "cs-item is-danger", "data-cs-ui": "reset", type: "button", onClick: () => setConfirmReset(true), children: _jsx6("span", { children: "Reset on this device\u2026" }) }), _jsx6("p", { className: "cs-hint", children: "C comment \xB7 I list \xB7 H pins" })] }) });
3646
- const tabsBar = _jsx6("div", { className: "cs-tabs", role: "tablist", children: [
3647
- ["page", "This page", counts.page, "scope-page"],
3648
- ["all", "All pages", counts.all, "scope-all"],
3649
- ["resolved", "Resolved", counts.resolved, "scope-resolved"]
3650
- ].map(([value, label, count, hook]) => _jsxs6("button", { type: "button", role: "tab", "aria-selected": tab === value, "data-cs-ui": hook, className: tab === value ? "is-on" : "", onClick: () => setTab(value), children: [label, count > 0 ? _jsx6("span", { className: "cs-count", children: count }) : null] }, value)) });
3651
- const setViewPref = (next) => {
3652
- setView(next);
3653
- savePrefs({ ...prefs, view: next });
3654
- };
3655
- const table = _jsx6(CommentTable, { rows: listed, route, activeId, tabs: tabsBar, onList: () => setViewPref("list"), onClose: () => setSurface("none"), onSelect: (row) => {
3656
- setView("list");
3657
- const item = placed.find((candidate) => candidate.thread.id === row.thread.id);
3658
- if (item)
3659
- select(item, true);
3660
- } });
3661
- const inbox = _jsxs6("section", { className: "cs-card cs-sheet cs-hit", "data-cs-ui": "panel", "aria-label": "Comments", children: [_jsxs6("header", { className: "cs-sheet-head", children: [_jsx6("h2", { children: "Comments" }), _jsx6("button", { className: "cs-icon-button cs-push", "data-cs-ui": "table-view", type: "button", "aria-label": "Table view", onClick: () => setViewPref("table"), children: _jsx6(Table, {}) }), _jsx6("button", { className: "cs-icon-button", type: "button", "aria-label": "Close comments", onClick: () => setSurface("none"), children: _jsx6(Close, {}) })] }), tabsBar, error ? _jsx6("p", { className: "cs-error cs-pad", "data-cs-ui": "error", children: error }) : null, _jsxs6("div", { className: "cs-list", children: [listed.length === 0 ? _jsxs6("div", { className: "cs-empty", children: [_jsx6("p", { children: tab === "resolved" ? "Nothing resolved yet." : tab === "page" ? "No comments on this page." : "No open comments." }), tab !== "resolved" ? _jsxs6("button", { className: "cs-button", type: "button", onClick: startComment, children: [_jsx6(AddComment, {}), " Add a comment"] }) : null] }) : null, groups.map((group) => _jsxs6("div", { children: [group.route ? _jsx6("div", { className: "cs-group", children: group.route === route ? "This page" : group.route }) : null, group.items.map((item) => {
3662
- const { thread } = item;
3663
- const expanded = activeId === thread.id;
3664
- const first = thread.messages[0];
3665
- const note = statusNote(item.status, thread.anchor, thread.route === route);
3666
- const onPage = item.status === "pinned" || item.status === "drifted";
3667
- return _jsxs6("article", { className: expanded ? "cs-thread is-open" : "cs-thread", "data-cs-ui": "thread", "data-thread-id": thread.id, "data-cs-status": item.status, children: [_jsxs6("div", { className: "cs-thread-main", role: "button", tabIndex: 0, "aria-expanded": expanded, "aria-label": `${thread.createdBy}: ${first?.body ?? ""}`, onClick: () => select(item), onKeyDown: (event) => {
3668
- if (event.key === "Enter" || event.key === " ") {
3669
- event.preventDefault();
3670
- select(item);
3671
- }
3672
- }, onMouseEnter: () => setPeekId(null), children: [_jsx6(Avatar, { name: thread.createdBy }), _jsxs6("div", { className: "cs-thread-body", children: [_jsxs6("div", { className: "cs-meta", children: [_jsx6("strong", { children: thread.createdBy }), _jsx6("time", { dateTime: thread.createdAt, children: ago(thread.createdAt) }), _jsx6(RoleBadge, { role: thread.role })] }), first?.body ? _jsx6("p", { className: "cs-text", children: _jsx6(MessageText, { body: first.body, mentions: first.mentions }) }) : null, _jsx6(FieldValues, { fields: thread.fields, clamp: !expanded }), _jsx6(MessageAttachments, { attachments: first?.attachments }), first && deliveryText(deliveries[first.id]) ? _jsx6("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[first.id]) }) : null, _jsxs6("div", { className: "cs-target", children: [_jsx6("span", { title: thread.anchor.component?.file ?? void 0, children: targetName(thread.anchor) }), note ? _jsx6("span", { className: `cs-note is-${note.tone}`, children: note.text }) : null] }), !expanded && thread.messages.length > 1 ? _jsxs6("div", { className: "cs-replies", children: [thread.messages.length - 1, " ", thread.messages.length === 2 ? "reply" : "replies"] }) : null] })] }), expanded ? _jsxs6("div", { className: "cs-thread-more", children: [editingId === thread.id && settings ? _jsx6(DetailsEditor, { settings, thread, onClose: () => setEditingId(null), onSave: (patch) => updateThread(session, member, thread.id, patch).then(() => refresh()).then(() => say("Details saved")).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t save.")) }) : null, thread.messages.slice(1).map((message) => _jsxs6("div", { className: "cs-message", children: [_jsx6(Avatar, { name: message.author, size: 20 }), _jsxs6("div", { children: [_jsxs6("div", { className: "cs-meta", children: [_jsx6("strong", { children: message.author }), _jsx6("time", { dateTime: message.createdAt, children: ago(message.createdAt) })] }), _jsx6("p", { className: "cs-text", children: _jsx6(MessageText, { body: message.body, mentions: message.mentions }) }), _jsx6(MessageAttachments, { attachments: message.attachments }), deliveryText(deliveries[message.id]) ? _jsx6("div", { className: "cs-delivery", "data-cs-ui": "delivery", children: deliveryText(deliveries[message.id]) }) : null] })] }, message.id)), _jsxs6("form", { className: "cs-reply", onSubmit: async (event) => {
3673
- event.preventDefault();
3674
- if (!member || !reply.trim() && !replyFiles.ids.length || replyFiles.uploading)
3675
- return;
3676
- setBusy(true);
3677
- const picked = mentionsIn(reply, replyPicked.current);
3678
- const element = thread.route === route ? resolveInDocument(thread.anchor, route).element : null;
3679
- const screenshot = picked.length && element ? await (replyShot.current ?? captureAround(element)).catch(() => null) : null;
3680
- replyToThread(session, member, thread.id, reply, {
3681
- mentions: picked.map((person) => person.id),
3682
- screenshot,
3683
- attachments: replyFiles.ids
3684
- }).then(async (result) => {
3685
- setReply("");
3686
- replyFiles.reset();
3687
- setError("");
3688
- replyPicked.current = /* @__PURE__ */ new Map();
3689
- replyShot.current = null;
3690
- if (result.delivery) {
3691
- setDeliveries((current) => ({ ...current, [result.message.id]: result.delivery }));
3692
- const note2 = deliveryText(result.delivery);
3693
- if (note2)
3694
- say(note2);
3695
- }
3696
- await refresh();
3697
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t reply.")).finally(() => setBusy(false));
3698
- }, children: [_jsx6(MentionTextarea, { "data-cs-ui": "reply-body", "aria-label": "Reply", placeholder: "Reply\u2026 (@ to mention)", rows: 1, value: reply, onValueChange: setReply, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3699
- replyPicked.current.set(person.id, person);
3700
- const element = thread.route === route ? resolveInDocument(thread.anchor, route).element : null;
3701
- if (element && !replyShot.current)
3702
- replyShot.current = captureAround(element);
3703
- }, onKeyDown: submitOnCmdEnter, onPaste: (event) => {
3704
- const files = filesFromClipboard(event);
3705
- if (files.length) {
3706
- event.preventDefault();
3707
- replyFiles.add(files);
3708
- }
3709
- } }), _jsx6(AttachButtons, { onFiles: replyFiles.add, capturing, onSnapshot: () => snapshot(thread.route === route ? resolveInDocument(thread.anchor, route).element : null, replyFiles.add) }), _jsx6("button", { className: "cs-send", "data-cs-ui": "reply-submit", type: "submit", "aria-label": "Post reply", disabled: busy || replyFiles.uploading || !reply.trim() && !replyFiles.ids.length, children: _jsx6(Send, {}) })] }), _jsx6(AttachmentTray, { items: replyFiles.items, onRemove: replyFiles.remove }), _jsxs6("div", { className: "cs-actions", children: [_jsxs6("button", { className: "cs-button", "data-cs-ui": "resolve", type: "button", onClick: () => {
3710
- if (!member)
3711
- return;
3712
- const next = thread.status === "resolved" ? "open" : "resolved";
3713
- updateThread(session, member, thread.id, { status: next }).then(() => refresh()).then(() => say(next === "resolved" ? "Resolved" : "Reopened")).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t update."));
3714
- }, children: [thread.status === "resolved" ? _jsx6(Undo, {}) : _jsx6(Check, {}), thread.status === "resolved" ? "Reopen" : "Resolve"] }), onPage && thread.status === "open" ? _jsxs6("button", { className: "cs-button", type: "button", onClick: () => showOnPage(thread), children: [_jsx6(Target, {}), " Show"] }) : null, detailsEnabled(settings) && editingId !== thread.id ? _jsxs6("button", { className: "cs-button", "data-cs-ui": "details-edit", type: "button", onClick: () => setEditingId(thread.id), children: [_jsx6(Pencil, {}), " Details"] }) : null, !onPage && thread.status === "open" && thread.route === route ? _jsxs6("button", { className: "cs-button", "data-cs-ui": "reattach", type: "button", onClick: () => {
3715
- setReattachId(thread.id);
3716
- setMode("reattach");
3717
- setSurface("none");
3718
- }, children: [_jsx6(Relink, {}), " Reattach"] }) : null] })] }) : null] }, thread.id);
3719
- })] }, group.route ?? "all"))] })] });
3720
- const ui = _jsxs6("div", { id: "cs-root", className: [
3721
- inside ? "cs-inside" : "cs-fixed",
3722
- `cs-at-${barPosition.replace("bottom-", "")}`,
3723
- theme === "dark" || theme === "system" && systemDark ? "cs-dark" : ""
3724
- ].join(" "), children: [highlight ? _jsxs6(_Fragment3, { children: [_jsx6("div", { className: "cs-outline", style: { top: highlight.top, left: highlight.left, width: highlight.width, height: highlight.height } }), _jsx6("div", { className: "cs-outline-label", style: { top: Math.max(4, highlight.top - 26), left: highlight.left }, children: highlight.label })] }) : null, flash ? _jsx6("div", { className: "cs-outline is-flash", style: { top: flash.top, left: flash.left, width: flash.width, height: flash.height } }) : null, mode !== "browse" ? _jsxs6("div", { className: "cs-banner cs-hit", "data-cs-ui": "mode", children: [_jsx6(Cursor, {}), _jsx6("span", { children: mode === "reattach" ? "Click the element this comment belongs to" : "Click anything to comment" }), _jsx6("button", { type: "button", onClick: () => {
3725
- setMode("browse");
3726
- setReattachId(null);
3727
- }, children: "Cancel" })] }) : null, pinsHidden ? null : placed.map((item) => item.top == null || item.left == null || item.status !== "pinned" && item.status !== "drifted" ? null : _jsxs6("button", { type: "button", className: [
3728
- "cs-pin",
3729
- item.status === "drifted" ? "is-drifted" : "",
3730
- activeId === item.thread.id && surface === "inbox" ? "is-active" : ""
3731
- ].join(" "), "data-cs-ui": "pin", "data-thread-id": item.thread.id, "data-cs-status": item.status, "aria-label": `Comment by ${item.thread.createdBy}`, style: { top: item.top >= 0 && item.top < 16 ? 16 : item.top, left: item.left }, onMouseEnter: () => setPeekId(item.thread.id), onMouseLeave: () => setPeekId(null), onClick: () => {
3732
- setPeekId(null);
3733
- setTab("page");
3734
- setActiveId(item.thread.id);
3735
- setSurface("inbox");
3736
- }, children: [_jsx6(Avatar, { name: item.thread.createdBy, size: 22 }), item.thread.messages.length > 1 ? _jsx6("span", { className: "cs-pin-count", children: item.thread.messages.length }) : null] }, item.thread.id)), peek && !pinsHidden && peek.top != null && peek.left != null ? _jsxs6("div", { className: "cs-peek", style: { top: peek.top + 18, left: Math.max(8, peek.left - 12) }, children: [_jsxs6("div", { className: "cs-meta", children: [_jsx6(Avatar, { name: peek.thread.createdBy, size: 18 }), _jsx6("strong", { children: peek.thread.createdBy }), _jsx6("time", { children: ago(peek.thread.createdAt) })] }), _jsx6("p", { className: "cs-text", children: _jsx6(MessageText, { body: peek.thread.messages[0]?.body ?? "", mentions: peek.thread.messages[0]?.mentions }) }), peek.thread.messages.length > 1 ? _jsxs6("div", { className: "cs-replies", children: [peek.thread.messages.length - 1, " ", peek.thread.messages.length === 2 ? "reply" : "replies"] }) : null] }) : null, draft ? _jsxs6("form", { className: "cs-card cs-composer cs-hit", "data-cs-ui": "composer", style: { top: draft.top, left: draft.left }, onSubmit: async (event) => {
3737
- event.preventDefault();
3738
- if (!member || !canPost || draftFiles.uploading)
3739
- return;
3740
- setBusy(true);
3741
- const picked = mentionsIn(body, draftPicked.current);
3742
- const screenshot = picked.length ? await (draftShot.current ?? captureAround(draft.element)).catch(() => null) : null;
3743
- createThread(session, member, draft.anchor, body, {
3744
- mentions: picked.map((person) => person.id),
3745
- screenshot,
3746
- attachments: draftFiles.ids,
3747
- url: pageHref(),
3748
- ...detailsPayload(settings, draftDetails)
3749
- }).then(async ({ thread, delivery }) => {
3750
- setDraft(null);
3751
- setBody("");
3752
- setDraftDetails(noDetails);
3753
- draftFiles.reset();
3754
- setError("");
3755
- draftPicked.current = /* @__PURE__ */ new Map();
3756
- draftShot.current = null;
3757
- const firstId = thread.messages[0]?.id;
3758
- if (delivery && firstId) {
3759
- setDeliveries((current) => ({ ...current, [firstId]: delivery }));
3760
- const note = deliveryText(delivery);
3761
- if (note)
3762
- say(note);
3763
- }
3764
- setActiveId(thread.id);
3765
- if (tab === "resolved")
3766
- setTab("page");
3767
- setSurface("inbox");
3768
- await refresh();
3769
- }).catch((caught) => setError(caught instanceof Error ? caught.message : "Couldn\u2019t post.")).finally(() => setBusy(false));
3770
- }, children: [_jsxs6("div", { className: "cs-composer-target", children: [_jsx6(Target, {}), _jsx6("span", { className: "cs-composer-name", children: targetName(draft.anchor) }), draft.anchor.component?.file ? _jsx6("code", { children: draft.anchor.component.file }) : null, _jsx6("button", { className: "cs-icon-button", type: "button", "aria-label": "Discard", onClick: () => setDraft(null), children: _jsx6(Close, {}) })] }), _jsxs6("div", { className: "cs-composer-body", children: [_jsx6(Avatar, { name: memberName, size: 24 }), _jsx6(MentionTextarea, { "data-cs-ui": "composer-body", "aria-label": "Comment", placeholder: "Add a comment\u2026 (@ to mention)", value: body, autoFocus: true, onValueChange: setBody, members, selfEmail: readJwt(member ?? "")?.email, onMention: (person) => {
3771
- draftPicked.current.set(person.id, person);
3772
- if (!draftShot.current)
3773
- draftShot.current = captureAround(draft.element);
3774
- }, onKeyDown: submitOnCmdEnter, onPaste: (event) => {
3775
- const files = filesFromClipboard(event);
3776
- if (files.length) {
3777
- event.preventDefault();
3778
- draftFiles.add(files);
3779
- }
3780
- } })] }), _jsx6(DetailsInput, { settings, value: draftDetails, onChange: setDraftDetails, onKeyDown: submitOnCmdEnter }), _jsx6(AttachmentTray, { items: draftFiles.items, onRemove: draftFiles.remove }), error ? _jsx6("p", { className: "cs-error cs-pad", children: error }) : null, _jsxs6("div", { className: "cs-composer-foot", children: [_jsx6(AttachButtons, { onFiles: draftFiles.add, capturing, onSnapshot: () => snapshot(draft.element, draftFiles.add) }), _jsx6("span", { className: "cs-hint", children: capturing ? "Capturing\u2026" : draftFiles.uploading ? "Uploading\u2026" : busy ? "Posting\u2026" : "\u2318\u21B5 to post" }), _jsx6("button", { className: "cs-send", "data-cs-ui": "composer-submit", type: "submit", "aria-label": "Post comment", disabled: busy || draftFiles.uploading || !canPost, children: _jsx6(Send, {}) })] })] }) : null, surface === "unlock" && !member ? unlockCard : null, surface === "menu" && member ? menuCard : null, surface === "settings" && member ? _jsx6(SettingsPanel, { settings, onChange: changeSettings, onBack: () => setSurface("menu"), error }) : null, surface === "inbox" && member ? view === "table" ? table : inbox : null, toast ? _jsx6("div", { className: "cs-toast", role: "status", children: toast }) : null, bar] });
3781
- return createPortal2(ui, host);
79
+ }, [sessionPath, source]);
80
+ useEffect(() => {
81
+ handle.current?.update(props.current);
82
+ }, [pollMs, position, theme, hasNavigate]);
83
+ return null;
3782
84
  }
3783
85
  export {
3784
86
  CommentToolbar