apostil 0.1.2 → 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/README.md +9 -3
- package/bin/apostil.js +0 -0
- package/dist/adapters/localStorage.cjs +46 -0
- package/dist/adapters/localStorage.cjs.map +1 -0
- package/dist/adapters/localStorage.d.cts +5 -0
- package/dist/adapters/nextjs.cjs +112 -0
- package/dist/adapters/nextjs.cjs.map +1 -0
- package/dist/adapters/nextjs.d.cts +22 -0
- package/dist/adapters/rest.cjs +64 -0
- package/dist/adapters/rest.cjs.map +1 -0
- package/dist/adapters/rest.d.cts +9 -0
- package/dist/cli/index.cjs +293 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/index.cjs +1467 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +64 -0
- package/dist/index.js +134 -64
- package/dist/index.js.map +1 -1
- package/dist/types-oQRt3lYH.d.cts +30 -0
- package/package.json +19 -7
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
|