apostil 0.1.1 → 0.1.3

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/index.cjs ADDED
@@ -0,0 +1,1467 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ApostilProvider: () => ApostilProvider,
24
+ CommentOverlay: () => CommentOverlay,
25
+ CommentSidebar: () => CommentSidebar,
26
+ CommentToggle: () => CommentToggle,
27
+ debug: () => debug,
28
+ useApostil: () => useApostil,
29
+ useCommentMode: () => useCommentMode,
30
+ useComments: () => useComments
31
+ });
32
+ module.exports = __toCommonJS(src_exports);
33
+
34
+ // src/context.tsx
35
+ var import_react = require("react");
36
+
37
+ // src/adapters/rest.ts
38
+ function createRestAdapter(baseUrl) {
39
+ return {
40
+ async load(pageId) {
41
+ const url = `${baseUrl}?pageId=${encodeURIComponent(pageId)}`;
42
+ try {
43
+ const res = await fetch(url);
44
+ if (!res.ok) {
45
+ console.warn(`[apostil] load failed: ${res.status} ${res.statusText} \u2014 ${url}`);
46
+ return [];
47
+ }
48
+ const data = await res.json();
49
+ return data;
50
+ } catch (e) {
51
+ console.warn(`[apostil] load error:`, e, `\u2014 ${url}`);
52
+ return [];
53
+ }
54
+ },
55
+ async save(pageId, threads) {
56
+ const url = `${baseUrl}?pageId=${encodeURIComponent(pageId)}`;
57
+ try {
58
+ const res = await fetch(url, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify(threads)
62
+ });
63
+ if (!res.ok) {
64
+ console.warn(`[apostil] save failed: ${res.status} ${res.statusText} \u2014 ${url}`);
65
+ }
66
+ } catch (e) {
67
+ console.warn(`[apostil] save error:`, e, `\u2014 ${url}`);
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ // src/utils.ts
74
+ var counter = 0;
75
+ function generateId() {
76
+ return `${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;
77
+ }
78
+ var USER_KEY = "apostil-user";
79
+ function loadUser() {
80
+ if (typeof window === "undefined") return null;
81
+ try {
82
+ const raw = localStorage.getItem(USER_KEY);
83
+ return raw ? JSON.parse(raw) : null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ function saveUser(user) {
89
+ if (typeof window === "undefined") return;
90
+ localStorage.setItem(USER_KEY, JSON.stringify(user));
91
+ }
92
+ var USER_COLORS = [
93
+ "#df461c",
94
+ "#2563eb",
95
+ "#16a34a",
96
+ "#9333ea",
97
+ "#ea580c",
98
+ "#0891b2",
99
+ "#c026d3",
100
+ "#4f46e5",
101
+ "#059669",
102
+ "#dc2626"
103
+ ];
104
+ function getRandomColor() {
105
+ return USER_COLORS[Math.floor(Math.random() * USER_COLORS.length)];
106
+ }
107
+
108
+ // src/debug.ts
109
+ var PREFIX = "[apostil]";
110
+ function isDebug() {
111
+ if (typeof window === "undefined") return false;
112
+ try {
113
+ return localStorage.getItem("apostil-debug") === "true";
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ var debug = {
119
+ log(...args) {
120
+ if (isDebug()) console.log(PREFIX, ...args);
121
+ },
122
+ warn(...args) {
123
+ if (isDebug()) console.warn(PREFIX, ...args);
124
+ },
125
+ error(...args) {
126
+ console.error(PREFIX, ...args);
127
+ },
128
+ /** Enable/disable debug logging */
129
+ enable() {
130
+ if (typeof window !== "undefined") {
131
+ localStorage.setItem("apostil-debug", "true");
132
+ console.log(PREFIX, "debug logging enabled \u2014 reload to take effect");
133
+ }
134
+ },
135
+ disable() {
136
+ if (typeof window !== "undefined") {
137
+ localStorage.removeItem("apostil-debug");
138
+ console.log(PREFIX, "debug logging disabled");
139
+ }
140
+ }
141
+ };
142
+ if (typeof window !== "undefined") {
143
+ window.__apostil_debug = debug;
144
+ }
145
+
146
+ // src/context.tsx
147
+ var import_jsx_runtime = require("react/jsx-runtime");
148
+ var defaultAdapter = createRestAdapter("/api/apostil");
149
+ var ApostilContext = (0, import_react.createContext)(null);
150
+ function ApostilProvider({
151
+ pageId,
152
+ storage,
153
+ children
154
+ }) {
155
+ const adapter = storage ?? defaultAdapter;
156
+ const [threads, setThreads] = (0, import_react.useState)([]);
157
+ const [user, setUserState] = (0, import_react.useState)(null);
158
+ const [commentMode, setCommentMode] = (0, import_react.useState)(false);
159
+ const [activeThreadId, setActiveThreadId] = (0, import_react.useState)(null);
160
+ const [sidebarOpen, setSidebarOpen] = (0, import_react.useState)(false);
161
+ const [loaded, setLoaded] = (0, import_react.useState)(false);
162
+ (0, import_react.useEffect)(() => {
163
+ const saved = loadUser();
164
+ if (saved) setUserState(saved);
165
+ }, []);
166
+ const pageIdRef = (0, import_react.useRef)(pageId);
167
+ (0, import_react.useEffect)(() => {
168
+ pageIdRef.current = pageId;
169
+ setLoaded(false);
170
+ debug.log("loading threads for pageId:", pageId);
171
+ adapter.load(pageId).then((t) => {
172
+ if (pageIdRef.current === pageId) {
173
+ debug.log("loaded", t.length, "threads for", pageId);
174
+ setThreads(t);
175
+ setLoaded(true);
176
+ }
177
+ });
178
+ }, [pageId]);
179
+ (0, import_react.useEffect)(() => {
180
+ if (loaded && pageIdRef.current === pageId && threads.length > 0) {
181
+ debug.log("saving", threads.length, "threads for pageId:", pageId);
182
+ adapter.save(pageId, threads);
183
+ }
184
+ }, [threads, pageId, loaded]);
185
+ const setUser = (0, import_react.useCallback)((name) => {
186
+ const u = { id: generateId(), name, color: getRandomColor() };
187
+ setUserState(u);
188
+ saveUser(u);
189
+ }, []);
190
+ const addThread = (0, import_react.useCallback)(
191
+ (pinX, pinY, body, targetId, targetLabel) => {
192
+ if (!user) return;
193
+ const threadId = generateId();
194
+ const thread = {
195
+ id: threadId,
196
+ pageId,
197
+ pinX,
198
+ pinY,
199
+ targetId,
200
+ targetLabel,
201
+ resolved: false,
202
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
203
+ comments: [{
204
+ id: generateId(),
205
+ threadId,
206
+ author: user,
207
+ body,
208
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
209
+ }]
210
+ };
211
+ debug.log("new thread:", { threadId, pinX, pinY, targetId, targetLabel, body });
212
+ setThreads((prev) => [...prev, thread]);
213
+ setActiveThreadId(threadId);
214
+ setCommentMode(false);
215
+ },
216
+ [user, pageId]
217
+ );
218
+ const addReply = (0, import_react.useCallback)(
219
+ (threadId, body) => {
220
+ if (!user) return;
221
+ setThreads(
222
+ (prev) => prev.map(
223
+ (t) => t.id === threadId ? { ...t, comments: [...t.comments, { id: generateId(), threadId, author: user, body, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] } : t
224
+ )
225
+ );
226
+ },
227
+ [user]
228
+ );
229
+ const resolveThread = (0, import_react.useCallback)((threadId) => {
230
+ setThreads((prev) => prev.map((t) => t.id === threadId ? { ...t, resolved: !t.resolved } : t));
231
+ setActiveThreadId(null);
232
+ }, []);
233
+ const deleteThread = (0, import_react.useCallback)((threadId) => {
234
+ setThreads((prev) => prev.filter((t) => t.id !== threadId));
235
+ setActiveThreadId(null);
236
+ }, []);
237
+ const unresolvedCount = threads.filter((t) => !t.resolved).length;
238
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApostilContext.Provider, { value: {
239
+ threads,
240
+ user,
241
+ commentMode,
242
+ activeThreadId,
243
+ sidebarOpen,
244
+ setCommentMode,
245
+ setActiveThreadId,
246
+ setSidebarOpen,
247
+ addThread,
248
+ addReply,
249
+ resolveThread,
250
+ deleteThread,
251
+ setUser,
252
+ unresolvedCount
253
+ }, children });
254
+ }
255
+ function useApostil() {
256
+ const ctx = (0, import_react.useContext)(ApostilContext);
257
+ if (!ctx) throw new Error("useApostil must be used within ApostilProvider");
258
+ return ctx;
259
+ }
260
+
261
+ // src/hooks/use-comments.ts
262
+ function useComments() {
263
+ const { threads, addThread, addReply, resolveThread, deleteThread, unresolvedCount } = useApostil();
264
+ return {
265
+ threads,
266
+ openThreads: threads.filter((t) => !t.resolved),
267
+ resolvedThreads: threads.filter((t) => t.resolved),
268
+ addThread,
269
+ addReply,
270
+ resolveThread,
271
+ deleteThread,
272
+ unresolvedCount
273
+ };
274
+ }
275
+
276
+ // src/hooks/use-comment-mode.ts
277
+ function useCommentMode() {
278
+ const { commentMode, setCommentMode, sidebarOpen, setSidebarOpen } = useApostil();
279
+ return {
280
+ commentMode,
281
+ setCommentMode,
282
+ toggleCommentMode: () => setCommentMode(!commentMode),
283
+ sidebarOpen,
284
+ setSidebarOpen,
285
+ toggleSidebar: () => setSidebarOpen(!sidebarOpen)
286
+ };
287
+ }
288
+
289
+ // src/components/comment-overlay.tsx
290
+ var import_react6 = require("react");
291
+
292
+ // src/components/comment-pin.tsx
293
+ var import_react2 = require("react");
294
+ var import_react_dom = require("react-dom");
295
+ var import_jsx_runtime2 = require("react/jsx-runtime");
296
+ function findTargetElement(targetId) {
297
+ try {
298
+ const el = document.querySelector(targetId);
299
+ if (el instanceof HTMLElement) return el;
300
+ } catch {
301
+ }
302
+ try {
303
+ const el = document.querySelector(`[data-comment-target="${CSS.escape(targetId)}"]`);
304
+ if (el instanceof HTMLElement) return el;
305
+ } catch {
306
+ }
307
+ return null;
308
+ }
309
+ function resolveOverlayPosition(thread, overlayEl) {
310
+ if (!overlayEl) return null;
311
+ const overlayRect = overlayEl.getBoundingClientRect();
312
+ return {
313
+ left: thread.pinX / 100 * overlayRect.width,
314
+ top: thread.pinY / 100 * overlayRect.height
315
+ };
316
+ }
317
+ function resolvePosition(thread, overlayEl) {
318
+ if (!overlayEl) return null;
319
+ const overlayRect = overlayEl.getBoundingClientRect();
320
+ if (thread.targetId) {
321
+ const target = findTargetElement(thread.targetId);
322
+ if (target) {
323
+ const targetRect = target.getBoundingClientRect();
324
+ return {
325
+ left: targetRect.left - overlayRect.left + thread.pinX / 100 * targetRect.width,
326
+ top: targetRect.top - overlayRect.top + thread.pinY / 100 * targetRect.height
327
+ };
328
+ }
329
+ return null;
330
+ }
331
+ return {
332
+ left: thread.pinX / 100 * overlayRect.width,
333
+ top: thread.pinY / 100 * overlayRect.height
334
+ };
335
+ }
336
+ function PinButton({
337
+ thread,
338
+ index,
339
+ isActive,
340
+ onClick
341
+ }) {
342
+ const authorColor = thread.comments[0]?.author.color ?? "#df461c";
343
+ const [hovered, setHovered] = (0, import_react2.useState)(false);
344
+ const buttonRef = (0, import_react2.useRef)(null);
345
+ const [tooltipPos, setTooltipPos] = (0, import_react2.useState)(null);
346
+ (0, import_react2.useEffect)(() => {
347
+ if (!hovered || !buttonRef.current) {
348
+ setTooltipPos(null);
349
+ return;
350
+ }
351
+ const rect = buttonRef.current.getBoundingClientRect();
352
+ setTooltipPos({
353
+ left: rect.left + rect.width / 2,
354
+ top: rect.bottom + 4
355
+ });
356
+ }, [hovered]);
357
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
359
+ "button",
360
+ {
361
+ ref: buttonRef,
362
+ onClick,
363
+ onMouseEnter: () => setHovered(true),
364
+ onMouseLeave: () => setHovered(false),
365
+ style: { position: "relative" },
366
+ children: [
367
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
368
+ "div",
369
+ {
370
+ className: `
371
+ flex items-center justify-center
372
+ w-7 h-7 rounded-full text-white text-xs font-semibold
373
+ shadow-lg cursor-pointer
374
+ transition-all duration-200
375
+ ${isActive ? "scale-125 ring-2 ring-white ring-offset-2" : "hover:scale-110"}
376
+ ${thread.resolved ? "opacity-40" : ""}
377
+ `,
378
+ style: { backgroundColor: authorColor },
379
+ children: index + 1
380
+ }
381
+ ),
382
+ !thread.resolved && !isActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
383
+ "div",
384
+ {
385
+ className: "absolute inset-0 rounded-full animate-ping opacity-20",
386
+ style: { backgroundColor: authorColor }
387
+ }
388
+ )
389
+ ]
390
+ }
391
+ ),
392
+ thread.targetLabel && hovered && tooltipPos && (0, import_react_dom.createPortal)(
393
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
394
+ "div",
395
+ {
396
+ className: "fixed whitespace-nowrap text-[10px] bg-neutral-800 text-white px-1.5 py-0.5 rounded pointer-events-none",
397
+ style: {
398
+ left: tooltipPos.left,
399
+ top: tooltipPos.top,
400
+ transform: "translateX(-50%)",
401
+ zIndex: 999999
402
+ },
403
+ children: thread.targetLabel
404
+ }
405
+ ),
406
+ document.body
407
+ )
408
+ ] });
409
+ }
410
+ function TargetedPin({
411
+ thread,
412
+ index
413
+ }) {
414
+ const { activeThreadId, setActiveThreadId } = useApostil();
415
+ const isActive = activeThreadId === thread.id;
416
+ const [targetEl, setTargetEl] = (0, import_react2.useState)(null);
417
+ (0, import_react2.useEffect)(() => {
418
+ if (!thread.targetId) return;
419
+ function tryFind() {
420
+ const el = findTargetElement(thread.targetId);
421
+ if (el) {
422
+ const pos = getComputedStyle(el).position;
423
+ if (pos === "static") el.style.position = "relative";
424
+ setTargetEl(el);
425
+ } else {
426
+ setTargetEl(null);
427
+ }
428
+ }
429
+ tryFind();
430
+ const observer = new MutationObserver(() => tryFind());
431
+ observer.observe(document.body, { childList: true, subtree: true });
432
+ return () => observer.disconnect();
433
+ }, [thread.targetId]);
434
+ if (!targetEl) return null;
435
+ return (0, import_react_dom.createPortal)(
436
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
437
+ "div",
438
+ {
439
+ className: "absolute pointer-events-auto",
440
+ style: {
441
+ left: `${thread.pinX}%`,
442
+ top: `${thread.pinY}%`,
443
+ transform: "translate(-50%, -50%)",
444
+ zIndex: 10
445
+ },
446
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
447
+ PinButton,
448
+ {
449
+ thread,
450
+ index,
451
+ isActive,
452
+ onClick: (e) => {
453
+ e.stopPropagation();
454
+ setActiveThreadId(isActive ? null : thread.id);
455
+ }
456
+ }
457
+ )
458
+ }
459
+ ),
460
+ targetEl
461
+ );
462
+ }
463
+ function OverlayPin({
464
+ thread,
465
+ index,
466
+ overlayRef
467
+ }) {
468
+ const { activeThreadId, setActiveThreadId } = useApostil();
469
+ const isActive = activeThreadId === thread.id;
470
+ const [pos, setPos] = (0, import_react2.useState)(null);
471
+ const updatePos = (0, import_react2.useCallback)(() => {
472
+ setPos(resolveOverlayPosition(thread, overlayRef.current));
473
+ }, [thread, overlayRef]);
474
+ (0, import_react2.useEffect)(() => {
475
+ updatePos();
476
+ window.addEventListener("resize", updatePos);
477
+ return () => window.removeEventListener("resize", updatePos);
478
+ }, [updatePos]);
479
+ if (!pos) return null;
480
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
481
+ "div",
482
+ {
483
+ className: "absolute pointer-events-auto",
484
+ style: {
485
+ left: pos.left,
486
+ top: pos.top,
487
+ transform: "translate(-50%, -50%)",
488
+ zIndex: 60
489
+ },
490
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
491
+ PinButton,
492
+ {
493
+ thread,
494
+ index,
495
+ isActive,
496
+ onClick: (e) => {
497
+ e.stopPropagation();
498
+ setActiveThreadId(isActive ? null : thread.id);
499
+ }
500
+ }
501
+ )
502
+ }
503
+ );
504
+ }
505
+ function CommentPin({
506
+ thread,
507
+ index,
508
+ overlayRef
509
+ }) {
510
+ if (thread.targetId) {
511
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TargetedPin, { thread, index }, `targeted-${thread.id}`);
512
+ }
513
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(OverlayPin, { thread, index, overlayRef }, `overlay-${thread.id}`);
514
+ }
515
+
516
+ // src/components/comment-thread.tsx
517
+ var import_react4 = require("react");
518
+
519
+ // src/icons.tsx
520
+ var import_jsx_runtime3 = require("react/jsx-runtime");
521
+ function Send({ className = "w-4 h-4" }) {
522
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m22 2-7 20-4-9-9-4Z" }),
524
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M22 2 11 13" })
525
+ ] });
526
+ }
527
+ function MessageSquare({ className = "w-4 h-4" }) {
528
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.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" }) });
529
+ }
530
+ function List({ className = "w-4 h-4" }) {
531
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "8", x2: "21", y1: "6", y2: "6" }),
533
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "8", x2: "21", y1: "12", y2: "12" }),
534
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "8", x2: "21", y1: "18", y2: "18" }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3", x2: "3.01", y1: "6", y2: "6" }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3", x2: "3.01", y1: "12", y2: "12" }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3", x2: "3.01", y1: "18", y2: "18" })
538
+ ] });
539
+ }
540
+ function X({ className = "w-4 h-4" }) {
541
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 6 6 18" }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m6 6 12 12" })
544
+ ] });
545
+ }
546
+ function Check({ className = "w-4 h-4" }) {
547
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
548
+ }
549
+ function Undo2({ className = "w-4 h-4" }) {
550
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 14 4 9l5-5" }),
552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
553
+ ] });
554
+ }
555
+ function Globe({ className = "w-4 h-4" }) {
556
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
559
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 12h20" })
560
+ ] });
561
+ }
562
+ function FileText({ className = "w-4 h-4" }) {
563
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
564
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
565
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 9H8" }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M16 13H8" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M16 17H8" })
569
+ ] });
570
+ }
571
+ function Trash2({ className = "w-4 h-4" }) {
572
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 6h18" }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
576
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
578
+ ] });
579
+ }
580
+
581
+ // src/components/comment-composer.tsx
582
+ var import_react3 = require("react");
583
+ var import_jsx_runtime4 = require("react/jsx-runtime");
584
+ function CommentComposer({
585
+ onSubmit,
586
+ placeholder = "Add a comment...",
587
+ autoFocus = false
588
+ }) {
589
+ const [value, setValue] = (0, import_react3.useState)("");
590
+ const inputRef = (0, import_react3.useRef)(null);
591
+ (0, import_react3.useEffect)(() => {
592
+ if (autoFocus) {
593
+ inputRef.current?.focus();
594
+ }
595
+ }, [autoFocus]);
596
+ const handleSubmit = () => {
597
+ const trimmed = value.trim();
598
+ if (!trimmed) return;
599
+ onSubmit(trimmed);
600
+ setValue("");
601
+ };
602
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 items-end", children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
604
+ "textarea",
605
+ {
606
+ ref: inputRef,
607
+ value,
608
+ onChange: (e) => setValue(e.target.value),
609
+ onKeyDown: (e) => {
610
+ if (e.key === "Enter" && !e.shiftKey) {
611
+ e.preventDefault();
612
+ handleSubmit();
613
+ }
614
+ },
615
+ placeholder,
616
+ rows: 1,
617
+ className: "flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300\n min-h-[36px] max-h-[120px]"
618
+ }
619
+ ),
620
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
621
+ "button",
622
+ {
623
+ onClick: handleSubmit,
624
+ disabled: !value.trim(),
625
+ className: "flex items-center justify-center w-8 h-8 rounded-lg\n bg-neutral-900 text-white disabled:opacity-30\n hover:bg-neutral-700 transition-colors shrink-0",
626
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Send, { className: "w-3.5 h-3.5" })
627
+ }
628
+ )
629
+ ] });
630
+ }
631
+
632
+ // src/components/comment-thread.tsx
633
+ var import_jsx_runtime5 = require("react/jsx-runtime");
634
+ function timeAgo(iso) {
635
+ const diff = Date.now() - new Date(iso).getTime();
636
+ const mins = Math.floor(diff / 6e4);
637
+ if (mins < 1) return "just now";
638
+ if (mins < 60) return `${mins}m ago`;
639
+ const hours = Math.floor(mins / 60);
640
+ if (hours < 24) return `${hours}h ago`;
641
+ const days = Math.floor(hours / 24);
642
+ return `${days}d ago`;
643
+ }
644
+ function ApostilThreadPopover({
645
+ thread,
646
+ overlayRef
647
+ }) {
648
+ const { activeThreadId, setActiveThreadId, addReply, resolveThread, deleteThread, user } = useApostil();
649
+ const ref = (0, import_react4.useRef)(null);
650
+ const isOpen = activeThreadId === thread.id;
651
+ const [pos, setPos] = (0, import_react4.useState)(null);
652
+ const updatePos = (0, import_react4.useCallback)(() => {
653
+ setPos(resolvePosition(thread, overlayRef.current));
654
+ }, [thread, overlayRef]);
655
+ (0, import_react4.useEffect)(() => {
656
+ if (!isOpen) return;
657
+ updatePos();
658
+ window.addEventListener("resize", updatePos);
659
+ document.addEventListener("scroll", updatePos, true);
660
+ return () => {
661
+ window.removeEventListener("resize", updatePos);
662
+ document.removeEventListener("scroll", updatePos, true);
663
+ };
664
+ }, [isOpen, updatePos]);
665
+ (0, import_react4.useEffect)(() => {
666
+ if (!isOpen) return;
667
+ const handler = (e) => {
668
+ if (ref.current && !ref.current.contains(e.target)) {
669
+ setActiveThreadId(null);
670
+ }
671
+ };
672
+ const timer = setTimeout(() => document.addEventListener("mousedown", handler), 0);
673
+ return () => {
674
+ clearTimeout(timer);
675
+ document.removeEventListener("mousedown", handler);
676
+ };
677
+ }, [isOpen, setActiveThreadId]);
678
+ if (!isOpen || !pos) return null;
679
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
680
+ "div",
681
+ {
682
+ ref,
683
+ className: "absolute z-[70] ml-5 -mt-3",
684
+ style: { left: pos.left, top: pos.top },
685
+ onClick: (e) => e.stopPropagation(),
686
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50", children: [
688
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
689
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "text-xs font-medium text-neutral-500", children: [
690
+ thread.comments.length,
691
+ " ",
692
+ thread.comments.length === 1 ? "comment" : "comments"
693
+ ] }),
694
+ thread.targetLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: thread.targetLabel }),
695
+ thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] text-emerald-600 font-medium", children: "Resolved" })
696
+ ] }),
697
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex gap-1", children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
699
+ "button",
700
+ {
701
+ onClick: () => resolveThread(thread.id),
702
+ className: "p-1 rounded hover:bg-neutral-200 transition-colors",
703
+ title: thread.resolved ? "Reopen" : "Resolve",
704
+ children: thread.resolved ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Undo2, { className: "w-3.5 h-3.5 text-neutral-500" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Check, { className: "w-3.5 h-3.5 text-emerald-600" })
705
+ }
706
+ ),
707
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
708
+ "button",
709
+ {
710
+ onClick: () => deleteThread(thread.id),
711
+ className: "p-1 rounded hover:bg-red-50 transition-colors",
712
+ title: "Delete thread",
713
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Trash2, { className: "w-3.5 h-3.5 text-neutral-400 hover:text-red-500" })
714
+ }
715
+ )
716
+ ] })
717
+ ] }),
718
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "max-h-64 overflow-y-auto", children: thread.comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "px-4 py-3 border-b border-neutral-50 last:border-0", children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [
720
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
721
+ "div",
722
+ {
723
+ className: "w-5 h-5 rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0",
724
+ style: { backgroundColor: comment.author.color },
725
+ children: comment.author.name[0]?.toUpperCase()
726
+ }
727
+ ),
728
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs font-medium text-neutral-800", children: comment.author.name }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] text-neutral-400 ml-auto", children: timeAgo(comment.createdAt) })
730
+ ] }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm text-neutral-700 leading-relaxed pl-7", children: comment.body })
732
+ ] }, comment.id)) }),
733
+ user && !thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "px-3 py-2.5 border-t border-neutral-100 bg-neutral-50/50", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
734
+ CommentComposer,
735
+ {
736
+ onSubmit: (body) => addReply(thread.id, body),
737
+ placeholder: "Reply...",
738
+ autoFocus: true
739
+ }
740
+ ) })
741
+ ] })
742
+ }
743
+ );
744
+ }
745
+
746
+ // src/components/user-prompt.tsx
747
+ var import_react5 = require("react");
748
+ var import_jsx_runtime6 = require("react/jsx-runtime");
749
+ function UserPrompt() {
750
+ const { user, setUser, commentMode } = useApostil();
751
+ const [name, setName] = (0, import_react5.useState)("");
752
+ if (user || !commentMode) return null;
753
+ const handleSubmit = () => {
754
+ const trimmed = name.trim();
755
+ if (!trimmed) return;
756
+ setUser(trimmed);
757
+ };
758
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
759
+ "div",
760
+ {
761
+ className: "fixed inset-0 flex items-center justify-center bg-black/20 backdrop-blur-[2px]",
762
+ style: { zIndex: 99999 },
763
+ onMouseDown: (e) => e.stopPropagation(),
764
+ onClick: (e) => e.stopPropagation(),
765
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-6 w-80", children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "text-sm font-semibold text-neutral-900 mb-1", children: "What's your name?" }),
767
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-xs text-neutral-500 mb-4", children: "This will be shown with your comments." }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
769
+ "input",
770
+ {
771
+ value: name,
772
+ onChange: (e) => setName(e.target.value),
773
+ onKeyDown: (e) => {
774
+ if (e.key === "Enter") handleSubmit();
775
+ },
776
+ placeholder: "Enter your name",
777
+ autoFocus: true,
778
+ className: "w-full rounded-lg border border-neutral-200 px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300 mb-3"
779
+ }
780
+ ),
781
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
782
+ "button",
783
+ {
784
+ onClick: handleSubmit,
785
+ disabled: !name.trim(),
786
+ className: "w-full py-2 rounded-lg bg-neutral-900 text-white text-sm font-medium\n disabled:opacity-30 hover:bg-neutral-700 transition-colors",
787
+ children: "Continue"
788
+ }
789
+ )
790
+ ] })
791
+ }
792
+ );
793
+ }
794
+
795
+ // src/components/comment-overlay.tsx
796
+ var import_jsx_runtime7 = require("react/jsx-runtime");
797
+ var cachedHighZ = 0;
798
+ var cacheTimestamp = 0;
799
+ function getHighestZIndex() {
800
+ const now = Date.now();
801
+ if (now - cacheTimestamp < 500) return cachedHighZ;
802
+ let max = 0;
803
+ const els = document.querySelectorAll("[style*='z-index'], [class*='z-']");
804
+ for (let i = 0; i < els.length; i++) {
805
+ const z = parseInt(getComputedStyle(els[i]).zIndex, 10);
806
+ if (!isNaN(z) && z > max) max = z;
807
+ }
808
+ cachedHighZ = Math.max(max, 100);
809
+ cacheTimestamp = now;
810
+ return cachedHighZ;
811
+ }
812
+ var SEMANTIC_TAGS = /* @__PURE__ */ new Set([
813
+ "SECTION",
814
+ "NAV",
815
+ "ASIDE",
816
+ "HEADER",
817
+ "FOOTER",
818
+ "MAIN",
819
+ "ARTICLE",
820
+ "FORM",
821
+ "DIALOG",
822
+ "DETAILS"
823
+ ]);
824
+ var MIN_TARGET_SIZE = 50;
825
+ var MAX_VIEWPORT_RATIO = 0.85;
826
+ function isVisualPanel(el) {
827
+ const style = getComputedStyle(el);
828
+ if (/auto|scroll/.test(style.overflow + style.overflowY + style.overflowX)) return true;
829
+ if (style.borderWidth && style.borderWidth !== "0px" && style.borderStyle !== "none") return true;
830
+ if (style.borderRadius && style.borderRadius !== "0px") return true;
831
+ if (style.backgroundColor && style.backgroundColor !== "rgba(0, 0, 0, 0)" && style.backgroundColor !== "transparent") return true;
832
+ if (style.boxShadow && style.boxShadow !== "none") return true;
833
+ return false;
834
+ }
835
+ function inferLabel(el) {
836
+ const heading = el.querySelector("h1, h2, h3, h4, h5, h6, [class*='title'], [class*='heading']");
837
+ if (heading) {
838
+ const text = heading.textContent?.trim();
839
+ if (text && text.length <= 40) return text;
840
+ }
841
+ const firstText = el.querySelector("span, p, label, strong");
842
+ if (firstText) {
843
+ const text = firstText.textContent?.trim();
844
+ if (text && text.length <= 30) return text;
845
+ }
846
+ return null;
847
+ }
848
+ function getElementId(el) {
849
+ const manual = el.getAttribute("data-comment-target");
850
+ if (manual) return manual;
851
+ if (el.id) return `#${el.id}`;
852
+ const label = el.getAttribute("aria-label");
853
+ if (label) return `${el.tagName.toLowerCase()}[aria-label="${label}"]`;
854
+ const parts = [];
855
+ let cur = el;
856
+ for (let depth = 0; cur && depth < 5; depth++) {
857
+ if (cur.id) {
858
+ parts.unshift(`#${cur.id}`);
859
+ break;
860
+ }
861
+ const p = cur.parentElement;
862
+ if (p) {
863
+ const siblings = Array.from(p.children);
864
+ const idx = siblings.indexOf(cur);
865
+ parts.unshift(`${cur.tagName.toLowerCase()}:nth-child(${idx + 1})`);
866
+ } else {
867
+ parts.unshift(cur.tagName.toLowerCase());
868
+ }
869
+ cur = p;
870
+ }
871
+ return parts.join(" > ");
872
+ }
873
+ function getElementLabel(el) {
874
+ const manual = el.getAttribute("data-comment-label");
875
+ if (manual) return manual;
876
+ const ariaLabel = el.getAttribute("aria-label");
877
+ if (ariaLabel) return ariaLabel;
878
+ const inferred = inferLabel(el);
879
+ if (inferred) return inferred;
880
+ const role = el.getAttribute("role");
881
+ if (el.id) {
882
+ const pretty = el.id.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
883
+ return role ? `${pretty} (${role})` : pretty;
884
+ }
885
+ const tag = el.tagName.toLowerCase();
886
+ if (SEMANTIC_TAGS.has(el.tagName)) return role ? `${tag} (${role})` : tag;
887
+ if (role) return role;
888
+ return tag;
889
+ }
890
+ function findCommentTarget(el, boundary) {
891
+ let current = el;
892
+ const candidates = [];
893
+ let depth = 0;
894
+ while (current && current !== boundary && current !== document.body) {
895
+ const rect = current.getBoundingClientRect();
896
+ const isBigEnough = rect.width >= MIN_TARGET_SIZE && rect.height >= MIN_TARGET_SIZE;
897
+ const isTooBig = rect.width > window.innerWidth * MAX_VIEWPORT_RATIO && rect.height > window.innerHeight * MAX_VIEWPORT_RATIO;
898
+ if (isBigEnough && !isTooBig) {
899
+ let score = 0;
900
+ if (current.getAttribute("data-comment-target")) score = 100;
901
+ else if (current.id) score = 80;
902
+ else if (current.getAttribute("aria-label")) score = 70;
903
+ else if (current.getAttribute("role")) score = 60;
904
+ else if (SEMANTIC_TAGS.has(current.tagName)) score = 50;
905
+ else if (isVisualPanel(current)) score = 40;
906
+ if (score > 0) {
907
+ candidates.push({ el: current, score, depth });
908
+ }
909
+ }
910
+ current = current.parentElement;
911
+ depth++;
912
+ }
913
+ if (candidates.length === 0) return null;
914
+ candidates.sort((a, b) => {
915
+ const scoreA = a.score + Math.max(0, 10 - a.depth);
916
+ const scoreB = b.score + Math.max(0, 10 - b.depth);
917
+ return scoreB - scoreA;
918
+ });
919
+ const best = candidates[0];
920
+ const targetId = getElementId(best.el);
921
+ const targetLabel = getElementLabel(best.el);
922
+ debug.log(" candidates:", candidates.map((c) => ({
923
+ tag: c.el.tagName.toLowerCase(),
924
+ id: c.el.id || void 0,
925
+ class: c.el.className?.toString().slice(0, 60) || void 0,
926
+ score: c.score,
927
+ depth: c.depth,
928
+ label: getElementLabel(c.el)
929
+ })));
930
+ return {
931
+ targetId,
932
+ targetLabel,
933
+ element: best.el
934
+ };
935
+ }
936
+ function CommentOverlay() {
937
+ const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId } = useApostil();
938
+ const overlayRef = (0, import_react6.useRef)(null);
939
+ const [pendingPin, setPendingPin] = (0, import_react6.useState)(null);
940
+ const [pendingPixel, setPendingPixel] = (0, import_react6.useState)(null);
941
+ const handleClick = (0, import_react6.useCallback)(
942
+ (e) => {
943
+ if (!commentMode || !overlayRef.current) return;
944
+ const overlayRect = overlayRef.current.getBoundingClientRect();
945
+ const overlay = overlayRef.current;
946
+ overlay.style.pointerEvents = "none";
947
+ const elementBelow = document.elementFromPoint(e.clientX, e.clientY);
948
+ overlay.style.pointerEvents = "";
949
+ debug.log(" click at", { clientX: e.clientX, clientY: e.clientY });
950
+ debug.log(" element below overlay:", elementBelow);
951
+ if (elementBelow) {
952
+ const path = [];
953
+ let walk = elementBelow;
954
+ while (walk && walk !== document.body) {
955
+ const attrs = [walk.tagName.toLowerCase()];
956
+ if (walk.id) attrs.push(`#${walk.id}`);
957
+ if (walk.getAttribute("data-comment-target")) attrs.push(`[data-comment-target="${walk.getAttribute("data-comment-target")}"]`);
958
+ if (walk.getAttribute("aria-label")) attrs.push(`[aria-label="${walk.getAttribute("aria-label")}"]`);
959
+ if (walk.getAttribute("role")) attrs.push(`[role="${walk.getAttribute("role")}"]`);
960
+ if (SEMANTIC_TAGS.has(walk.tagName)) attrs.push("(semantic)");
961
+ path.push(attrs.join(""));
962
+ walk = walk.parentElement;
963
+ }
964
+ debug.log(" DOM path:", path.join(" \u2192 "));
965
+ }
966
+ const target = elementBelow ? findCommentTarget(elementBelow, overlayRef.current) : null;
967
+ if (target) {
968
+ const targetRect = target.element.getBoundingClientRect();
969
+ const x = (e.clientX - targetRect.left) / targetRect.width * 100;
970
+ const y = (e.clientY - targetRect.top) / targetRect.height * 100;
971
+ debug.log(" \u2705 target found:", {
972
+ targetId: target.targetId,
973
+ targetLabel: target.targetLabel,
974
+ element: target.element,
975
+ relativePos: { x: x.toFixed(1), y: y.toFixed(1) },
976
+ targetRect: { w: targetRect.width, h: targetRect.height }
977
+ });
978
+ setPendingPin({ x, y, targetId: target.targetId, targetLabel: target.targetLabel });
979
+ } else {
980
+ const x = (e.clientX - overlayRect.left) / overlayRect.width * 100;
981
+ const y = (e.clientY - overlayRect.top) / overlayRect.height * 100;
982
+ debug.log(" \u26A0\uFE0F no target found, using overlay-relative position:", {
983
+ x: x.toFixed(1),
984
+ y: y.toFixed(1)
985
+ });
986
+ setPendingPin({ x, y });
987
+ }
988
+ setPendingPixel({
989
+ left: e.clientX - overlayRect.left,
990
+ top: e.clientY - overlayRect.top
991
+ });
992
+ setActiveThreadId(null);
993
+ },
994
+ [commentMode, setActiveThreadId]
995
+ );
996
+ const handleNewComment = (0, import_react6.useCallback)(
997
+ (body) => {
998
+ if (!pendingPin) return;
999
+ debug.log(" saving thread:", {
1000
+ pinX: pendingPin.x.toFixed(1),
1001
+ pinY: pendingPin.y.toFixed(1),
1002
+ targetId: pendingPin.targetId ?? "(none)",
1003
+ targetLabel: pendingPin.targetLabel ?? "(none)",
1004
+ body
1005
+ });
1006
+ addThread(pendingPin.x, pendingPin.y, body, pendingPin.targetId, pendingPin.targetLabel);
1007
+ setPendingPin(null);
1008
+ setPendingPixel(null);
1009
+ },
1010
+ [pendingPin, addThread]
1011
+ );
1012
+ const cancelPending = (0, import_react6.useCallback)(() => {
1013
+ setPendingPin(null);
1014
+ setPendingPixel(null);
1015
+ setCommentMode(false);
1016
+ }, [setCommentMode]);
1017
+ (0, import_react6.useEffect)(() => {
1018
+ const hash = window.location.hash;
1019
+ console.log("[apostil] hash check:", hash, "threads:", threads.length, threads.map((t) => t.id));
1020
+ if (!hash.startsWith("#apostil-")) return;
1021
+ const threadId = hash.replace("#apostil-", "");
1022
+ console.log("[apostil] looking for thread:", threadId);
1023
+ if (threads.length === 0) {
1024
+ console.log("[apostil] no threads loaded yet, waiting...");
1025
+ return;
1026
+ }
1027
+ const found = threads.find((t) => t.id === threadId);
1028
+ console.log("[apostil] found thread:", found ? "yes" : "no");
1029
+ if (found) {
1030
+ setActiveThreadId(threadId);
1031
+ window.history.replaceState(null, "", window.location.pathname);
1032
+ }
1033
+ }, [threads, setActiveThreadId]);
1034
+ (0, import_react6.useEffect)(() => {
1035
+ const handler = (e) => {
1036
+ const tag = e.target?.tagName;
1037
+ if (e.key === "Escape") {
1038
+ if (pendingPin) {
1039
+ setPendingPin(null);
1040
+ setPendingPixel(null);
1041
+ setCommentMode(false);
1042
+ } else if (commentMode) {
1043
+ setCommentMode(false);
1044
+ }
1045
+ return;
1046
+ }
1047
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
1048
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
1049
+ if (e.key === "c" || e.key === "C") {
1050
+ e.preventDefault();
1051
+ if (!commentMode) {
1052
+ debug.log(" comment mode ON");
1053
+ setActiveThreadId(null);
1054
+ setCommentMode(true);
1055
+ } else {
1056
+ debug.log(" comment mode OFF");
1057
+ setCommentMode(false);
1058
+ }
1059
+ }
1060
+ };
1061
+ document.addEventListener("keydown", handler);
1062
+ return () => document.removeEventListener("keydown", handler);
1063
+ }, [commentMode, pendingPin, setCommentMode, setActiveThreadId]);
1064
+ const sortedThreads = [...threads].sort((a, b) => {
1065
+ if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
1066
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
1067
+ });
1068
+ const showingUserPrompt = commentMode && !user;
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1070
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1071
+ "div",
1072
+ {
1073
+ ref: overlayRef,
1074
+ className: `fixed inset-0 ${commentMode && !showingUserPrompt ? "cursor-crosshair pointer-events-auto" : "pointer-events-none"}`,
1075
+ style: { zIndex: commentMode ? getHighestZIndex() + 10 : 55 },
1076
+ onMouseDown: handleClick,
1077
+ children: [
1078
+ sortedThreads.map((thread, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pointer-events-auto", children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CommentPin, { thread, index: i, overlayRef }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ApostilThreadPopover, { thread, overlayRef })
1081
+ ] }, thread.id)),
1082
+ pendingPin && pendingPixel && user && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1083
+ "div",
1084
+ {
1085
+ className: "absolute z-[70] pointer-events-auto",
1086
+ style: {
1087
+ left: pendingPixel.left,
1088
+ top: pendingPixel.top
1089
+ },
1090
+ onMouseDown: (e) => e.stopPropagation(),
1091
+ onClick: (e) => e.stopPropagation(),
1092
+ children: [
1093
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1094
+ "div",
1095
+ {
1096
+ className: "absolute -translate-x-1/2 -translate-y-1/2 w-7 h-7 rounded-full\n flex items-center justify-center text-white text-xs font-semibold\n shadow-lg ring-2 ring-white ring-offset-2 animate-bounce",
1097
+ style: { backgroundColor: user.color },
1098
+ children: "+"
1099
+ }
1100
+ ),
1101
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "absolute ml-5 -mt-3 w-72", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
1103
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-xs text-neutral-500", children: "New comment" }),
1104
+ pendingPin.targetLabel && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
1105
+ ] }),
1106
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1107
+ CommentComposer,
1108
+ {
1109
+ onSubmit: handleNewComment,
1110
+ placeholder: "What's on your mind?",
1111
+ autoFocus: true
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1115
+ "button",
1116
+ {
1117
+ onClick: cancelPending,
1118
+ className: "mt-2 text-xs text-neutral-400 hover:text-neutral-600 transition-colors",
1119
+ children: "Cancel"
1120
+ }
1121
+ )
1122
+ ] }) })
1123
+ ]
1124
+ }
1125
+ )
1126
+ ]
1127
+ }
1128
+ ),
1129
+ commentMode && !pendingPin && !showingUserPrompt && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm", children: "Click anywhere to add a comment" }) }),
1130
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserPrompt, {})
1131
+ ] });
1132
+ }
1133
+
1134
+ // src/components/comment-toggle.tsx
1135
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1136
+ function CommentToggle() {
1137
+ const {
1138
+ commentMode,
1139
+ setCommentMode,
1140
+ sidebarOpen,
1141
+ setSidebarOpen,
1142
+ unresolvedCount,
1143
+ setActiveThreadId
1144
+ } = useApostil();
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end", children: [
1146
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1147
+ "button",
1148
+ {
1149
+ onClick: () => setSidebarOpen(!sidebarOpen),
1150
+ className: `flex items-center justify-center w-10 h-10 rounded-full shadow-lg
1151
+ transition-all duration-200 hover:scale-105
1152
+ ${sidebarOpen ? "bg-neutral-900 text-white" : "bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200"}`,
1153
+ title: "Toggle comment list",
1154
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(List, { className: "w-4 h-4" })
1155
+ }
1156
+ ),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1158
+ "button",
1159
+ {
1160
+ onClick: () => {
1161
+ if (commentMode) {
1162
+ setCommentMode(false);
1163
+ } else {
1164
+ setActiveThreadId(null);
1165
+ setCommentMode(true);
1166
+ }
1167
+ },
1168
+ className: `relative flex items-center justify-center w-12 h-12 rounded-full shadow-lg
1169
+ transition-all duration-200 hover:scale-105
1170
+ ${commentMode ? "bg-neutral-900 text-white ring-2 ring-neutral-400" : "bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200"}`,
1171
+ title: commentMode ? "Exit comment mode" : "Add comment",
1172
+ children: [
1173
+ commentMode ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(X, { className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageSquare, { className: "w-5 h-5" }),
1174
+ unresolvedCount > 0 && !commentMode && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full\n bg-red-500 text-white text-[10px] font-semibold\n flex items-center justify-center px-1", children: unresolvedCount })
1175
+ ]
1176
+ }
1177
+ )
1178
+ ] });
1179
+ }
1180
+
1181
+ // src/components/comment-sidebar.tsx
1182
+ var import_react7 = require("react");
1183
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1184
+ function timeAgo2(iso) {
1185
+ const diff = Date.now() - new Date(iso).getTime();
1186
+ const mins = Math.floor(diff / 6e4);
1187
+ if (mins < 1) return "just now";
1188
+ if (mins < 60) return `${mins}m ago`;
1189
+ const hours = Math.floor(mins / 60);
1190
+ if (hours < 24) return `${hours}h ago`;
1191
+ const days = Math.floor(hours / 24);
1192
+ return `${days}d ago`;
1193
+ }
1194
+ function pageIdToDisplay(pageId) {
1195
+ return pageId.replace(/--/g, "/").replace(/-/g, ".");
1196
+ }
1197
+ function CommentSidebar() {
1198
+ const {
1199
+ threads,
1200
+ sidebarOpen,
1201
+ setSidebarOpen,
1202
+ setActiveThreadId,
1203
+ resolveThread
1204
+ } = useApostil();
1205
+ const [tab, setTab] = (0, import_react7.useState)("page");
1206
+ const [allPages, setAllPages] = (0, import_react7.useState)([]);
1207
+ const [loadingAll, setLoadingAll] = (0, import_react7.useState)(false);
1208
+ (0, import_react7.useEffect)(() => {
1209
+ if (!sidebarOpen || tab !== "all") return;
1210
+ setLoadingAll(true);
1211
+ async function fetchAll() {
1212
+ try {
1213
+ const res = await fetch("/api/apostil");
1214
+ if (res.ok) {
1215
+ const data = await res.json();
1216
+ setAllPages(data);
1217
+ }
1218
+ } catch {
1219
+ }
1220
+ setLoadingAll(false);
1221
+ }
1222
+ fetchAll();
1223
+ }, [sidebarOpen, tab]);
1224
+ if (!sidebarOpen) return null;
1225
+ const openThreads = threads.filter((t) => !t.resolved);
1226
+ const resolvedThreads = threads.filter((t) => t.resolved);
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "absolute top-0 right-0 bottom-0 w-80 z-[75] bg-white border-l border-neutral-200 shadow-xl flex flex-col", children: [
1228
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between px-4 py-3 border-b border-neutral-100", children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
1230
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageSquare, { className: "w-4 h-4 text-neutral-500" }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-sm font-semibold text-neutral-900", children: "Comments" })
1232
+ ] }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1234
+ "button",
1235
+ {
1236
+ onClick: () => setSidebarOpen(false),
1237
+ className: "p-1 rounded hover:bg-neutral-100 transition-colors",
1238
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(X, { className: "w-4 h-4 text-neutral-500" })
1239
+ }
1240
+ )
1241
+ ] }),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex border-b border-neutral-100", children: [
1243
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1244
+ "button",
1245
+ {
1246
+ onClick: () => setTab("page"),
1247
+ className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "page" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1248
+ children: [
1249
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FileText, { className: "w-3 h-3" }),
1250
+ "This Page",
1251
+ openThreads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full", children: openThreads.length })
1252
+ ]
1253
+ }
1254
+ ),
1255
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1256
+ "button",
1257
+ {
1258
+ onClick: () => setTab("all"),
1259
+ className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "all" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1260
+ children: [
1261
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Globe, { className: "w-3 h-3" }),
1262
+ "All Pages"
1263
+ ]
1264
+ }
1265
+ )
1266
+ ] }),
1267
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 overflow-y-auto", children: tab === "page" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1268
+ PageThreads,
1269
+ {
1270
+ threads,
1271
+ openThreads,
1272
+ resolvedThreads,
1273
+ onSelect: setActiveThreadId,
1274
+ onResolve: resolveThread
1275
+ }
1276
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1277
+ AllPagesView,
1278
+ {
1279
+ pages: allPages,
1280
+ loading: loadingAll
1281
+ }
1282
+ ) })
1283
+ ] });
1284
+ }
1285
+ function PageThreads({
1286
+ threads,
1287
+ openThreads,
1288
+ resolvedThreads,
1289
+ onSelect,
1290
+ onResolve
1291
+ }) {
1292
+ if (threads.length === 0) {
1293
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments on this page." });
1294
+ }
1295
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1296
+ openThreads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1297
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1298
+ "Open (",
1299
+ openThreads.length,
1300
+ ")"
1301
+ ] }),
1302
+ openThreads.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1303
+ ThreadItem,
1304
+ {
1305
+ thread,
1306
+ onSelect: () => onSelect(thread.id),
1307
+ onResolve: () => onResolve(thread.id)
1308
+ },
1309
+ thread.id
1310
+ ))
1311
+ ] }),
1312
+ resolvedThreads.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
1313
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1314
+ "Resolved (",
1315
+ resolvedThreads.length,
1316
+ ")"
1317
+ ] }),
1318
+ resolvedThreads.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1319
+ ThreadItem,
1320
+ {
1321
+ thread,
1322
+ onSelect: () => onSelect(thread.id),
1323
+ onResolve: () => onResolve(thread.id),
1324
+ resolved: true
1325
+ },
1326
+ thread.id
1327
+ ))
1328
+ ] })
1329
+ ] });
1330
+ }
1331
+ function AllPagesView({
1332
+ pages,
1333
+ loading
1334
+ }) {
1335
+ if (loading) {
1336
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "p-6 text-center text-sm text-neutral-400", children: "Loading..." });
1337
+ }
1338
+ if (pages.length === 0) {
1339
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments in this project yet." });
1340
+ }
1341
+ const totalOpen = pages.reduce((s, p) => s + p.threads.filter((t) => !t.resolved).length, 0);
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1343
+ totalOpen > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1344
+ totalOpen,
1345
+ " open across ",
1346
+ pages.length,
1347
+ " pages"
1348
+ ] }),
1349
+ pages.map((page) => {
1350
+ const open = page.threads.filter((t) => !t.resolved);
1351
+ const resolved = page.threads.filter((t) => t.resolved);
1352
+ const displayName = pageIdToDisplay(page.pageId);
1353
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-b border-neutral-50", children: [
1354
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "px-4 py-2.5 flex items-center justify-between bg-neutral-50/50", children: [
1355
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs font-semibold text-neutral-700 truncate", children: displayName }),
1356
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1.5", children: [
1357
+ open.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full font-medium", children: open.length }),
1358
+ resolved.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] bg-neutral-100 text-neutral-500 px-1.5 py-px rounded-full font-medium", children: resolved.length })
1359
+ ] })
1360
+ ] }),
1361
+ [...open, ...resolved].map((thread) => {
1362
+ const firstComment = thread.comments[0];
1363
+ if (!firstComment) return null;
1364
+ const isResolved = thread.resolved;
1365
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1366
+ "div",
1367
+ {
1368
+ onClick: () => {
1369
+ const path = page.pageId === "home" ? "/" : "/" + page.pageId.replace(/--/g, "/");
1370
+ window.location.href = path + "#apostil-" + thread.id;
1371
+ },
1372
+ className: `px-4 py-2.5 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors ${isResolved ? "opacity-50" : ""}`,
1373
+ children: [
1374
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
1375
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
1376
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1377
+ "div",
1378
+ {
1379
+ className: "w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold",
1380
+ style: { backgroundColor: firstComment.author.color },
1381
+ children: firstComment.author.name[0]?.toUpperCase()
1382
+ }
1383
+ ),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1385
+ ] }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) })
1387
+ ] }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1389
+ thread.comments.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1390
+ thread.comments.length - 1,
1391
+ " ",
1392
+ thread.comments.length - 1 === 1 ? "reply" : "replies"
1393
+ ] })
1394
+ ]
1395
+ },
1396
+ thread.id
1397
+ );
1398
+ })
1399
+ ] }, page.pageId);
1400
+ })
1401
+ ] });
1402
+ }
1403
+ function ThreadItem({
1404
+ thread,
1405
+ onSelect,
1406
+ onResolve,
1407
+ resolved
1408
+ }) {
1409
+ const firstComment = thread.comments[0];
1410
+ if (!firstComment) return null;
1411
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1412
+ "div",
1413
+ {
1414
+ onClick: onSelect,
1415
+ className: `px-4 py-3 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors
1416
+ ${resolved ? "opacity-60" : ""}`,
1417
+ children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
1419
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1421
+ "div",
1422
+ {
1423
+ className: "w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold",
1424
+ style: { backgroundColor: firstComment.author.color },
1425
+ children: firstComment.author.name[0]?.toUpperCase()
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1429
+ ] }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1", children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1433
+ "button",
1434
+ {
1435
+ onClick: (e) => {
1436
+ e.stopPropagation();
1437
+ onResolve();
1438
+ },
1439
+ className: "p-0.5 rounded hover:bg-neutral-200 transition-colors",
1440
+ children: resolved ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Undo2, { className: "w-3 h-3 text-neutral-400" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Check, { className: "w-3 h-3 text-emerald-600" })
1441
+ }
1442
+ )
1443
+ ] })
1444
+ ] }),
1445
+ thread.targetLabel && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "inline-block text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium ml-6 mb-1", children: thread.targetLabel }),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1447
+ thread.comments.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1448
+ thread.comments.length - 1,
1449
+ " ",
1450
+ thread.comments.length - 1 === 1 ? "reply" : "replies"
1451
+ ] })
1452
+ ]
1453
+ }
1454
+ );
1455
+ }
1456
+ // Annotate the CommonJS export names for ESM import in node:
1457
+ 0 && (module.exports = {
1458
+ ApostilProvider,
1459
+ CommentOverlay,
1460
+ CommentSidebar,
1461
+ CommentToggle,
1462
+ debug,
1463
+ useApostil,
1464
+ useCommentMode,
1465
+ useComments
1466
+ });
1467
+ //# sourceMappingURL=index.cjs.map