@flanksource/clicky-ui 0.3.31 → 0.3.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comments/CommentCard.cjs +79 -44
- package/dist/comments/CommentCard.cjs.map +1 -1
- package/dist/comments/CommentCard.d.ts +2 -0
- package/dist/comments/CommentCard.d.ts.map +1 -1
- package/dist/comments/CommentCard.js +81 -46
- package/dist/comments/CommentCard.js.map +1 -1
- package/dist/comments/CommentCardParts.cjs +20 -0
- package/dist/comments/CommentCardParts.cjs.map +1 -1
- package/dist/comments/CommentCardParts.d.ts +4 -0
- package/dist/comments/CommentCardParts.d.ts.map +1 -1
- package/dist/comments/CommentCardParts.js +22 -2
- package/dist/comments/CommentCardParts.js.map +1 -1
- package/dist/comments/CommentProvider.cjs +3 -0
- package/dist/comments/CommentProvider.cjs.map +1 -1
- package/dist/comments/CommentProvider.d.ts +1 -1
- package/dist/comments/CommentProvider.d.ts.map +1 -1
- package/dist/comments/CommentProvider.js +3 -0
- package/dist/comments/CommentProvider.js.map +1 -1
- package/dist/comments/CommentSidePanel.cjs +43 -17
- package/dist/comments/CommentSidePanel.cjs.map +1 -1
- package/dist/comments/CommentSidePanel.d.ts.map +1 -1
- package/dist/comments/CommentSidePanel.js +44 -18
- package/dist/comments/CommentSidePanel.js.map +1 -1
- package/dist/comments/CommentThreadList.cjs +2 -0
- package/dist/comments/CommentThreadList.cjs.map +1 -1
- package/dist/comments/CommentThreadList.d.ts +3 -2
- package/dist/comments/CommentThreadList.d.ts.map +1 -1
- package/dist/comments/CommentThreadList.js +2 -0
- package/dist/comments/CommentThreadList.js.map +1 -1
- package/dist/comments/comment-types.cjs +16 -4
- package/dist/comments/comment-types.cjs.map +1 -1
- package/dist/comments/comment-types.d.ts +11 -1
- package/dist/comments/comment-types.d.ts.map +1 -1
- package/dist/comments/comment-types.js +16 -4
- package/dist/comments/comment-types.js.map +1 -1
- package/dist/comments/comment-utils.cjs +31 -1
- package/dist/comments/comment-utils.cjs.map +1 -1
- package/dist/comments/comment-utils.d.ts +9 -2
- package/dist/comments/comment-utils.d.ts.map +1 -1
- package/dist/comments/comment-utils.js +31 -1
- package/dist/comments/comment-utils.js.map +1 -1
- package/dist/comments.cjs +3 -0
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.js +4 -1
- package/dist/components/EndpointSelector.cjs +6 -6
- package/dist/components/EndpointSelector.cjs.map +1 -1
- package/dist/components/EndpointSelector.d.ts.map +1 -1
- package/dist/components/EndpointSelector.js +6 -6
- package/dist/components/EndpointSelector.js.map +1 -1
- package/dist/components/ErrorWrapper.cjs +3 -2
- package/dist/components/ErrorWrapper.cjs.map +1 -1
- package/dist/components/ErrorWrapper.js +3 -2
- package/dist/components/ErrorWrapper.js.map +1 -1
- package/dist/components/RangeSlider.cjs +108 -45
- package/dist/components/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider.d.ts +3 -1
- package/dist/components/RangeSlider.d.ts.map +1 -1
- package/dist/components/RangeSlider.js +108 -45
- package/dist/components/RangeSlider.js.map +1 -1
- package/dist/components/SecretKeySelector.cjs +4 -4
- package/dist/components/SecretKeySelector.cjs.map +1 -1
- package/dist/components/SecretKeySelector.js +4 -4
- package/dist/components/SecretKeySelector.js.map +1 -1
- package/dist/data/test-runner/status.cjs +1 -1
- package/dist/data/test-runner/status.cjs.map +1 -1
- package/dist/data/test-runner/status.js +1 -1
- package/dist/data/test-runner/status.js.map +1 -1
- package/dist/data/test-runner/types.d.ts +6 -0
- package/dist/data/test-runner/types.d.ts.map +1 -1
- package/dist/data/verification/verifyReportTests.cjs +1 -0
- package/dist/data/verification/verifyReportTests.cjs.map +1 -1
- package/dist/data/verification/verifyReportTests.d.ts.map +1 -1
- package/dist/data/verification/verifyReportTests.js +1 -0
- package/dist/data/verification/verifyReportTests.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { Icon } from "../data/Icon.js";
|
|
|
5
5
|
import { CommentThread } from "./CommentThread.js";
|
|
6
6
|
import { CommentThreadList } from "./CommentThreadList.js";
|
|
7
7
|
import { useCommentContextOptional } from "./comment-context.js";
|
|
8
|
-
import {
|
|
8
|
+
import { getRoots, resolveCommentStage, selectCommentThreadsByStage, buildThreadListHandlers, selectAnchorThreads, buildReplyMap, sortReplies } from "./comment-utils.js";
|
|
9
9
|
import { DOCUMENT_ANCHOR } from "./comment-types.js";
|
|
10
10
|
import { UiComment } from "../icons/components/UiComment.js";
|
|
11
11
|
function defaultAnchorLabel(anchor) {
|
|
@@ -186,7 +186,7 @@ function CommentSidePanel(props) {
|
|
|
186
186
|
const railRef = useRef(null);
|
|
187
187
|
const focusedRef = useRef(null);
|
|
188
188
|
const [focusedOffset, setFocusedOffset] = useState(null);
|
|
189
|
-
const [
|
|
189
|
+
const [stage, setStage] = useState("active");
|
|
190
190
|
const focusedAnchor = (ctx == null ? void 0 : ctx.railMode) === "focused" ? ctx.focusedAnchor : null;
|
|
191
191
|
useEffect(() => {
|
|
192
192
|
if (!ctx || props.focusedAlignment !== "anchor" || !focusedAnchor) {
|
|
@@ -221,12 +221,21 @@ function CommentSidePanel(props) {
|
|
|
221
221
|
};
|
|
222
222
|
}, [ctx, focusedAnchor, props.focusedAlignment]);
|
|
223
223
|
if (!ctx) return null;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
const counts = getRoots(ctx.comments).reduce(
|
|
225
|
+
(result, root) => {
|
|
226
|
+
const rootStage = resolveCommentStage(ctx.config, root.status);
|
|
227
|
+
if (rootStage) result[rootStage] += 1;
|
|
228
|
+
return result;
|
|
229
|
+
},
|
|
230
|
+
{ active: 0, resolved: 0, closed: 0 }
|
|
231
|
+
);
|
|
232
|
+
const visible = selectCommentThreadsByStage(ctx.comments, ctx.config, stage);
|
|
233
|
+
if (ctx.railMode === "closed" && counts.active + counts.resolved + counts.closed === 0)
|
|
229
234
|
return null;
|
|
235
|
+
function selectStage(next) {
|
|
236
|
+
setStage(next);
|
|
237
|
+
if ((ctx == null ? void 0 : ctx.railMode) !== "all") ctx == null ? void 0 : ctx.openCommentList();
|
|
238
|
+
}
|
|
230
239
|
return /* @__PURE__ */ jsxs(
|
|
231
240
|
"aside",
|
|
232
241
|
{
|
|
@@ -240,28 +249,45 @@ function CommentSidePanel(props) {
|
|
|
240
249
|
"data-testid": "comment-rail-header",
|
|
241
250
|
className: "sticky top-0 z-20 flex items-center gap-2 bg-background py-2",
|
|
242
251
|
children: [
|
|
243
|
-
(
|
|
252
|
+
(counts.active > 0 || ctx.railMode === "all") && /* @__PURE__ */ jsx(
|
|
244
253
|
RailToggle,
|
|
245
254
|
{
|
|
246
|
-
active: ctx.railMode === "all",
|
|
247
|
-
onClick:
|
|
255
|
+
active: ctx.railMode === "all" && stage === "active",
|
|
256
|
+
onClick: () => selectStage("active"),
|
|
248
257
|
testId: "comment-open-all",
|
|
249
|
-
children: ctx.railMode === "closed" ? `Open comments (${
|
|
258
|
+
children: ctx.railMode === "closed" ? `Open comments (${counts.active})` : ctx.railMode === "focused" ? `All comments (${counts.active})` : `Open (${counts.active})`
|
|
250
259
|
}
|
|
251
260
|
),
|
|
252
|
-
|
|
261
|
+
counts.resolved > 0 && /* @__PURE__ */ jsxs(
|
|
253
262
|
"button",
|
|
254
263
|
{
|
|
255
264
|
type: "button",
|
|
256
|
-
"aria-pressed":
|
|
257
|
-
onClick: () =>
|
|
265
|
+
"aria-pressed": ctx.railMode === "all" && stage === "resolved",
|
|
266
|
+
onClick: () => selectStage("resolved"),
|
|
258
267
|
className: cn(
|
|
259
268
|
"rounded-full border px-2 py-1 text-xs font-medium transition-colors",
|
|
260
|
-
|
|
269
|
+
ctx.railMode === "all" && stage === "resolved" ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:text-foreground"
|
|
261
270
|
),
|
|
262
271
|
children: [
|
|
263
272
|
"Resolved (",
|
|
264
|
-
|
|
273
|
+
counts.resolved,
|
|
274
|
+
")"
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
),
|
|
278
|
+
counts.closed > 0 && /* @__PURE__ */ jsxs(
|
|
279
|
+
"button",
|
|
280
|
+
{
|
|
281
|
+
type: "button",
|
|
282
|
+
"aria-pressed": ctx.railMode === "all" && stage === "closed",
|
|
283
|
+
onClick: () => selectStage("closed"),
|
|
284
|
+
className: cn(
|
|
285
|
+
"rounded-full border px-2 py-1 text-xs font-medium transition-colors",
|
|
286
|
+
ctx.railMode === "all" && stage === "closed" ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-muted-foreground hover:text-foreground"
|
|
287
|
+
),
|
|
288
|
+
children: [
|
|
289
|
+
"Closed (",
|
|
290
|
+
counts.closed,
|
|
265
291
|
")"
|
|
266
292
|
]
|
|
267
293
|
}
|
|
@@ -284,7 +310,7 @@ function CommentSidePanel(props) {
|
|
|
284
310
|
ctx,
|
|
285
311
|
comments: visible,
|
|
286
312
|
label,
|
|
287
|
-
emptyLabel:
|
|
313
|
+
emptyLabel: stage === "active" ? "No open comments." : stage === "resolved" ? "No resolved comments." : "No closed comments.",
|
|
288
314
|
...props.threadToMarkdown ? { threadToMarkdown: props.threadToMarkdown } : {}
|
|
289
315
|
}
|
|
290
316
|
) : ctx.railMode === "focused" && ctx.focusedAnchor ? /* @__PURE__ */ jsx(
|
|
@@ -298,7 +324,7 @@ function CommentSidePanel(props) {
|
|
|
298
324
|
FocusedComments,
|
|
299
325
|
{
|
|
300
326
|
ctx,
|
|
301
|
-
comments:
|
|
327
|
+
comments: ctx.comments,
|
|
302
328
|
anchor: ctx.focusedAnchor,
|
|
303
329
|
label: label(ctx.focusedAnchor),
|
|
304
330
|
...props.compact !== void 0 ? { compact: props.compact } : {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentSidePanel.js","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n selectAnchorThreads,\n selectUnresolvedThreads,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n /** Serialize one whole thread for Copy and its maximized Markdown tab. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [showResolved, setShowResolved] = useState(false);\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const unresolved = selectUnresolvedThreads(ctx.comments, ctx.config);\n const visible = showResolved ? ctx.comments : unresolved;\n const total = getRoots(visible).length;\n const resolvedCount =\n getRoots(ctx.comments).length - getRoots(unresolved).length;\n if (ctx.railMode === \"closed\" && total === 0 && resolvedCount === 0)\n return null;\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(total > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\"}\n onClick={ctx.openCommentList}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${total})`\n : `All comments (${total})`}\n </RailToggle>\n )}\n {resolvedCount > 0 && (\n <button\n type=\"button\"\n aria-pressed={showResolved}\n onClick={() => setShowResolved((shown) => !shown)}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n showResolved\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({resolvedCount})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={\n resolvedCount > 0 ? \"No open comments.\" : \"No comments yet.\"\n }\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={visible}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentSidePanel.js","sources":["../../src/comments/CommentSidePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type MouseEvent } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiComment } from \"../icons\";\nimport { CommentThread } from \"./CommentThread\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport {\n useCommentContextOptional,\n type CommentContextValue,\n} from \"./comment-context\";\nimport {\n buildThreadListHandlers,\n getRoots,\n resolveCommentStage,\n selectAnchorThreads,\n selectCommentThreadsByStage,\n sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n DOCUMENT_ANCHOR,\n type Comment,\n type CommentAnchor,\n type CommentStatusStage,\n} from \"./comment-types\";\n\nexport type CommentSidePanelProps = {\n /** Explicit label per anchor key. */\n anchorLabels?: Record<CommentAnchor, string>;\n /** Fallback label formatter for anchors without an explicit label. */\n formatAnchorLabel?: (anchor: CommentAnchor) => string;\n compact?: boolean;\n /** Position the focused thread beside its registered content anchor. */\n focusedAlignment?: \"flow\" | \"anchor\";\n className?: string;\n /** Serialize one whole thread for Copy and its maximized Markdown tab. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\nfunction defaultAnchorLabel(anchor: CommentAnchor): string {\n if (anchor === DOCUMENT_ANCHOR) return \"General\";\n return anchor\n .replace(/\\[\\d+\\]/g, \"\")\n .replaceAll(\".\", \" › \")\n .replaceAll(\"_\", \" \")\n .trim();\n}\n\nfunction useAnchorLabel(props: CommentSidePanelProps) {\n return (anchor: CommentAnchor): string =>\n props.anchorLabels?.[anchor] ??\n props.formatAnchorLabel?.(anchor) ??\n defaultAnchorLabel(anchor);\n}\n\nfunction LocationMeta({ label }: { label: string }) {\n return (\n <span\n data-testid=\"comment-location-meta\"\n className=\"inline-flex items-center rounded-full bg-muted px-2 py-1 text-[10px] font-medium text-muted-foreground\"\n >\n {label}\n </span>\n );\n}\n\nfunction RailToggle({\n active,\n onClick,\n children,\n testId,\n}: {\n active?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n testId?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n data-testid={testId}\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium shadow-sm transition-colors\",\n active\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon icon={UiComment} className=\"text-xs\" />\n {children}\n </button>\n );\n}\n\nfunction orderedAnchors(ctx: CommentContextValue): string[] {\n return Object.keys(ctx.commentMeta).sort((a, b) => {\n if (a === DOCUMENT_ANCHOR) return -1;\n if (b === DOCUMENT_ANCHOR) return 1;\n const at = ctx.getAnchorTop(a);\n const bt = ctx.getAnchorTop(b);\n if (at == null && bt == null) return a.localeCompare(b);\n if (at == null) return 1;\n if (bt == null) return -1;\n if (at !== bt) return at - bt;\n return a.localeCompare(b);\n });\n}\n\nfunction orderComments(\n comments: Comment[],\n anchorOrder: Map<string, number>,\n): Comment[] {\n const replyMap = buildReplyMap(comments);\n const roots = getRoots(comments).sort((a, b) => {\n const ai =\n anchorOrder.get(a.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n const bi =\n anchorOrder.get(b.anchor ?? DOCUMENT_ANCHOR) ?? Number.MAX_SAFE_INTEGER;\n if (ai !== bi) return ai - bi;\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n return roots.flatMap((root) => [\n root,\n ...sortReplies(replyMap.get(root.id) ?? []),\n ]);\n}\n\nfunction AllComments({\n ctx,\n comments,\n label,\n emptyLabel,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n label: (a: CommentAnchor) => string;\n emptyLabel: string;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const ordered = useMemo(() => {\n const anchorOrder = new Map(orderedAnchors(ctx).map((a, i) => [a, i]));\n return orderComments(comments, anchorOrder);\n }, [ctx, comments]);\n const handlers = buildThreadListHandlers(ordered, ctx.config, ctx.callbacks);\n\n function activateThread(\n event: MouseEvent<HTMLDivElement>,\n anchor: CommentAnchor,\n ) {\n const target = event.target as Element;\n if (\n target.closest(\n 'button, a, input, textarea, select, [contenteditable=\"true\"]',\n )\n )\n return;\n const roleButton = target.closest('[role=\"button\"]');\n if (roleButton && roleButton !== target.closest(\"[data-comment-kind]\"))\n return;\n if (anchor !== DOCUMENT_ANCHOR) {\n const found = ctx.scrollToAnchor(anchor, {\n behavior: \"smooth\",\n block: \"start\",\n offset: 12,\n });\n if (!found) return;\n }\n ctx.focusAnchor(anchor);\n }\n\n if (ordered.length === 0) {\n return (\n <p className=\"rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground\">\n {emptyLabel}\n </p>\n );\n }\n\n return (\n <div\n data-testid=\"comment-all-rail\"\n className=\"space-y-3 overflow-y-auto pr-1\"\n >\n <CommentThreadList\n comments={ordered}\n config={ctx.config}\n compact\n renderRootMeta={(c) => {\n const anchor = c.anchor ?? DOCUMENT_ANCHOR;\n const available =\n anchor === DOCUMENT_ANCHOR || ctx.getAnchorTop(anchor) != null;\n return (\n <LocationMeta\n label={`${label(anchor)}${available ? \"\" : \" · Unavailable\"}`}\n />\n );\n }}\n getThreadProps={(c) => ({\n \"data-testid\": \"comment-feed-item\",\n onMouseEnter: () =>\n ctx.setHighlightAnchor(c.anchor ?? DOCUMENT_ANCHOR),\n onMouseLeave: () => ctx.setHighlightAnchor(null),\n onClick: (event) =>\n activateThread(event, c.anchor ?? DOCUMENT_ANCHOR),\n })}\n {...handlers}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\nfunction FocusedComments({\n ctx,\n comments: visible,\n anchor,\n label,\n compact,\n threadToMarkdown,\n}: {\n ctx: CommentContextValue;\n comments: Comment[];\n anchor: CommentAnchor;\n label: string;\n compact?: boolean;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n}) {\n const comments = selectAnchorThreads(visible, anchor);\n const hasComments = comments.length > 0;\n return (\n <div className=\"space-y-3\" data-comment-anchor={anchor}>\n <LocationMeta\n label={\n anchor === DOCUMENT_ANCHOR\n ? \"Whole-page comment\"\n : `Attached to ${label}`\n }\n />\n <CommentThread\n comments={comments}\n config={ctx.config}\n anchor={anchor}\n compact={compact ?? false}\n autoFocusComposer={!hasComments}\n defaultExpanded={hasComments}\n composerPlaceholder={\n hasComments ? \"Add another top-level comment…\" : \"Add a comment…\"\n }\n {...ctx.callbacks}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n />\n </div>\n );\n}\n\n/**\n * A controlled comment rail driven by {@link CommentProvider}. Shows a focused\n * thread for the active anchor, the full document feed in anchor order, or a\n * toggle when collapsed. Renders nothing outside a provider or when empty.\n */\nexport function CommentSidePanel(props: CommentSidePanelProps) {\n const ctx = useCommentContextOptional();\n const label = useAnchorLabel(props);\n const railRef = useRef<HTMLElement>(null);\n const focusedRef = useRef<HTMLDivElement>(null);\n const [focusedOffset, setFocusedOffset] = useState<number | null>(null);\n const [stage, setStage] = useState<CommentStatusStage>(\"active\");\n const focusedAnchor = ctx?.railMode === \"focused\" ? ctx.focusedAnchor : null;\n\n useEffect(() => {\n if (!ctx || props.focusedAlignment !== \"anchor\" || !focusedAnchor) {\n setFocusedOffset(null);\n return;\n }\n const content = ctx.contentRef.current;\n const rail = railRef.current;\n const focused = focusedRef.current;\n if (!content || !rail || !focused) return;\n\n const update = () => {\n const anchorTop = ctx.getAnchorTop(focusedAnchor);\n if (anchorTop == null) {\n setFocusedOffset(null);\n return;\n }\n const desiredTop = anchorTop + content.getBoundingClientRect().top;\n const currentOffset = Number.parseFloat(focused.style.top) || 0;\n const nextOffset =\n currentOffset + desiredTop - focused.getBoundingClientRect().top;\n setFocusedOffset(Math.max(0, nextOffset));\n };\n update();\n content.addEventListener(\"scroll\", update, { passive: true });\n window.addEventListener(\"resize\", update);\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n observer?.observe(content);\n observer?.observe(rail);\n return () => {\n content.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n observer?.disconnect();\n };\n }, [ctx, focusedAnchor, props.focusedAlignment]);\n\n if (!ctx) return null;\n\n // Threads, not cards: a reply is part of its root, never a separate entry.\n const counts = getRoots(ctx.comments).reduce(\n (result, root) => {\n const rootStage = resolveCommentStage(ctx.config, root.status);\n if (rootStage) result[rootStage] += 1;\n return result;\n },\n { active: 0, resolved: 0, closed: 0 },\n );\n const visible = selectCommentThreadsByStage(ctx.comments, ctx.config, stage);\n if (\n ctx.railMode === \"closed\" &&\n counts.active + counts.resolved + counts.closed === 0\n )\n return null;\n\n function selectStage(next: CommentStatusStage) {\n setStage(next);\n if (ctx?.railMode !== \"all\") ctx?.openCommentList();\n }\n\n return (\n <aside\n ref={railRef}\n data-testid=\"comment-side-panel\"\n className={cn(\"relative w-[320px] space-y-3\", props.className)}\n >\n <div\n data-testid=\"comment-rail-header\"\n className=\"sticky top-0 z-20 flex items-center gap-2 bg-background py-2\"\n >\n {(counts.active > 0 || ctx.railMode === \"all\") && (\n <RailToggle\n active={ctx.railMode === \"all\" && stage === \"active\"}\n onClick={() => selectStage(\"active\")}\n testId=\"comment-open-all\"\n >\n {ctx.railMode === \"closed\"\n ? `Open comments (${counts.active})`\n : ctx.railMode === \"focused\"\n ? `All comments (${counts.active})`\n : `Open (${counts.active})`}\n </RailToggle>\n )}\n {counts.resolved > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"resolved\"}\n onClick={() => selectStage(\"resolved\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"resolved\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Resolved ({counts.resolved})\n </button>\n )}\n {counts.closed > 0 && (\n <button\n type=\"button\"\n aria-pressed={ctx.railMode === \"all\" && stage === \"closed\"}\n onClick={() => selectStage(\"closed\")}\n className={cn(\n \"rounded-full border px-2 py-1 text-xs font-medium transition-colors\",\n ctx.railMode === \"all\" && stage === \"closed\"\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-border bg-background text-muted-foreground hover:text-foreground\",\n )}\n >\n Closed ({counts.closed})\n </button>\n )}\n {ctx.railMode !== \"closed\" && (\n <button\n type=\"button\"\n onClick={ctx.closeRail}\n className=\"ml-auto rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {ctx.railMode === \"all\" ? \"Close\" : \"Hide\"}\n </button>\n )}\n </div>\n {ctx.railMode === \"all\" ? (\n <AllComments\n ctx={ctx}\n comments={visible}\n label={label}\n emptyLabel={\n stage === \"active\"\n ? \"No open comments.\"\n : stage === \"resolved\"\n ? \"No resolved comments.\"\n : \"No closed comments.\"\n }\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n ) : ctx.railMode === \"focused\" && ctx.focusedAnchor ? (\n <div\n ref={focusedRef}\n data-testid=\"comment-focused-rail\"\n className=\"relative space-y-3\"\n style={focusedOffset == null ? undefined : { top: focusedOffset }}\n >\n <FocusedComments\n ctx={ctx}\n comments={ctx.comments}\n anchor={ctx.focusedAnchor}\n label={label(ctx.focusedAnchor)}\n {...(props.compact !== undefined ? { compact: props.compact } : {})}\n {...(props.threadToMarkdown\n ? { threadToMarkdown: props.threadToMarkdown }\n : {})}\n />\n </div>\n ) : null}\n </aside>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -71,6 +71,7 @@ function CommentThreadList({
|
|
|
71
71
|
renderBody,
|
|
72
72
|
threadToMarkdown,
|
|
73
73
|
onUpdateStatus,
|
|
74
|
+
onClose,
|
|
74
75
|
onChecklistToggle,
|
|
75
76
|
onDelete,
|
|
76
77
|
onReply
|
|
@@ -94,6 +95,7 @@ function CommentThreadList({
|
|
|
94
95
|
function cardHandlers(id) {
|
|
95
96
|
return {
|
|
96
97
|
...onUpdateStatus ? { onUpdateStatus: (status) => onUpdateStatus(id, status) } : {},
|
|
98
|
+
...onClose ? { onClose: () => onClose(id) } : {},
|
|
97
99
|
...onChecklistToggle ? { onChecklistToggle: (index) => onChecklistToggle(id, index) } : {},
|
|
98
100
|
...onDelete ? { onDelete: () => onDelete(id) } : {}
|
|
99
101
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
|
|
@@ -14,7 +14,8 @@ export type CommentThreadListProps = {
|
|
|
14
14
|
renderBody?: (body: string) => ReactNode;
|
|
15
15
|
/** Serialize a root and its replies for Copy and the Markdown preview. */
|
|
16
16
|
threadToMarkdown?: (thread: readonly Comment[]) => string;
|
|
17
|
-
onUpdateStatus?: (id: string, status: string) => void
|
|
17
|
+
onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
|
|
18
|
+
onClose?: (id: string) => void | Promise<void>;
|
|
18
19
|
onChecklistToggle?: (id: string, index: number) => void;
|
|
19
20
|
onDelete?: (id: string) => void;
|
|
20
21
|
/** Submit a reply to `parent`. Presence enables the inline reply affordance. */
|
|
@@ -25,5 +26,5 @@ export type CommentThreadListProps = {
|
|
|
25
26
|
* optional inline reply box. Fully controlled — mutations route through the
|
|
26
27
|
* id-keyed callbacks.
|
|
27
28
|
*/
|
|
28
|
-
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
29
|
+
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
29
30
|
//# sourceMappingURL=CommentThreadList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCA8JxB"}
|
|
@@ -69,6 +69,7 @@ function CommentThreadList({
|
|
|
69
69
|
renderBody,
|
|
70
70
|
threadToMarkdown,
|
|
71
71
|
onUpdateStatus,
|
|
72
|
+
onClose,
|
|
72
73
|
onChecklistToggle,
|
|
73
74
|
onDelete,
|
|
74
75
|
onReply
|
|
@@ -92,6 +93,7 @@ function CommentThreadList({
|
|
|
92
93
|
function cardHandlers(id) {
|
|
93
94
|
return {
|
|
94
95
|
...onUpdateStatus ? { onUpdateStatus: (status) => onUpdateStatus(id, status) } : {},
|
|
96
|
+
...onClose ? { onClose: () => onClose(id) } : {},
|
|
95
97
|
...onChecklistToggle ? { onChecklistToggle: (index) => onChecklistToggle(id, index) } : {},
|
|
96
98
|
...onDelete ? { onDelete: () => onDelete(id) } : {}
|
|
97
99
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAgCA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,gBAAgB,OAAO,KAAK;AAClC,iBAAe,OAAO;AACpB,QAAI,CAAC,KAAK,UAAU,cAAc,QAAS;AAC3C,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM;AAC1B,cAAQ,EAAE;AAAA,IACZ,UAAA;AACE,oBAAc,UAAU;AACxB,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AACA,SACE,qBAAC,OAAA,EAAI,WAAU,0BAAyB,aAAW,YACjD,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,QACvC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,SAAS;AACrB,cAAE,eAAA;AACF,iBAAK,KAAA;AAAA,UACP;AACA,cAAI,EAAE,QAAQ,SAAU,UAAA;AAAA,QAC1B;AAAA,QACA,aAAY;AAAA,QACZ,eAAY;AAAA,QACZ,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,IAEZ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,UAAU,cAAc,CAAC,KAAK,KAAA;AAAA,QAC9B,SAAS,MAAM,KAAK,KAAA;AAAA,QACrB,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAED,GACF;AAEJ;AAOO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAGhE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAwB,IAAI;AAC1E,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,IACtC;AAAA,EAAA;AAGF,QAAM,CAAC,cAAc,eAAe,IAAI,SAAwB,IAAI;AACpE,QAAM,QAAQ,SAAS,QAAQ;AAC/B,QAAM,WAAW,cAAc,QAAQ;AAEvC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,gBAAgB,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,eAAe;AACtE,QAAM,kBAAkB,gBACpB,CAAC,eAAe,GAAG,YAAY,SAAS,IAAI,cAAc,EAAE,KAAK,CAAA,CAAE,CAAC,IACpE,CAAA;AAEJ,WAAS,iBAAiB;AACxB,uBAAmB,IAAI;AACvB,oBAAgB,IAAI;AACpB,oBAAgB,QAAQ;AAAA,EAC1B;AAEA,WAAS,aAAa,IAAY;AAChC,WAAO;AAAA,MACL,GAAI,iBACA,EAAE,gBAAgB,CAAC,WAAmB,eAAe,IAAI,MAAM,EAAA,IAC/D,CAAA;AAAA,MACJ,GAAI,oBACA,EAAE,mBAAmB,CAAC,UAAkB,kBAAkB,IAAI,KAAK,EAAA,IACnE,CAAA;AAAA,MACJ,GAAI,WAAW,EAAE,UAAU,MAAM,SAAS,EAAE,EAAA,IAAM,CAAA;AAAA,IAAC;AAAA,EAEvD;AAEA,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,SAAI,WAAU,aACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,YAAM,UAAU,YAAY,SAAS,IAAI,KAAK,EAAE,KAAK,EAAE;AACvD,YAAM,SAAS,CAAC,MAAM,GAAG,OAAO;AAChC,YAAM,eAAc,iDAAiB,UAAS,CAAA;AAC9C,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,eAAY;AAAA,UACZ,mBAAiB,KAAK;AAAA,UACrB,GAAG;AAAA,UACJ,WAAW,GAAG,mBAAmB,YAAY,SAAS;AAAA,UAErD,UAAA;AAAA,YAAA,qCACE,OAAA,EAAI,WAAU,QAAQ,UAAA,eAAe,IAAI,GAAE,IAC1C;AAAA,YACJ;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,YAAY,MAAM,mBAAmB,KAAK,EAAE;AAAA,gBAC3C,GAAI,mBACD,EAAE,QAAQ,MAAM,SAAS,iBAAiB,MAAM,CAAC,EAAA,IACjD,CAAA;AAAA,gBACH,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,gBAClC,GAAG,aAAa,KAAK,EAAE;AAAA,gBACvB,GAAI,UAAU,EAAE,SAAS,MAAM,cAAc,KAAK,EAAE,MAAM,CAAA;AAAA,cAAC;AAAA,YAAA;AAAA,YAE7D,QAAQ,SAAS,KAChB,oBAAC,OAAA,EAAI,WAAU,eACZ,UAAA,QAAQ,IAAI,CAAC,UACZ;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,SAAS;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,YAAY,MAAM,mBAAmB,KAAK,EAAE;AAAA,gBAC3C,GAAI,mBACD,EAAE,QAAQ,MAAM,SAAS,iBAAiB,MAAM,CAAC,EAAA,IACjD,CAAA;AAAA,gBACH,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,gBAClC,GAAG,aAAa,MAAM,EAAE;AAAA,cAAA;AAAA,cAVpB,MAAM;AAAA,YAAA,CAYd,GACH;AAAA,YAED,WAAW,eAAe,KAAK,MAC9B;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,UAAU,MAAM,cAAc,IAAI;AAAA,gBAClC,UAAU,OAAO,SAAS;AACxB,wBAAM,QAAQ,MAAM,IAAI;AACxB,gCAAc,IAAI;AAAA,gBACpB;AAAA,cAAA;AAAA,YAAA;AAAA,UACF;AAAA,QAAA;AAAA,QAhDG,KAAK;AAAA,MAAA;AAAA,IAoDhB,CAAC,EAAA,CACH;AAAA,IACC,iBACC,oBAAC,OAAA,EAAM,MAAI,MAAC,SAAS,gBAAgB,OAAM,kBAAiB,MAAK,MAC/D,UAAA,qBAAC,OAAA,EAAI,WAAU,aAAY,eAAY,wBACpC,UAAA;AAAA,MAAA,oBACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM;AAAA,YACJ,EAAE,IAAI,UAAU,OAAO,SAAA;AAAA,YACvB,EAAE,IAAI,YAAY,OAAO,WAAA;AAAA,UAAW;AAAA,UAEtC,OAAO;AAAA,UACP,UAAU,CAAC,QACT,gBAAgB,QAAQ,aAAa,aAAa,QAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,MAI/D,iBAAiB,cAAc,mBAC9B,oBAAC,YAAS,MAAM,iBAAiB,eAAe,GAAG,wBAElD,OAAA,EAAI,WAAU,aAEZ,UAAA,gBAAgB,IAAI,CAAC,YACpB,qBAAC,OAAA,EAAqB,WAAU,eAC9B,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS;AAAA,YACT;AAAA,YACA,iBAAe;AAAA,YACd,GAAI,mBACD;AAAA,cACE,QAAQ,MACN,SAAS,iBAAiB,eAAe,CAAC;AAAA,YAAA,IAE9C,CAAA;AAAA,YACH,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,YAClC,GAAG,aAAa,QAAQ,EAAE;AAAA,YAC1B,GAAI,UACD,EAAE,SAAS,MAAM,gBAAgB,QAAQ,EAAE,MAC3C,CAAA;AAAA,UAAC;AAAA,QAAA;AAAA,QAEN,WAAW,iBAAiB,QAAQ,MACnC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,UAAU,MAAM,gBAAgB,IAAI;AAAA,YACpC,UAAU,OAAO,SAAS;AACxB,oBAAM,QAAQ,eAAe,IAAI;AACjC,8BAAgB,IAAI;AAAA,YACtB;AAAA,UAAA;AAAA,QAAA;AAAA,MACF,KAxBM,QAAQ,EA0BlB,CACD,EAAA,CACH;AAAA,IAAA,EAAA,CAEJ,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const DOCUMENT_ANCHOR = "__document__";
|
|
4
4
|
const DEFAULT_COMMENT_STATUSES = [
|
|
5
|
-
{
|
|
5
|
+
{
|
|
6
|
+
value: "open",
|
|
7
|
+
label: "Open",
|
|
8
|
+
tone: "info",
|
|
9
|
+
unresolved: true,
|
|
10
|
+
stage: "active"
|
|
11
|
+
},
|
|
6
12
|
{
|
|
7
13
|
value: "in_progress",
|
|
8
14
|
label: "In progress",
|
|
9
15
|
tone: "warning",
|
|
10
|
-
unresolved: true
|
|
16
|
+
unresolved: true,
|
|
17
|
+
stage: "active"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: "resolved",
|
|
21
|
+
label: "Resolved",
|
|
22
|
+
tone: "success",
|
|
23
|
+
stage: "resolved"
|
|
11
24
|
},
|
|
12
|
-
{ value: "
|
|
13
|
-
{ value: "closed", label: "Closed", tone: "neutral" }
|
|
25
|
+
{ value: "closed", label: "Closed", tone: "neutral", stage: "closed" }
|
|
14
26
|
];
|
|
15
27
|
const DEFAULT_CHECKLIST_CYCLE = ["open", "in_progress", "done"];
|
|
16
28
|
exports.DEFAULT_CHECKLIST_CYCLE = DEFAULT_CHECKLIST_CYCLE;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-types.cjs","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {
|
|
1
|
+
{"version":3,"file":"comment-types.cjs","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** Product lifecycle stage represented by one or more stored statuses. */\nexport type CommentStatusStage = \"active\" | \"resolved\" | \"closed\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /** Lifecycle stage used by review queues and status actions. */\n stage?: CommentStatusStage;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** ISO-8601 time when a human closed the resolved comment. */\n closedAt?: string;\n /** Human who closed the comment. */\n closedBy?: CommentAuthor;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n /** Human-only transition from resolved to closed. */\n onClose?: (id: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved / closed. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {\n value: \"open\",\n label: \"Open\",\n tone: \"info\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"resolved\",\n label: \"Resolved\",\n tone: \"success\",\n stage: \"resolved\",\n },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\", stage: \"closed\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":";;AA8GO,MAAM,kBAAkB;AAuGxB,MAAM,2BAAkD;AAAA,EAC7D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,WAAW,OAAO,SAAA;AAC9D;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;;;;"}
|
|
@@ -2,6 +2,8 @@ import { ReactNode } from '../../../../node_modules/@types/react/index.d.ts';
|
|
|
2
2
|
import { StaticIconComponent } from '../data/Icon';
|
|
3
3
|
/** Semantic tone driving a status/facet color (maps onto Badge tones). */
|
|
4
4
|
export type CommentTone = "default" | "info" | "success" | "warning" | "danger" | "neutral";
|
|
5
|
+
/** Product lifecycle stage represented by one or more stored statuses. */
|
|
6
|
+
export type CommentStatusStage = "active" | "resolved" | "closed";
|
|
5
7
|
/** A single selectable status (e.g. "open", "resolved"). Consumer-defined. */
|
|
6
8
|
export type CommentStatusConfig = {
|
|
7
9
|
/** Stable value stored on a comment's `status`. */
|
|
@@ -12,6 +14,8 @@ export type CommentStatusConfig = {
|
|
|
12
14
|
tone?: CommentTone;
|
|
13
15
|
/** Optional leading glyph. */
|
|
14
16
|
icon?: string | StaticIconComponent;
|
|
17
|
+
/** Lifecycle stage used by review queues and status actions. */
|
|
18
|
+
stage?: CommentStatusStage;
|
|
15
19
|
/**
|
|
16
20
|
* Whether this status counts as "unresolved/open" — drives progress tallies
|
|
17
21
|
* and which groups start expanded. Defaults to false.
|
|
@@ -102,6 +106,10 @@ export type Comment = {
|
|
|
102
106
|
anchor?: CommentAnchor | null;
|
|
103
107
|
/** Optional positive/negative review signal; roots only. */
|
|
104
108
|
rating?: CommentRating;
|
|
109
|
+
/** ISO-8601 time when a human closed the resolved comment. */
|
|
110
|
+
closedAt?: string;
|
|
111
|
+
/** Human who closed the comment. */
|
|
112
|
+
closedBy?: CommentAuthor;
|
|
105
113
|
/** facet.key → selected option value. */
|
|
106
114
|
facets?: Record<string, string>;
|
|
107
115
|
refs?: CommentRefGroup[];
|
|
@@ -130,6 +138,8 @@ export type CommentCallbacks = {
|
|
|
130
138
|
onCreate?: (input: CommentCreateInput) => void | Promise<void>;
|
|
131
139
|
onReply?: (input: CommentReplyInput) => void | Promise<void>;
|
|
132
140
|
onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
|
|
141
|
+
/** Human-only transition from resolved to closed. */
|
|
142
|
+
onClose?: (id: string) => void | Promise<void>;
|
|
133
143
|
onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;
|
|
134
144
|
onDelete?: (id: string) => void | Promise<void>;
|
|
135
145
|
onChecklistToggle?: (id: string, index: number, next: string) => void | Promise<void>;
|
|
@@ -169,7 +179,7 @@ export type CommentRenderOptions = {
|
|
|
169
179
|
/** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */
|
|
170
180
|
renderBody?: (body: string) => ReactNode;
|
|
171
181
|
};
|
|
172
|
-
/** A domain-free default status set: open / in progress / resolved. */
|
|
182
|
+
/** A domain-free default status set: open / in progress / resolved / closed. */
|
|
173
183
|
export declare const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[];
|
|
174
184
|
/** Default checklist status cycle used when a consumer omits one. */
|
|
175
185
|
export declare const DEFAULT_CHECKLIST_CYCLE: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-types.d.ts","sourceRoot":"","sources":["../../src/comments/comment-types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"comment-types.d.ts","sourceRoot":"","sources":["../../src/comments/comment-types.ts"],"names":[],"mappings":"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"}
|