@flanksource/clicky-ui 0.3.32 → 0.3.34

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.
Files changed (67) hide show
  1. package/dist/comments/CommentCard.cjs +9 -8
  2. package/dist/comments/CommentCard.cjs.map +1 -1
  3. package/dist/comments/CommentCard.d.ts.map +1 -1
  4. package/dist/comments/CommentCard.js +2 -1
  5. package/dist/comments/CommentCard.js.map +1 -1
  6. package/dist/comments/CommentCardParts.cjs +2 -1
  7. package/dist/comments/CommentCardParts.cjs.map +1 -1
  8. package/dist/comments/CommentCardParts.d.ts.map +1 -1
  9. package/dist/comments/CommentCardParts.js +2 -1
  10. package/dist/comments/CommentCardParts.js.map +1 -1
  11. package/dist/comments/CommentSidePanel.cjs +3 -2
  12. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  13. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  14. package/dist/comments/CommentSidePanel.js +2 -1
  15. package/dist/comments/CommentSidePanel.js.map +1 -1
  16. package/dist/comments/comment-utils.cjs +0 -27
  17. package/dist/comments/comment-utils.cjs.map +1 -1
  18. package/dist/comments/comment-utils.d.ts +1 -7
  19. package/dist/comments/comment-utils.d.ts.map +1 -1
  20. package/dist/comments/comment-utils.js +0 -27
  21. package/dist/comments/comment-utils.js.map +1 -1
  22. package/dist/comments.cjs +4 -3
  23. package/dist/comments.cjs.map +1 -1
  24. package/dist/comments.d.ts +1 -0
  25. package/dist/comments.d.ts.map +1 -1
  26. package/dist/comments.js +2 -1
  27. package/dist/comments.js.map +1 -1
  28. package/dist/components/EndpointSelector.cjs +6 -6
  29. package/dist/components/EndpointSelector.cjs.map +1 -1
  30. package/dist/components/EndpointSelector.d.ts.map +1 -1
  31. package/dist/components/EndpointSelector.js +6 -6
  32. package/dist/components/EndpointSelector.js.map +1 -1
  33. package/dist/components/ErrorWrapper.cjs +3 -2
  34. package/dist/components/ErrorWrapper.cjs.map +1 -1
  35. package/dist/components/ErrorWrapper.js +3 -2
  36. package/dist/components/ErrorWrapper.js.map +1 -1
  37. package/dist/components/RangeSlider.cjs +122 -45
  38. package/dist/components/RangeSlider.cjs.map +1 -1
  39. package/dist/components/RangeSlider.d.ts +3 -1
  40. package/dist/components/RangeSlider.d.ts.map +1 -1
  41. package/dist/components/RangeSlider.js +122 -45
  42. package/dist/components/RangeSlider.js.map +1 -1
  43. package/dist/components/SecretKeySelector.cjs +4 -4
  44. package/dist/components/SecretKeySelector.cjs.map +1 -1
  45. package/dist/components/SecretKeySelector.js +4 -4
  46. package/dist/components/SecretKeySelector.js.map +1 -1
  47. package/dist/data/badge-variants.d.ts +2 -2
  48. package/dist/data/version-info.cjs +3 -3
  49. package/dist/data/version-info.js +3 -3
  50. package/dist/index.cjs +4 -3
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.js +2 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/lib/comment-stage.cjs +31 -0
  55. package/dist/lib/comment-stage.cjs.map +1 -0
  56. package/dist/lib/comment-stage.d.ts +8 -0
  57. package/dist/lib/comment-stage.d.ts.map +1 -0
  58. package/dist/lib/comment-stage.js +31 -0
  59. package/dist/lib/comment-stage.js.map +1 -0
  60. package/dist/styles.css +1 -1
  61. package/dist/utils.cjs +4 -0
  62. package/dist/utils.cjs.map +1 -1
  63. package/dist/utils.d.ts +1 -0
  64. package/dist/utils.d.ts.map +1 -1
  65. package/dist/utils.js +4 -0
  66. package/dist/utils.js.map +1 -1
  67. package/package.json +1 -1
@@ -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 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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"}
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 sortReplies,\n buildReplyMap,\n} from \"./comment-utils\";\nimport {\n resolveCommentStage,\n selectCommentThreadsByStage,\n} from \"../lib/comment-stage\";\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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"}
@@ -38,22 +38,6 @@ function isUnresolved(config, status) {
38
38
  var _a;
39
39
  return ((_a = resolveStatusConfig(config, status)) == null ? void 0 : _a.unresolved) ?? false;
40
40
  }
