comment-mode 0.1.2 → 0.1.4
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 +27 -8
- package/dist/index.d.mts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +414 -148
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +415 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useCallback,
|
|
1
|
+
import { createContext, useState, useCallback, useLayoutEffect, useMemo, useEffect, useRef, useContext } from 'react';
|
|
2
2
|
import { createClient } from '@supabase/supabase-js';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -15,6 +15,7 @@ function AuthProvider(props) {
|
|
|
15
15
|
const [accessToken, setAccessToken] = useState(null);
|
|
16
16
|
const [isReady, setIsReady] = useState(false);
|
|
17
17
|
const [displayName, setDisplayNameState] = useState("");
|
|
18
|
+
const [avatarUrl, setAvatarUrl] = useState(null);
|
|
18
19
|
const apiBaseUrl = (_a = config.apiBaseUrl) != null ? _a : DEFAULT_API_BASE_URL;
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
let cancelled = false;
|
|
@@ -27,8 +28,13 @@ function AuthProvider(props) {
|
|
|
27
28
|
const userName = ((_c = meta == null ? void 0 : meta.user_name) == null ? void 0 : _c.trim()) || ((_d = meta == null ? void 0 : meta.preferred_username) == null ? void 0 : _d.trim());
|
|
28
29
|
const emailPrefix = (_f = (_e = u.email) == null ? void 0 : _e.split("@")[0]) == null ? void 0 : _f.trim();
|
|
29
30
|
return fullName || userName || emailPrefix || "";
|
|
31
|
+
}, getAvatarUrlFromUser2 = function(u) {
|
|
32
|
+
var _a2;
|
|
33
|
+
const meta = u.user_metadata;
|
|
34
|
+
const url = (_a2 = meta == null ? void 0 : meta.avatar_url) == null ? void 0 : _a2.trim();
|
|
35
|
+
return url || null;
|
|
30
36
|
};
|
|
31
|
-
var getDisplayNameFromUser = getDisplayNameFromUser2;
|
|
37
|
+
var getDisplayNameFromUser = getDisplayNameFromUser2, getAvatarUrlFromUser = getAvatarUrlFromUser2;
|
|
32
38
|
const response = await fetch(`${apiBaseUrl}/auth/config`);
|
|
33
39
|
if (!response.ok) {
|
|
34
40
|
throw new Error(`Failed to load auth config: ${response.status}`);
|
|
@@ -48,16 +54,19 @@ function AuthProvider(props) {
|
|
|
48
54
|
setUser({ id: session.user.id, email: session.user.email });
|
|
49
55
|
setAccessToken(session.access_token);
|
|
50
56
|
setDisplayNameState(getDisplayNameFromUser2(session.user));
|
|
57
|
+
setAvatarUrl(getAvatarUrlFromUser2(session.user));
|
|
51
58
|
}
|
|
52
59
|
client.auth.onAuthStateChange((_event, session2) => {
|
|
53
60
|
if (!session2 || !session2.user) {
|
|
54
61
|
setUser(null);
|
|
55
62
|
setAccessToken(null);
|
|
56
63
|
setDisplayNameState("");
|
|
64
|
+
setAvatarUrl(null);
|
|
57
65
|
} else {
|
|
58
66
|
setUser({ id: session2.user.id, email: session2.user.email });
|
|
59
67
|
setAccessToken(session2.access_token);
|
|
60
68
|
setDisplayNameState(getDisplayNameFromUser2(session2.user));
|
|
69
|
+
setAvatarUrl(getAvatarUrlFromUser2(session2.user));
|
|
61
70
|
}
|
|
62
71
|
});
|
|
63
72
|
} catch (err) {
|
|
@@ -102,6 +111,7 @@ function AuthProvider(props) {
|
|
|
102
111
|
setUser(null);
|
|
103
112
|
setAccessToken(null);
|
|
104
113
|
setDisplayNameState("");
|
|
114
|
+
setAvatarUrl(null);
|
|
105
115
|
}, [supabase]);
|
|
106
116
|
const value = useMemo(
|
|
107
117
|
() => ({
|
|
@@ -110,6 +120,7 @@ function AuthProvider(props) {
|
|
|
110
120
|
accessToken,
|
|
111
121
|
isReady,
|
|
112
122
|
displayName,
|
|
123
|
+
avatarUrl,
|
|
113
124
|
signInWithEmail,
|
|
114
125
|
signInWithGitHub,
|
|
115
126
|
signOut
|
|
@@ -120,6 +131,7 @@ function AuthProvider(props) {
|
|
|
120
131
|
accessToken,
|
|
121
132
|
isReady,
|
|
122
133
|
displayName,
|
|
134
|
+
avatarUrl,
|
|
123
135
|
signInWithEmail,
|
|
124
136
|
signInWithGitHub,
|
|
125
137
|
signOut
|
|
@@ -134,6 +146,49 @@ function useAuthInternal() {
|
|
|
134
146
|
}
|
|
135
147
|
return ctx;
|
|
136
148
|
}
|
|
149
|
+
|
|
150
|
+
// src/selector.ts
|
|
151
|
+
function getSelector(element, root) {
|
|
152
|
+
if (element === root) return ":scope";
|
|
153
|
+
const id = element.id;
|
|
154
|
+
if (id && /^[a-zA-Z][\w-]*$/.test(id) && !id.toLowerCase().startsWith("radix")) {
|
|
155
|
+
try {
|
|
156
|
+
const matches = root.querySelectorAll(`#${escapeSelectorId(id)}`);
|
|
157
|
+
if (matches.length === 1 && matches[0] === element) return `#${escapeSelectorId(id)}`;
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const path = [];
|
|
162
|
+
let current = element;
|
|
163
|
+
while (current && current !== root) {
|
|
164
|
+
let selector = current.tagName.toLowerCase();
|
|
165
|
+
if (current.id && /^[a-zA-Z][\w-]*$/.test(current.id)) {
|
|
166
|
+
selector += `#${escapeSelectorId(current.id)}`;
|
|
167
|
+
path.unshift(selector);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const parent = current.parentElement;
|
|
171
|
+
if (!parent) break;
|
|
172
|
+
const siblings = Array.from(parent.children).filter(
|
|
173
|
+
(el) => el.tagName === current.tagName
|
|
174
|
+
);
|
|
175
|
+
const index = siblings.indexOf(current);
|
|
176
|
+
if (siblings.length > 1) selector += `:nth-of-type(${index + 1})`;
|
|
177
|
+
path.unshift(selector);
|
|
178
|
+
current = parent;
|
|
179
|
+
}
|
|
180
|
+
return path.join(" > ");
|
|
181
|
+
}
|
|
182
|
+
function escapeSelectorId(id) {
|
|
183
|
+
return CSS.escape(id);
|
|
184
|
+
}
|
|
185
|
+
function isElementVisible(el) {
|
|
186
|
+
const rect = el.getBoundingClientRect();
|
|
187
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
188
|
+
const style = window.getComputedStyle(el);
|
|
189
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
137
192
|
var CommentsContext = createContext(void 0);
|
|
138
193
|
function CommentsProvider(props) {
|
|
139
194
|
var _a;
|
|
@@ -146,6 +201,7 @@ function CommentsProvider(props) {
|
|
|
146
201
|
const [commentModeEnabled, setCommentModeEnabled] = useState(true);
|
|
147
202
|
const [hoveredRect, setHoveredRect] = useState(null);
|
|
148
203
|
const [pendingAnchor, setPendingAnchor] = useState(null);
|
|
204
|
+
const pendingAnchorElementRef = useRef(null);
|
|
149
205
|
const surfaceRef = useRef(null);
|
|
150
206
|
const surfaceClickHandlerRef = useRef(null);
|
|
151
207
|
const onPinClickRef = useRef(null);
|
|
@@ -171,11 +227,17 @@ function CommentsProvider(props) {
|
|
|
171
227
|
}
|
|
172
228
|
const data = await response.json();
|
|
173
229
|
if (cancelled) return;
|
|
174
|
-
const mapped = (_b = (_a2 = data.threads) == null ? void 0 : _a2.map((t) =>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
230
|
+
const mapped = (_b = (_a2 = data.threads) == null ? void 0 : _a2.map((t) => {
|
|
231
|
+
var _a3, _b2;
|
|
232
|
+
return {
|
|
233
|
+
id: t.id,
|
|
234
|
+
anchor: { x: t.anchorX, y: t.anchorY },
|
|
235
|
+
anchorSelector: (_a3 = t.anchorSelector) != null ? _a3 : null,
|
|
236
|
+
anchorRelative: typeof t.anchorRelativeX === "number" && typeof t.anchorRelativeY === "number" ? { x: t.anchorRelativeX, y: t.anchorRelativeY } : null,
|
|
237
|
+
status: "open",
|
|
238
|
+
firstCommentAuthorAvatarUrl: (_b2 = t.firstCommentAuthorAvatarUrl) != null ? _b2 : null
|
|
239
|
+
};
|
|
240
|
+
})) != null ? _b : [];
|
|
179
241
|
setThreads(mapped);
|
|
180
242
|
} catch (err) {
|
|
181
243
|
if (cancelled) return;
|
|
@@ -193,11 +255,30 @@ function CommentsProvider(props) {
|
|
|
193
255
|
};
|
|
194
256
|
}, [apiBaseUrl, config.projectSlug, config.surfaceId, accessToken, isAuthReady]);
|
|
195
257
|
const createThread = useCallback(
|
|
196
|
-
async (anchor, initialCommentBody, initialAuthorName) => {
|
|
258
|
+
async (anchor, initialCommentBody, initialAuthorName, anchorElement, initialAuthorAvatarUrl) => {
|
|
259
|
+
var _a2, _b, _c;
|
|
260
|
+
const root = surfaceRef.current;
|
|
261
|
+
const anchorSelector = root && anchorElement && root.contains(anchorElement) ? getSelector(anchorElement, root) : null;
|
|
262
|
+
let anchorRelative = null;
|
|
263
|
+
if (root && anchorElement && anchorSelector) {
|
|
264
|
+
const surfaceRect = root.getBoundingClientRect();
|
|
265
|
+
const elRect = anchorElement.getBoundingClientRect();
|
|
266
|
+
if (elRect.width > 0 && elRect.height > 0) {
|
|
267
|
+
const clickX = surfaceRect.left + anchor.x * surfaceRect.width;
|
|
268
|
+
const clickY = surfaceRect.top + anchor.y * surfaceRect.height;
|
|
269
|
+
anchorRelative = {
|
|
270
|
+
x: Math.max(0, Math.min(1, (clickX - elRect.left) / elRect.width)),
|
|
271
|
+
y: Math.max(0, Math.min(1, (clickY - elRect.top) / elRect.height))
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
197
275
|
const optimisticThread = {
|
|
198
276
|
id: `${Date.now()}`,
|
|
199
277
|
anchor,
|
|
200
|
-
|
|
278
|
+
anchorSelector: anchorSelector != null ? anchorSelector : void 0,
|
|
279
|
+
anchorRelative: anchorRelative != null ? anchorRelative : void 0,
|
|
280
|
+
status: "open",
|
|
281
|
+
firstCommentAuthorAvatarUrl: initialAuthorAvatarUrl != null ? initialAuthorAvatarUrl : null
|
|
201
282
|
};
|
|
202
283
|
setThreads((prev) => [...prev, optimisticThread]);
|
|
203
284
|
try {
|
|
@@ -214,8 +295,12 @@ function CommentsProvider(props) {
|
|
|
214
295
|
surfaceId: config.surfaceId,
|
|
215
296
|
anchorX: anchor.x,
|
|
216
297
|
anchorY: anchor.y,
|
|
298
|
+
anchorSelector: anchorSelector != null ? anchorSelector : null,
|
|
299
|
+
anchorRelativeX: (_a2 = anchorRelative == null ? void 0 : anchorRelative.x) != null ? _a2 : null,
|
|
300
|
+
anchorRelativeY: (_b = anchorRelative == null ? void 0 : anchorRelative.y) != null ? _b : null,
|
|
217
301
|
initialCommentBody: initialCommentBody != null ? initialCommentBody : null,
|
|
218
|
-
initialAuthorName: initialAuthorName != null ? initialAuthorName : null
|
|
302
|
+
initialAuthorName: initialAuthorName != null ? initialAuthorName : null,
|
|
303
|
+
initialAuthorAvatarUrl: initialAuthorAvatarUrl != null ? initialAuthorAvatarUrl : null
|
|
219
304
|
})
|
|
220
305
|
});
|
|
221
306
|
if (!response.ok) {
|
|
@@ -224,7 +309,8 @@ function CommentsProvider(props) {
|
|
|
224
309
|
const data = await response.json();
|
|
225
310
|
const persistedThread = {
|
|
226
311
|
...optimisticThread,
|
|
227
|
-
id: data.id
|
|
312
|
+
id: data.id,
|
|
313
|
+
firstCommentAuthorAvatarUrl: (_c = initialAuthorAvatarUrl != null ? initialAuthorAvatarUrl : optimisticThread.firstCommentAuthorAvatarUrl) != null ? _c : null
|
|
228
314
|
};
|
|
229
315
|
setThreads(
|
|
230
316
|
(prev) => prev.map((t) => t === optimisticThread ? persistedThread : t)
|
|
@@ -258,13 +344,14 @@ function CommentsProvider(props) {
|
|
|
258
344
|
}
|
|
259
345
|
const data = await response.json();
|
|
260
346
|
const mapped = (_b = (_a2 = data.comments) == null ? void 0 : _a2.map((c) => {
|
|
261
|
-
var _a3;
|
|
347
|
+
var _a3, _b2;
|
|
262
348
|
return {
|
|
263
349
|
id: c.id,
|
|
264
350
|
threadId: c.threadId,
|
|
265
351
|
body: c.body,
|
|
266
352
|
createdAt: c.createdAt,
|
|
267
|
-
authorName: (_a3 = c.authorName) != null ? _a3 : null
|
|
353
|
+
authorName: (_a3 = c.authorName) != null ? _a3 : null,
|
|
354
|
+
authorAvatarUrl: (_b2 = c.authorAvatarUrl) != null ? _b2 : null
|
|
268
355
|
};
|
|
269
356
|
})) != null ? _b : [];
|
|
270
357
|
setCommentsByThread((prev) => ({
|
|
@@ -280,7 +367,7 @@ function CommentsProvider(props) {
|
|
|
280
367
|
[apiBaseUrl, accessToken]
|
|
281
368
|
);
|
|
282
369
|
const addComment = useCallback(
|
|
283
|
-
async (threadId, body, authorName) => {
|
|
370
|
+
async (threadId, body, authorName, authorAvatarUrl) => {
|
|
284
371
|
const trimmed = body.trim();
|
|
285
372
|
if (!trimmed) {
|
|
286
373
|
throw new Error("Comment body is empty");
|
|
@@ -289,7 +376,8 @@ function CommentsProvider(props) {
|
|
|
289
376
|
id: `${Date.now()}`,
|
|
290
377
|
threadId,
|
|
291
378
|
body: trimmed,
|
|
292
|
-
authorName: authorName != null ? authorName : null
|
|
379
|
+
authorName: authorName != null ? authorName : null,
|
|
380
|
+
authorAvatarUrl: authorAvatarUrl != null ? authorAvatarUrl : null
|
|
293
381
|
};
|
|
294
382
|
setCommentsByThread((prev) => {
|
|
295
383
|
var _a2;
|
|
@@ -305,7 +393,7 @@ function CommentsProvider(props) {
|
|
|
305
393
|
"Content-Type": "application/json",
|
|
306
394
|
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
307
395
|
},
|
|
308
|
-
body: JSON.stringify({ threadId, body: trimmed, authorName })
|
|
396
|
+
body: JSON.stringify({ threadId, body: trimmed, authorName, authorAvatarUrl: authorAvatarUrl != null ? authorAvatarUrl : null })
|
|
309
397
|
});
|
|
310
398
|
if (!response.ok) {
|
|
311
399
|
throw new Error(`Failed to create comment: ${response.status}`);
|
|
@@ -315,7 +403,9 @@ function CommentsProvider(props) {
|
|
|
315
403
|
id: data.id,
|
|
316
404
|
threadId,
|
|
317
405
|
body: trimmed,
|
|
318
|
-
createdAt: data.createdAt
|
|
406
|
+
createdAt: data.createdAt,
|
|
407
|
+
authorName: authorName != null ? authorName : null,
|
|
408
|
+
authorAvatarUrl: authorAvatarUrl != null ? authorAvatarUrl : null
|
|
319
409
|
};
|
|
320
410
|
setCommentsByThread((prev) => {
|
|
321
411
|
var _a2;
|
|
@@ -382,6 +472,7 @@ function CommentsProvider(props) {
|
|
|
382
472
|
surfaceClickHandlerRef,
|
|
383
473
|
pendingAnchor,
|
|
384
474
|
setPendingAnchor,
|
|
475
|
+
pendingAnchorElementRef,
|
|
385
476
|
onPinClickRef
|
|
386
477
|
}),
|
|
387
478
|
[
|
|
@@ -428,22 +519,33 @@ function useComments() {
|
|
|
428
519
|
deleteThread: ctx.deleteThread
|
|
429
520
|
};
|
|
430
521
|
}
|
|
431
|
-
var
|
|
522
|
+
var PIN_SIZE = 28;
|
|
523
|
+
var PIN_BASE_STYLE = {
|
|
432
524
|
position: "absolute",
|
|
433
525
|
transform: "translate(-50%, -50%)",
|
|
434
|
-
width:
|
|
435
|
-
height:
|
|
526
|
+
width: PIN_SIZE,
|
|
527
|
+
height: PIN_SIZE,
|
|
436
528
|
borderRadius: "999px",
|
|
437
|
-
background: "#f97316",
|
|
438
|
-
border: "2px solid #fff",
|
|
439
|
-
boxShadow: "0 4px 10px rgba(0,0,0,0.25)",
|
|
440
529
|
display: "flex",
|
|
441
530
|
alignItems: "center",
|
|
442
531
|
justifyContent: "center",
|
|
532
|
+
cursor: "pointer"
|
|
533
|
+
};
|
|
534
|
+
var PIN_DOT_STYLE = {
|
|
535
|
+
...PIN_BASE_STYLE,
|
|
536
|
+
background: "#f97316",
|
|
537
|
+
border: "2px solid #fff",
|
|
538
|
+
boxShadow: "0 4px 10px rgba(0,0,0,0.25)",
|
|
443
539
|
color: "#fff",
|
|
444
540
|
fontSize: 10,
|
|
445
|
-
fontWeight: 600
|
|
446
|
-
|
|
541
|
+
fontWeight: 600
|
|
542
|
+
};
|
|
543
|
+
var PIN_AVATAR_STYLE = {
|
|
544
|
+
...PIN_BASE_STYLE,
|
|
545
|
+
border: "2px solid #fff",
|
|
546
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
|
|
547
|
+
overflow: "hidden",
|
|
548
|
+
backgroundColor: "#e5e7eb"
|
|
447
549
|
};
|
|
448
550
|
function CommentSurface(props) {
|
|
449
551
|
const {
|
|
@@ -455,6 +557,67 @@ function CommentSurface(props) {
|
|
|
455
557
|
onPinClickRef
|
|
456
558
|
} = useCommentsInternal();
|
|
457
559
|
const { threads } = useComments();
|
|
560
|
+
const [resolvedPinPositions, setResolvedPinPositions] = useState({});
|
|
561
|
+
const recalcPinPositions = useCallback(() => {
|
|
562
|
+
const root = surfaceRef.current;
|
|
563
|
+
if (!root) return;
|
|
564
|
+
const surfaceRect = root.getBoundingClientRect();
|
|
565
|
+
if (surfaceRect.width === 0 && surfaceRect.height === 0) return;
|
|
566
|
+
const next = {};
|
|
567
|
+
threads.forEach((thread) => {
|
|
568
|
+
if (thread.anchorSelector) {
|
|
569
|
+
try {
|
|
570
|
+
const el = root.querySelector(thread.anchorSelector);
|
|
571
|
+
if (!el || !(el instanceof HTMLElement)) {
|
|
572
|
+
next[thread.id] = "hide";
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (!isElementVisible(el)) {
|
|
576
|
+
next[thread.id] = "hide";
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const elRect = el.getBoundingClientRect();
|
|
580
|
+
const rel = thread.anchorRelative;
|
|
581
|
+
const rx = rel && typeof rel.x === "number" ? rel.x : 0.5;
|
|
582
|
+
const ry = rel && typeof rel.y === "number" ? rel.y : 0.5;
|
|
583
|
+
const left = (elRect.left - surfaceRect.left + rx * elRect.width) / surfaceRect.width;
|
|
584
|
+
const top = (elRect.top - surfaceRect.top + ry * elRect.height) / surfaceRect.height;
|
|
585
|
+
next[thread.id] = { left, top };
|
|
586
|
+
} catch {
|
|
587
|
+
next[thread.id] = "hide";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
setResolvedPinPositions(next);
|
|
592
|
+
}, [threads, surfaceRef]);
|
|
593
|
+
useLayoutEffect(() => {
|
|
594
|
+
recalcPinPositions();
|
|
595
|
+
}, [recalcPinPositions]);
|
|
596
|
+
useLayoutEffect(() => {
|
|
597
|
+
const root = surfaceRef.current;
|
|
598
|
+
if (!root) return;
|
|
599
|
+
const scrollables = [root];
|
|
600
|
+
const walk = (el) => {
|
|
601
|
+
if (el === root) return;
|
|
602
|
+
const style = window.getComputedStyle(el);
|
|
603
|
+
const overflow = style.overflow + style.overflowX + style.overflowY;
|
|
604
|
+
if (overflow.includes("scroll") || overflow.includes("auto")) {
|
|
605
|
+
scrollables.push(el);
|
|
606
|
+
}
|
|
607
|
+
if (el instanceof HTMLElement && el.children.length) {
|
|
608
|
+
Array.from(el.children).forEach(walk);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
walk(root);
|
|
612
|
+
const handleScroll = () => recalcPinPositions();
|
|
613
|
+
scrollables.forEach((el) => el.addEventListener("scroll", handleScroll, { passive: true }));
|
|
614
|
+
const ro = new ResizeObserver(handleScroll);
|
|
615
|
+
ro.observe(root);
|
|
616
|
+
return () => {
|
|
617
|
+
scrollables.forEach((el) => el.removeEventListener("scroll", handleScroll));
|
|
618
|
+
ro.disconnect();
|
|
619
|
+
};
|
|
620
|
+
}, [recalcPinPositions, surfaceRef]);
|
|
458
621
|
const handleMouseMove = useCallback(
|
|
459
622
|
(e) => {
|
|
460
623
|
if (!commentModeEnabled) return;
|
|
@@ -506,42 +669,58 @@ function CommentSurface(props) {
|
|
|
506
669
|
zIndex: 1
|
|
507
670
|
},
|
|
508
671
|
children: [
|
|
509
|
-
threads.map((thread) =>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
672
|
+
threads.map((thread) => {
|
|
673
|
+
const resolved = resolvedPinPositions[thread.id];
|
|
674
|
+
if (resolved === "hide") return null;
|
|
675
|
+
const left = typeof resolved === "object" ? resolved.left : thread.anchor.x;
|
|
676
|
+
const top = typeof resolved === "object" ? resolved.top : thread.anchor.y;
|
|
677
|
+
const pinStyle = {
|
|
678
|
+
...thread.firstCommentAuthorAvatarUrl ? PIN_AVATAR_STYLE : PIN_DOT_STYLE,
|
|
679
|
+
left: `${left * 100}%`,
|
|
680
|
+
top: `${top * 100}%`,
|
|
681
|
+
pointerEvents: "auto"
|
|
682
|
+
};
|
|
683
|
+
return /* @__PURE__ */ jsx(
|
|
684
|
+
"div",
|
|
685
|
+
{
|
|
686
|
+
"data-commentator-pin": true,
|
|
687
|
+
role: "button",
|
|
688
|
+
tabIndex: 0,
|
|
689
|
+
onClick: (e) => {
|
|
690
|
+
var _a;
|
|
524
691
|
e.stopPropagation();
|
|
525
|
-
(_a = onPinClickRef.current) == null ? void 0 : _a.call(onPinClickRef, thread.id,
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
692
|
+
(_a = onPinClickRef.current) == null ? void 0 : _a.call(onPinClickRef, thread.id, e.clientX, e.clientY);
|
|
693
|
+
},
|
|
694
|
+
onKeyDown: (e) => {
|
|
695
|
+
var _a;
|
|
696
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
697
|
+
e.preventDefault();
|
|
698
|
+
e.stopPropagation();
|
|
699
|
+
(_a = onPinClickRef.current) == null ? void 0 : _a.call(onPinClickRef, thread.id, 0, 0);
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
style: pinStyle,
|
|
703
|
+
children: thread.firstCommentAuthorAvatarUrl ? /* @__PURE__ */ jsx(
|
|
704
|
+
"img",
|
|
705
|
+
{
|
|
706
|
+
src: thread.firstCommentAuthorAvatarUrl,
|
|
707
|
+
alt: "",
|
|
708
|
+
width: PIN_SIZE,
|
|
709
|
+
height: PIN_SIZE,
|
|
710
|
+
style: { display: "block", width: PIN_SIZE, height: PIN_SIZE, objectFit: "cover" }
|
|
711
|
+
}
|
|
712
|
+
) : "\u25CF"
|
|
533
713
|
},
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
)),
|
|
714
|
+
thread.id
|
|
715
|
+
);
|
|
716
|
+
}),
|
|
538
717
|
pendingAnchor && /* @__PURE__ */ jsx(
|
|
539
718
|
"div",
|
|
540
719
|
{
|
|
541
720
|
"aria-hidden": true,
|
|
542
721
|
"data-commentator-pin": true,
|
|
543
722
|
style: {
|
|
544
|
-
...
|
|
723
|
+
...PIN_DOT_STYLE,
|
|
545
724
|
left: `${pendingAnchor.x * 100}%`,
|
|
546
725
|
top: `${pendingAnchor.y * 100}%`,
|
|
547
726
|
cursor: "default",
|
|
@@ -567,11 +746,12 @@ function CommentSettings() {
|
|
|
567
746
|
const {
|
|
568
747
|
user,
|
|
569
748
|
displayName,
|
|
749
|
+
avatarUrl,
|
|
570
750
|
signOut,
|
|
571
751
|
isReady: isAuthReady,
|
|
572
752
|
signInWithGitHub
|
|
573
753
|
} = useCommentAuth();
|
|
574
|
-
return /* @__PURE__ */
|
|
754
|
+
return /* @__PURE__ */ jsx(
|
|
575
755
|
"div",
|
|
576
756
|
{
|
|
577
757
|
style: {
|
|
@@ -583,90 +763,79 @@ function CommentSettings() {
|
|
|
583
763
|
flexDirection: "column",
|
|
584
764
|
gap: 8
|
|
585
765
|
},
|
|
586
|
-
children:
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
{
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
),
|
|
608
|
-
user && /* @__PURE__ */ jsx(
|
|
609
|
-
"button",
|
|
610
|
-
{
|
|
611
|
-
type: "button",
|
|
612
|
-
onClick: () => signOut().catch(() => {
|
|
613
|
-
}),
|
|
614
|
-
style: {
|
|
615
|
-
borderRadius: 999,
|
|
616
|
-
border: "1px solid #e5e7eb",
|
|
617
|
-
backgroundColor: "#ffffff",
|
|
618
|
-
padding: "4px 10px",
|
|
619
|
-
fontSize: 11,
|
|
620
|
-
color: "#374151",
|
|
621
|
-
cursor: "pointer"
|
|
622
|
-
},
|
|
623
|
-
children: "Sign out"
|
|
766
|
+
children: user ? /* @__PURE__ */ jsxs(
|
|
767
|
+
"div",
|
|
768
|
+
{
|
|
769
|
+
style: {
|
|
770
|
+
display: "flex",
|
|
771
|
+
alignItems: "center",
|
|
772
|
+
gap: 8,
|
|
773
|
+
fontSize: 12,
|
|
774
|
+
color: "#6b7280"
|
|
775
|
+
},
|
|
776
|
+
children: [
|
|
777
|
+
avatarUrl && /* @__PURE__ */ jsx(
|
|
778
|
+
"img",
|
|
779
|
+
{
|
|
780
|
+
src: avatarUrl,
|
|
781
|
+
alt: "",
|
|
782
|
+
width: 24,
|
|
783
|
+
height: 24,
|
|
784
|
+
style: {
|
|
785
|
+
borderRadius: "50%",
|
|
786
|
+
flexShrink: 0
|
|
624
787
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
788
|
+
}
|
|
789
|
+
),
|
|
790
|
+
/* @__PURE__ */ jsx("span", { children: displayName || user.email || "unknown user" }),
|
|
791
|
+
/* @__PURE__ */ jsx(
|
|
792
|
+
"button",
|
|
793
|
+
{
|
|
794
|
+
type: "button",
|
|
795
|
+
onClick: () => signOut().catch(() => {
|
|
796
|
+
}),
|
|
797
|
+
style: {
|
|
798
|
+
borderRadius: 999,
|
|
799
|
+
border: "1px solid #e5e7eb",
|
|
800
|
+
backgroundColor: "#ffffff",
|
|
801
|
+
padding: "4px 10px",
|
|
802
|
+
fontSize: 11,
|
|
803
|
+
color: "#374151",
|
|
804
|
+
cursor: "pointer"
|
|
805
|
+
},
|
|
806
|
+
children: "Sign out"
|
|
807
|
+
}
|
|
808
|
+
)
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
) : /* @__PURE__ */ jsx("div", { style: { marginTop: 4 }, children: /* @__PURE__ */ jsxs(
|
|
812
|
+
"button",
|
|
813
|
+
{
|
|
814
|
+
type: "button",
|
|
815
|
+
onClick: () => signInWithGitHub().catch(() => {
|
|
816
|
+
}),
|
|
817
|
+
disabled: !isAuthReady,
|
|
818
|
+
style: {
|
|
819
|
+
width: "100%",
|
|
820
|
+
padding: "8px 12px",
|
|
821
|
+
borderRadius: 10,
|
|
822
|
+
border: "1px solid #e5e7eb",
|
|
823
|
+
backgroundColor: "#24292f",
|
|
824
|
+
color: "#fff",
|
|
825
|
+
fontSize: 13,
|
|
826
|
+
fontWeight: 500,
|
|
827
|
+
cursor: isAuthReady ? "pointer" : "default",
|
|
828
|
+
display: "flex",
|
|
829
|
+
alignItems: "center",
|
|
830
|
+
justifyContent: "center",
|
|
831
|
+
gap: 8
|
|
832
|
+
},
|
|
833
|
+
children: [
|
|
834
|
+
/* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" }) }),
|
|
835
|
+
"Sign in with GitHub"
|
|
836
|
+
]
|
|
837
|
+
}
|
|
838
|
+
) })
|
|
670
839
|
}
|
|
671
840
|
);
|
|
672
841
|
}
|
|
@@ -714,13 +883,15 @@ function CommentOverlay(props) {
|
|
|
714
883
|
surfaceClickHandlerRef,
|
|
715
884
|
pendingAnchor,
|
|
716
885
|
setPendingAnchor,
|
|
886
|
+
pendingAnchorElementRef,
|
|
717
887
|
onPinClickRef
|
|
718
888
|
} = useCommentsInternal();
|
|
719
889
|
const {
|
|
720
890
|
user,
|
|
721
891
|
isReady: isAuthReady,
|
|
722
892
|
signInWithGitHub,
|
|
723
|
-
displayName
|
|
893
|
+
displayName,
|
|
894
|
+
avatarUrl
|
|
724
895
|
} = useCommentAuth();
|
|
725
896
|
const [activeThreadId, setActiveThreadId] = useState(null);
|
|
726
897
|
const [draft, setDraft] = useState("");
|
|
@@ -741,18 +912,19 @@ function CommentOverlay(props) {
|
|
|
741
912
|
}, [activeThreadId, pendingAnchor, commentsByThread, loadComments]);
|
|
742
913
|
const handleSurfaceClick = useCallback(
|
|
743
914
|
(event) => {
|
|
744
|
-
var _a2;
|
|
915
|
+
var _a2, _b;
|
|
745
916
|
setShowSettings(false);
|
|
746
917
|
const rect = (_a2 = surfaceRef.current) == null ? void 0 : _a2.getBoundingClientRect();
|
|
747
918
|
if (!rect) return;
|
|
748
919
|
const x = (event.clientX - rect.left) / rect.width;
|
|
749
920
|
const y = (event.clientY - rect.top) / rect.height;
|
|
750
921
|
setPendingAnchor({ x, y });
|
|
922
|
+
pendingAnchorElementRef.current = (_b = event.target) != null ? _b : null;
|
|
751
923
|
setActiveThreadId(null);
|
|
752
924
|
setDraft("");
|
|
753
925
|
setPanelPosition(computePanelPosition(event.clientX, event.clientY));
|
|
754
926
|
},
|
|
755
|
-
[setPendingAnchor, surfaceRef]
|
|
927
|
+
[setPendingAnchor, surfaceRef, pendingAnchorElementRef]
|
|
756
928
|
);
|
|
757
929
|
useEffect(() => {
|
|
758
930
|
surfaceClickHandlerRef.current = handleSurfaceClick;
|
|
@@ -780,6 +952,19 @@ function CommentOverlay(props) {
|
|
|
780
952
|
setDraft("");
|
|
781
953
|
setPanelPosition(null);
|
|
782
954
|
}, [commentModeEnabled, setCommentModeEnabled, setHoveredRect, setPendingAnchor]);
|
|
955
|
+
useEffect(() => {
|
|
956
|
+
const onKeyDown = (e) => {
|
|
957
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "m") {
|
|
958
|
+
const target = e.target;
|
|
959
|
+
const isEditable = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
|
|
960
|
+
if (isEditable) return;
|
|
961
|
+
e.preventDefault();
|
|
962
|
+
handleToggle();
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
window.addEventListener("keydown", onKeyDown);
|
|
966
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
967
|
+
}, [handleToggle]);
|
|
783
968
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
784
969
|
/* @__PURE__ */ jsxs(
|
|
785
970
|
"div",
|
|
@@ -843,7 +1028,36 @@ function CommentOverlay(props) {
|
|
|
843
1028
|
fontSize: 14,
|
|
844
1029
|
color: "#4b5563"
|
|
845
1030
|
},
|
|
846
|
-
children:
|
|
1031
|
+
children: /* @__PURE__ */ jsxs(
|
|
1032
|
+
"svg",
|
|
1033
|
+
{
|
|
1034
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1035
|
+
fill: "none",
|
|
1036
|
+
viewBox: "0 0 24 24",
|
|
1037
|
+
strokeWidth: 1.5,
|
|
1038
|
+
stroke: "currentColor",
|
|
1039
|
+
style: { width: 18, height: 18 },
|
|
1040
|
+
"aria-hidden": true,
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsx(
|
|
1043
|
+
"path",
|
|
1044
|
+
{
|
|
1045
|
+
strokeLinecap: "round",
|
|
1046
|
+
strokeLinejoin: "round",
|
|
1047
|
+
d: "M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
|
1048
|
+
}
|
|
1049
|
+
),
|
|
1050
|
+
/* @__PURE__ */ jsx(
|
|
1051
|
+
"path",
|
|
1052
|
+
{
|
|
1053
|
+
strokeLinecap: "round",
|
|
1054
|
+
strokeLinejoin: "round",
|
|
1055
|
+
d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
|
1056
|
+
}
|
|
1057
|
+
)
|
|
1058
|
+
]
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
847
1061
|
}
|
|
848
1062
|
)
|
|
849
1063
|
]
|
|
@@ -892,15 +1106,40 @@ function CommentOverlay(props) {
|
|
|
892
1106
|
marginBottom: 4
|
|
893
1107
|
},
|
|
894
1108
|
children: [
|
|
895
|
-
/* @__PURE__ */
|
|
896
|
-
"
|
|
1109
|
+
/* @__PURE__ */ jsxs(
|
|
1110
|
+
"div",
|
|
897
1111
|
{
|
|
898
1112
|
style: {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1113
|
+
display: "flex",
|
|
1114
|
+
alignItems: "center",
|
|
1115
|
+
gap: 8
|
|
902
1116
|
},
|
|
903
|
-
children:
|
|
1117
|
+
children: [
|
|
1118
|
+
user && avatarUrl && /* @__PURE__ */ jsx(
|
|
1119
|
+
"img",
|
|
1120
|
+
{
|
|
1121
|
+
src: avatarUrl,
|
|
1122
|
+
alt: "",
|
|
1123
|
+
width: 24,
|
|
1124
|
+
height: 24,
|
|
1125
|
+
style: {
|
|
1126
|
+
borderRadius: "50%",
|
|
1127
|
+
flexShrink: 0
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
),
|
|
1131
|
+
/* @__PURE__ */ jsx(
|
|
1132
|
+
"span",
|
|
1133
|
+
{
|
|
1134
|
+
style: {
|
|
1135
|
+
fontSize: 13,
|
|
1136
|
+
fontWeight: 600,
|
|
1137
|
+
color: "#111827"
|
|
1138
|
+
},
|
|
1139
|
+
children: user ? activeThreadId ? "Thread" : "New comment" : "Sign in to comment"
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
]
|
|
904
1143
|
}
|
|
905
1144
|
),
|
|
906
1145
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
@@ -1007,19 +1246,24 @@ function CommentOverlay(props) {
|
|
|
1007
1246
|
"form",
|
|
1008
1247
|
{
|
|
1009
1248
|
onSubmit: async (event) => {
|
|
1249
|
+
var _a2;
|
|
1010
1250
|
event.preventDefault();
|
|
1011
1251
|
if (!draft.trim()) return;
|
|
1012
1252
|
try {
|
|
1013
1253
|
if (activeThreadId) {
|
|
1014
|
-
await addComment(activeThreadId, draft, displayName);
|
|
1254
|
+
await addComment(activeThreadId, draft, displayName, avatarUrl != null ? avatarUrl : null);
|
|
1015
1255
|
} else if (pendingAnchor) {
|
|
1256
|
+
const element = (_a2 = pendingAnchorElementRef.current) != null ? _a2 : void 0;
|
|
1016
1257
|
const thread = await createThread(
|
|
1017
1258
|
pendingAnchor,
|
|
1018
1259
|
draft,
|
|
1019
|
-
displayName || null
|
|
1260
|
+
displayName || null,
|
|
1261
|
+
element,
|
|
1262
|
+
avatarUrl != null ? avatarUrl : null
|
|
1020
1263
|
);
|
|
1021
1264
|
setActiveThreadId(thread.id);
|
|
1022
1265
|
setPendingAnchor(null);
|
|
1266
|
+
pendingAnchorElementRef.current = null;
|
|
1023
1267
|
}
|
|
1024
1268
|
setDraft("");
|
|
1025
1269
|
} catch (err) {
|
|
@@ -1037,6 +1281,7 @@ function CommentOverlay(props) {
|
|
|
1037
1281
|
placeholder: "Add a comment...",
|
|
1038
1282
|
style: {
|
|
1039
1283
|
width: "100%",
|
|
1284
|
+
color: "#000000",
|
|
1040
1285
|
resize: "none",
|
|
1041
1286
|
fontSize: 13,
|
|
1042
1287
|
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
|
|
@@ -1103,7 +1348,27 @@ function CommentOverlay(props) {
|
|
|
1103
1348
|
)
|
|
1104
1349
|
] });
|
|
1105
1350
|
}
|
|
1351
|
+
function Commentator(props) {
|
|
1352
|
+
const {
|
|
1353
|
+
config,
|
|
1354
|
+
position = "right",
|
|
1355
|
+
surfaceStyle,
|
|
1356
|
+
surfaceClassName,
|
|
1357
|
+
children
|
|
1358
|
+
} = props;
|
|
1359
|
+
return /* @__PURE__ */ jsxs(CommentProvider, { config, children: [
|
|
1360
|
+
/* @__PURE__ */ jsx(
|
|
1361
|
+
CommentSurface,
|
|
1362
|
+
{
|
|
1363
|
+
style: { minHeight: "100%", position: "relative", ...surfaceStyle },
|
|
1364
|
+
className: surfaceClassName,
|
|
1365
|
+
children
|
|
1366
|
+
}
|
|
1367
|
+
),
|
|
1368
|
+
/* @__PURE__ */ jsx(CommentOverlay, { position })
|
|
1369
|
+
] });
|
|
1370
|
+
}
|
|
1106
1371
|
|
|
1107
|
-
export { CommentOverlay, CommentProvider, CommentSettings, CommentSurface, useCommentAuth, useComments };
|
|
1372
|
+
export { CommentOverlay, CommentProvider, CommentSettings, CommentSurface, Commentator, useCommentAuth, useComments };
|
|
1108
1373
|
//# sourceMappingURL=index.mjs.map
|
|
1109
1374
|
//# sourceMappingURL=index.mjs.map
|