41
- function resolveCommentStage(config, status) {
42
- if (status == null) return "active";
43
- const resolved = resolveStatusConfig(config, status);
44
- if (!resolved) return void 0;
45
- if (resolved.stage) return resolved.stage;
46
- if (resolved.unresolved) return "active";
47
- if (resolved.value.toLowerCase() === "closed" || resolved.label.toLowerCase() === "closed") {
48
- return "closed";
49
- }
50
- return "resolved";
51
- }
52
- function statusForCommentStage(config, stage) {
53
- return config.statuses.find(
54
- (status) => resolveCommentStage(config, status.value) === stage
55
- );
56
- }
57
41
  function getRoots(comments) {
58
42
  return comments.filter((c) => !c.parentId);
59
43
  }
@@ -93,14 +77,6 @@ function selectUnresolvedThreads(comments, config) {
93
77
  );
94
78
  return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
95
79
  }
96
- function selectCommentThreadsByStage(comments, config, stage) {
97
- const keep = new Set(
98
- getRoots(comments).filter((root) => resolveCommentStage(config, root.status) === stage).map((root) => String(root.id))
99
- );
100
- return comments.filter(
101
- (comment) => keep.has(String(comment.parentId ?? comment.id))
102
- );
103
- }
104
80
  function sortReplies(replies) {
105
81
  return [...replies].sort((a, b) => {
106
82
  if (a.createdAt !== b.createdAt)
@@ -259,14 +235,11 @@ exports.hasActiveFilters = hasActiveFilters;
259
235
  exports.isUnresolved = isUnresolved;
260
236
  exports.matchMentionsInBody = matchMentionsInBody;
261
237
  exports.nextChecklistStatus = nextChecklistStatus;
262
- exports.resolveCommentStage = resolveCommentStage;
263
238
  exports.resolveFacetOption = resolveFacetOption;
264
239
  exports.resolveStatusConfig = resolveStatusConfig;
265
240
  exports.selectAnchorThreads = selectAnchorThreads;
266
- exports.selectCommentThreadsByStage = selectCommentThreadsByStage;
267
241
  exports.selectUnresolvedThreads = selectUnresolvedThreads;
268
242
  exports.sortReplies = sortReplies;
269
- exports.statusForCommentStage = statusForCommentStage;
270
243
  exports.toneToBadgeTone = toneToBadgeTone;
271
244
  exports.truncatePlain = truncatePlain;
272
245
  //# sourceMappingURL=comment-utils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.cjs","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentStatusStage,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Resolves a stored status to its lifecycle stage. */\nexport function resolveCommentStage(\n config: CommentConfig,\n status: string | undefined,\n): CommentStatusStage | undefined {\n if (status == null) return \"active\";\n const resolved = resolveStatusConfig(config, status);\n if (!resolved) return undefined;\n if (resolved.stage) return resolved.stage;\n if (resolved.unresolved) return \"active\";\n if (\n resolved.value.toLowerCase() === \"closed\" ||\n resolved.label.toLowerCase() === \"closed\"\n ) {\n return \"closed\";\n }\n return \"resolved\";\n}\n\n/** Returns the first configured stored status for a lifecycle stage. */\nexport function statusForCommentStage(\n config: CommentConfig,\n stage: CommentStatusStage,\n): CommentStatusConfig | undefined {\n return config.statuses.find(\n (status) => resolveCommentStage(config, status.value) === stage,\n );\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Selects roots in one lifecycle stage together with every descendant reply. */\nexport function selectCommentThreadsByStage(\n comments: Comment[],\n config: CommentConfig,\n stage: CommentStatusStage,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => resolveCommentStage(config, root.status) === stage)\n .map((root) => String(root.id)),\n );\n return comments.filter((comment) =>\n keep.has(String(comment.parentId ?? comment.id)),\n );\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\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 onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":["DOCUMENT_ANCHOR","DEFAULT_CHECKLIST_CYCLE"],"mappings":"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"}
1
+ {"version":3,"file":"comment-utils.cjs","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\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 onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":["DOCUMENT_ANCHOR","DEFAULT_CHECKLIST_CYCLE"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { BadgeTone } from '../data/Badge';
2
- import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentStatusStage, CommentTone } from './comment-types';
2
+ import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentTone } from './comment-types';
3
3
  /** Human-readable name for an author, falling back to "Unknown". */
4
4
  export declare function authorDisplayName(author: CommentAuthor | null): string;
5
5
  /** Maps a CommentTone onto the Badge component's tone vocabulary. */
@@ -12,10 +12,6 @@ export declare function resolveFacetOption(facet: CommentFacet, value: string |
12
12
  export declare function defaultStatusValue(config: CommentConfig): string | undefined;
13
13
  /** Whether a stored status counts as unresolved/open. */
14
14
  export declare function isUnresolved(config: CommentConfig, status: string | undefined): boolean;
15
- /** Resolves a stored status to its lifecycle stage. */
16
- export declare function resolveCommentStage(config: CommentConfig, status: string | undefined): CommentStatusStage | undefined;
17
- /** Returns the first configured stored status for a lifecycle stage. */
18
- export declare function statusForCommentStage(config: CommentConfig, stage: CommentStatusStage): CommentStatusConfig | undefined;
19
15
  /** Returns the root (non-reply) comments in input order. */
20
16
  export declare function getRoots(comments: Comment[]): Comment[];
21
17
  /** Indexes replies by their parent id. */
@@ -31,8 +27,6 @@ export declare function selectAnchorThreads(comments: Comment[], anchor: Comment
31
27
  * been acted on, so it counts as unresolved; replies follow their root.
32
28
  */
33
29
  export declare function selectUnresolvedThreads(comments: Comment[], config: CommentConfig): Comment[];
34
- /** Selects roots in one lifecycle stage together with every descendant reply. */
35
- export declare function selectCommentThreadsByStage(comments: Comment[], config: CommentConfig, stage: CommentStatusStage): Comment[];
36
30
  /** Stable reply ordering: by creation time, then id. */
37
31
  export declare function sortReplies(replies: Comment[]): Comment[];
38
32
  /** Groups root comments by the value of a facet key (missing → ""). */
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"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"}
@@ -36,22 +36,6 @@ function isUnresolved(config, status) {
36
36
  var _a;
37
37
  return ((_a = resolveStatusConfig(config, status)) == null ? void 0 : _a.unresolved) ?? false;
38
38
  }
39
- function resolveCommentStage(config, status) {
40
- if (status == null) return "active";
41
- const resolved = resolveStatusConfig(config, status);
42
- if (!resolved) return void 0;
43
- if (resolved.stage) return resolved.stage;
44
- if (resolved.unresolved) return "active";
45
- if (resolved.value.toLowerCase() === "closed" || resolved.label.toLowerCase() === "closed") {
46
- return "closed";
47
- }
48
- return "resolved";
49
- }
50
- function statusForCommentStage(config, stage) {
51
- return config.statuses.find(
52
- (status) => resolveCommentStage(config, status.value) === stage
53
- );
54
- }
55
39
  function getRoots(comments) {
56
40
  return comments.filter((c) => !c.parentId);
57
41
  }
@@ -91,14 +75,6 @@ function selectUnresolvedThreads(comments, config) {
91
75
  );
92
76
  return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
93
77
  }
94
- function selectCommentThreadsByStage(comments, config, stage) {
95
- const keep = new Set(
96
- getRoots(comments).filter((root) => resolveCommentStage(config, root.status) === stage).map((root) => String(root.id))
97
- );
98
- return comments.filter(
99
- (comment) => keep.has(String(comment.parentId ?? comment.id))
100
- );
101
- }
102
78
  function sortReplies(replies) {
103
79
  return [...replies].sort((a, b) => {
104
80
  if (a.createdAt !== b.createdAt)
@@ -258,14 +234,11 @@ export {
258
234
  isUnresolved,
259
235
  matchMentionsInBody,
260
236
  nextChecklistStatus,
261
- resolveCommentStage,
262
237
  resolveFacetOption,
263
238
  resolveStatusConfig,
264
239
  selectAnchorThreads,
265
- selectCommentThreadsByStage,
266
240
  selectUnresolvedThreads,
267
241
  sortReplies,
268
- statusForCommentStage,
269
242
  toneToBadgeTone,
270
243
  truncatePlain
271
244
  };
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.js","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentStatusStage,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Resolves a stored status to its lifecycle stage. */\nexport function resolveCommentStage(\n config: CommentConfig,\n status: string | undefined,\n): CommentStatusStage | undefined {\n if (status == null) return \"active\";\n const resolved = resolveStatusConfig(config, status);\n if (!resolved) return undefined;\n if (resolved.stage) return resolved.stage;\n if (resolved.unresolved) return \"active\";\n if (\n resolved.value.toLowerCase() === \"closed\" ||\n resolved.label.toLowerCase() === \"closed\"\n ) {\n return \"closed\";\n }\n return \"resolved\";\n}\n\n/** Returns the first configured stored status for a lifecycle stage. */\nexport function statusForCommentStage(\n config: CommentConfig,\n stage: CommentStatusStage,\n): CommentStatusConfig | undefined {\n return config.statuses.find(\n (status) => resolveCommentStage(config, status.value) === stage,\n );\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Selects roots in one lifecycle stage together with every descendant reply. */\nexport function selectCommentThreadsByStage(\n comments: Comment[],\n config: CommentConfig,\n stage: CommentStatusStage,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => resolveCommentStage(config, root.status) === stage)\n .map((root) => String(root.id)),\n );\n return comments.filter((comment) =>\n keep.has(String(comment.parentId ?? comment.id)),\n );\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\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 onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"comment-utils.js","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\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 onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":[],"mappings":"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"}
package/dist/comments.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const commentTypes = require("./comments/comment-types.cjs");
4
4
  const commentUtils = require("./comments/comment-utils.cjs");
5
+ const commentStage = require("./lib/comment-stage.cjs");
5
6
  const CommentMarkdown = require("./comments/CommentMarkdown.cjs");
6
7
  const CommentAuthor = require("./comments/CommentAuthor.cjs");
7
8
  const MentionTextarea = require("./comments/MentionTextarea.cjs");
@@ -33,16 +34,16 @@ exports.hasActiveFilters = commentUtils.hasActiveFilters;
33
34
  exports.isUnresolved = commentUtils.isUnresolved;
34
35
  exports.matchMentionsInBody = commentUtils.matchMentionsInBody;
35
36
  exports.nextChecklistStatus = commentUtils.nextChecklistStatus;
36
- exports.resolveCommentStage = commentUtils.resolveCommentStage;
37
37
  exports.resolveFacetOption = commentUtils.resolveFacetOption;
38
38
  exports.resolveStatusConfig = commentUtils.resolveStatusConfig;
39
39
  exports.selectAnchorThreads = commentUtils.selectAnchorThreads;
40
- exports.selectCommentThreadsByStage = commentUtils.selectCommentThreadsByStage;
41
40
  exports.selectUnresolvedThreads = commentUtils.selectUnresolvedThreads;
42
41
  exports.sortReplies = commentUtils.sortReplies;
43
- exports.statusForCommentStage = commentUtils.statusForCommentStage;
44
42
  exports.toneToBadgeTone = commentUtils.toneToBadgeTone;
45
43
  exports.truncatePlain = commentUtils.truncatePlain;
44
+ exports.resolveCommentStage = commentStage.resolveCommentStage;
45
+ exports.selectCommentThreadsByStage = commentStage.selectCommentThreadsByStage;
46
+ exports.statusForCommentStage = commentStage.statusForCommentStage;
46
47
  exports.CommentMarkdown = CommentMarkdown.CommentMarkdown;
47
48
  exports.CommentAuthorAvatar = CommentAuthor.CommentAuthorAvatar;
48
49
  exports.MentionTextarea = MentionTextarea.MentionTextarea;
@@ -1 +1 @@
1
- {"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,6 @@
1
1
  export * from './comments/comment-types';
2
2
  export * from './comments/comment-utils';
3
+ export { resolveCommentStage, selectCommentThreadsByStage, statusForCommentStage, } from './lib/comment-stage';
3
4
  export { CommentMarkdown, type CommentMarkdownProps, } from './comments/CommentMarkdown';
4
5
  export { CommentAuthorAvatar, type CommentAuthorAvatarProps, } from './comments/CommentAuthor';
5
6
  export { MentionTextarea, type MentionTextareaProps, } from './comments/MentionTextarea';
@@ -1 +1 @@
1
- {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AAEzC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,GAC5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACxF,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,wBAAwB,EACxB,+BAA+B,EAC/B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../src/comments.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,GAC5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACxF,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,wBAAwB,EACxB,+BAA+B,EAC/B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC"}
package/dist/comments.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_CHECKLIST_CYCLE, DEFAULT_COMMENT_STATUSES, DOCUMENT_ANCHOR } from "./comments/comment-types.js";
2
- import { applyCommentFilters, authorDisplayName, buildReplyMap, buildThreadListHandlers, commentCountForPrefix, defaultStatusValue, deriveAnchorCounts, deriveAnchorMeta, emptyCommentFilters, getRoots, groupByFacet, hasActiveFilters, isUnresolved, matchMentionsInBody, nextChecklistStatus, resolveCommentStage, resolveFacetOption, resolveStatusConfig, selectAnchorThreads, selectCommentThreadsByStage, selectUnresolvedThreads, sortReplies, statusForCommentStage, toneToBadgeTone, truncatePlain } from "./comments/comment-utils.js";
2
+ import { applyCommentFilters, authorDisplayName, buildReplyMap, buildThreadListHandlers, commentCountForPrefix, defaultStatusValue, deriveAnchorCounts, deriveAnchorMeta, emptyCommentFilters, getRoots, groupByFacet, hasActiveFilters, isUnresolved, matchMentionsInBody, nextChecklistStatus, resolveFacetOption, resolveStatusConfig, selectAnchorThreads, selectUnresolvedThreads, sortReplies, toneToBadgeTone, truncatePlain } from "./comments/comment-utils.js";
3
+ import { resolveCommentStage, selectCommentThreadsByStage, statusForCommentStage } from "./lib/comment-stage.js";
3
4
  import { CommentMarkdown } from "./comments/CommentMarkdown.js";
4
5
  import { CommentAuthorAvatar } from "./comments/CommentAuthor.js";
5
6
  import { MentionTextarea } from "./comments/MentionTextarea.js";
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"comments.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
@@ -27,10 +27,10 @@ const DEFAULT_MODE_KINDS = {
27
27
  ingress: ["ingress"],
28
28
  "port-forward": ["service", "deployment"]
29
29
  };
30
- const WORKLOAD_FIELD = "max-w-full min-w-0 basis-56 shrink grow";
31
- const SCHEME_FIELD = "max-w-full min-w-0 basis-32 shrink grow-0";
32
- const PORT_FIELD = "max-w-full min-w-0 basis-36 shrink grow-0";
33
- const PATH_FIELD = "max-w-full min-w-0 basis-56 shrink grow";
30
+ const WORKLOAD_FIELD = "col-span-2 max-w-full min-w-0 @min-[40rem]/endpoint:col-span-1";
31
+ const SCHEME_FIELD = "max-w-full min-w-0 w-full";
32
+ const PORT_FIELD = "max-w-full min-w-0 w-full";
33
+ const PATH_FIELD = "col-span-2 max-w-full min-w-0 @min-[40rem]/endpoint:col-span-3 @min-[56rem]/endpoint:col-span-1";
34
34
  function isWorkloadValue(value) {
35
35
  return value !== void 0 && value.mode !== "url";
36
36
  }
@@ -268,7 +268,7 @@ function EndpointSelector({
268
268
  };
269
269
  const workloadValue = isWorkloadValue(value) && value.mode === activeMode ? targetKey(value.target, namespace, selectedResource) : "";
270
270
  const showPort = activeMode !== "url" && (activeMode !== "ingress" || showIngressPort);
271
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("space-y-density-2", className), children: [
271
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("@container/endpoint space-y-density-2", className), children: [
272
272
  /* @__PURE__ */ jsxRuntime.jsx(
273
273
  SegmentedControl.SegmentedControl,
274
274
  {
@@ -294,7 +294,7 @@ function EndpointSelector({
294
294
  })() : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-density-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
295
295
  "div",
296
296
  {
297
- className: "flex w-full max-w-full min-w-0 flex-nowrap items-end gap-density-2",
297
+ className: "grid w-full max-w-full min-w-0 grid-cols-2 items-end gap-density-2 @min-[40rem]/endpoint:grid-cols-[minmax(14rem,1fr)_auto_auto] @min-[56rem]/endpoint:grid-cols-[minmax(14rem,1fr)_auto_auto_minmax(14rem,1fr)]",
298
298
  "data-slot": "endpoint-fields",
299
299
  children: [
300
300
  /* @__PURE__ */ jsxRuntime.jsx(