@flanksource/clicky-ui 0.3.30 → 0.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comments/CommentCard.cjs +156 -273
- package/dist/comments/CommentCard.cjs.map +1 -1
- package/dist/comments/CommentCard.d.ts +5 -1
- package/dist/comments/CommentCard.d.ts.map +1 -1
- package/dist/comments/CommentCard.js +151 -268
- package/dist/comments/CommentCard.js.map +1 -1
- package/dist/comments/CommentCardParts.cjs +115 -0
- package/dist/comments/CommentCardParts.cjs.map +1 -0
- package/dist/comments/CommentCardParts.d.ts +21 -0
- package/dist/comments/CommentCardParts.d.ts.map +1 -0
- package/dist/comments/CommentCardParts.js +115 -0
- package/dist/comments/CommentCardParts.js.map +1 -0
- package/dist/comments/CommentProvider.cjs +3 -0
- package/dist/comments/CommentProvider.cjs.map +1 -1
- package/dist/comments/CommentProvider.d.ts +1 -1
- package/dist/comments/CommentProvider.d.ts.map +1 -1
- package/dist/comments/CommentProvider.js +3 -0
- package/dist/comments/CommentProvider.js.map +1 -1
- package/dist/comments/CommentSidePanel.cjs +55 -22
- package/dist/comments/CommentSidePanel.cjs.map +1 -1
- package/dist/comments/CommentSidePanel.d.ts +3 -1
- package/dist/comments/CommentSidePanel.d.ts.map +1 -1
- package/dist/comments/CommentSidePanel.js +56 -23
- package/dist/comments/CommentSidePanel.js.map +1 -1
- package/dist/comments/CommentThread.cjs +2 -0
- package/dist/comments/CommentThread.cjs.map +1 -1
- package/dist/comments/CommentThread.d.ts +2 -1
- package/dist/comments/CommentThread.d.ts.map +1 -1
- package/dist/comments/CommentThread.js +2 -0
- package/dist/comments/CommentThread.js.map +1 -1
- package/dist/comments/CommentThreadList.cjs +52 -34
- package/dist/comments/CommentThreadList.cjs.map +1 -1
- package/dist/comments/CommentThreadList.d.ts +5 -2
- package/dist/comments/CommentThreadList.d.ts.map +1 -1
- package/dist/comments/CommentThreadList.js +52 -34
- package/dist/comments/CommentThreadList.js.map +1 -1
- package/dist/comments/comment-types.cjs +16 -4
- package/dist/comments/comment-types.cjs.map +1 -1
- package/dist/comments/comment-types.d.ts +11 -1
- package/dist/comments/comment-types.d.ts.map +1 -1
- package/dist/comments/comment-types.js +16 -4
- package/dist/comments/comment-types.js.map +1 -1
- package/dist/comments/comment-utils.cjs +31 -1
- package/dist/comments/comment-utils.cjs.map +1 -1
- package/dist/comments/comment-utils.d.ts +9 -2
- package/dist/comments/comment-utils.d.ts.map +1 -1
- package/dist/comments/comment-utils.js +31 -1
- package/dist/comments/comment-utils.js.map +1 -1
- package/dist/comments.cjs +3 -0
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.js +4 -1
- package/dist/components/ComboboxMenu.cjs +4 -1
- package/dist/components/ComboboxMenu.cjs.map +1 -1
- package/dist/components/ComboboxMenu.d.ts.map +1 -1
- package/dist/components/ComboboxMenu.js +5 -2
- package/dist/components/ComboboxMenu.js.map +1 -1
- package/dist/components/ComboboxTags.cjs +5 -1
- package/dist/components/ComboboxTags.cjs.map +1 -1
- package/dist/components/ComboboxTags.d.ts.map +1 -1
- package/dist/components/ComboboxTags.js +6 -2
- package/dist/components/ComboboxTags.js.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.cjs +24 -35
- package/dist/components/ConnectionLoggingPolicy.cjs.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.d.ts.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.js +24 -35
- package/dist/components/ConnectionLoggingPolicy.js.map +1 -1
- package/dist/components/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect.d.ts +7 -0
- package/dist/components/MultiSelect.d.ts.map +1 -1
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/filter-bar-multi.cjs +10 -1
- package/dist/components/filter-bar-multi.cjs.map +1 -1
- package/dist/components/filter-bar-multi.d.ts.map +1 -1
- package/dist/components/filter-bar-multi.js +11 -2
- package/dist/components/filter-bar-multi.js.map +1 -1
- package/dist/data/ai/SessionViewer.model.cjs +23 -8
- package/dist/data/ai/SessionViewer.model.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.model.d.ts +8 -0
- package/dist/data/ai/SessionViewer.model.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.model.js +23 -8
- package/dist/data/ai/SessionViewer.model.js.map +1 -1
- package/dist/data/ai/SessionViewer.rows.cjs +9 -1
- package/dist/data/ai/SessionViewer.rows.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.rows.js +9 -1
- package/dist/data/ai/SessionViewer.rows.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs +24 -2
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.js +25 -3
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.cjs +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.js +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +6 -4
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +10 -1
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +6 -4
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/test-runner/status.cjs +1 -1
- package/dist/data/test-runner/status.cjs.map +1 -1
- package/dist/data/test-runner/status.js +1 -1
- package/dist/data/test-runner/status.js.map +1 -1
- package/dist/data/test-runner/types.d.ts +6 -0
- package/dist/data/test-runner/types.d.ts.map +1 -1
- package/dist/data/verification/VerificationResults.cjs +99 -0
- package/dist/data/verification/VerificationResults.cjs.map +1 -0
- package/dist/data/verification/VerificationResults.d.ts +27 -0
- package/dist/data/verification/VerificationResults.d.ts.map +1 -0
- package/dist/data/verification/VerificationResults.js +99 -0
- package/dist/data/verification/VerificationResults.js.map +1 -0
- package/dist/data/verification/adapterViews.cjs +116 -0
- package/dist/data/verification/adapterViews.cjs.map +1 -0
- package/dist/data/verification/adapterViews.d.ts +13 -0
- package/dist/data/verification/adapterViews.d.ts.map +1 -0
- package/dist/data/verification/adapterViews.js +116 -0
- package/dist/data/verification/adapterViews.js.map +1 -0
- package/dist/data/verification/adapters.cjs +26 -0
- package/dist/data/verification/adapters.cjs.map +1 -0
- package/dist/data/verification/adapters.d.ts +6 -0
- package/dist/data/verification/adapters.d.ts.map +1 -0
- package/dist/data/verification/adapters.js +26 -0
- package/dist/data/verification/adapters.js.map +1 -0
- package/dist/data/verification/index.d.ts +5 -0
- package/dist/data/verification/index.d.ts.map +1 -0
- package/dist/data/verification/verify-report.cjs +58 -0
- package/dist/data/verification/verify-report.cjs.map +1 -0
- package/dist/data/verification/verify-report.d.ts +96 -0
- package/dist/data/verification/verify-report.d.ts.map +1 -0
- package/dist/data/verification/verify-report.js +58 -0
- package/dist/data/verification/verify-report.js.map +1 -0
- package/dist/data/verification/verifyReportTests.cjs +84 -0
- package/dist/data/verification/verifyReportTests.cjs.map +1 -0
- package/dist/data/verification/verifyReportTests.d.ts +13 -0
- package/dist/data/verification/verifyReportTests.d.ts.map +1 -0
- package/dist/data/verification/verifyReportTests.js +84 -0
- package/dist/data/verification/verifyReportTests.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +10 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +10 -0
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +13 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
const DOCUMENT_ANCHOR = "__document__";
|
|
2
2
|
const DEFAULT_COMMENT_STATUSES = [
|
|
3
|
-
{
|
|
3
|
+
{
|
|
4
|
+
value: "open",
|
|
5
|
+
label: "Open",
|
|
6
|
+
tone: "info",
|
|
7
|
+
unresolved: true,
|
|
8
|
+
stage: "active"
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
value: "in_progress",
|
|
6
12
|
label: "In progress",
|
|
7
13
|
tone: "warning",
|
|
8
|
-
unresolved: true
|
|
14
|
+
unresolved: true,
|
|
15
|
+
stage: "active"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "resolved",
|
|
19
|
+
label: "Resolved",
|
|
20
|
+
tone: "success",
|
|
21
|
+
stage: "resolved"
|
|
9
22
|
},
|
|
10
|
-
{ value: "
|
|
11
|
-
{ value: "closed", label: "Closed", tone: "neutral" }
|
|
23
|
+
{ value: "closed", label: "Closed", tone: "neutral", stage: "closed" }
|
|
12
24
|
];
|
|
13
25
|
const DEFAULT_CHECKLIST_CYCLE = ["open", "in_progress", "done"];
|
|
14
26
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-types.js","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {
|
|
1
|
+
{"version":3,"file":"comment-types.js","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** Product lifecycle stage represented by one or more stored statuses. */\nexport type CommentStatusStage = \"active\" | \"resolved\" | \"closed\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /** Lifecycle stage used by review queues and status actions. */\n stage?: CommentStatusStage;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** ISO-8601 time when a human closed the resolved comment. */\n closedAt?: string;\n /** Human who closed the comment. */\n closedBy?: CommentAuthor;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n /** Human-only transition from resolved to closed. */\n onClose?: (id: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved / closed. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {\n value: \"open\",\n label: \"Open\",\n tone: \"info\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"resolved\",\n label: \"Resolved\",\n tone: \"success\",\n stage: \"resolved\",\n },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\", stage: \"closed\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":"AA8GO,MAAM,kBAAkB;AAuGxB,MAAM,2BAAkD;AAAA,EAC7D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,WAAW,OAAO,SAAA;AAC9D;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;"}
|
|
@@ -38,6 +38,22 @@ 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
|
+
}
|
|
41
57
|
function getRoots(comments) {
|
|
42
58
|
return comments.filter((c) => !c.parentId);
|
|
43
59
|
}
|
|
@@ -71,10 +87,20 @@ function selectAnchorThreads(comments, anchor) {
|
|
|
71
87
|
}
|
|
72
88
|
function selectUnresolvedThreads(comments, config) {
|
|
73
89
|
const keep = new Set(
|
|
74
|
-
getRoots(comments).filter(
|
|
90
|
+
getRoots(comments).filter(
|
|
91
|
+
(root) => root.status == null || isUnresolved(config, root.status)
|
|
92
|
+
).map((root) => String(root.id))
|
|
75
93
|
);
|
|
76
94
|
return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
|
|
77
95
|
}
|
|
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
|
+
}
|
|
78
104
|
function sortReplies(replies) {
|
|
79
105
|
return [...replies].sort((a, b) => {
|
|
80
106
|
if (a.createdAt !== b.createdAt)
|
|
@@ -187,6 +213,7 @@ function matchMentionsInBody(body, mentionables) {
|
|
|
187
213
|
function buildThreadListHandlers(comments, config, cb) {
|
|
188
214
|
const handlers = {};
|
|
189
215
|
if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;
|
|
216
|
+
if (cb.onClose) handlers.onClose = cb.onClose;
|
|
190
217
|
if (cb.onDelete) handlers.onDelete = cb.onDelete;
|
|
191
218
|
if (cb.onChecklistToggle) {
|
|
192
219
|
const toggle = cb.onChecklistToggle;
|
|
@@ -232,11 +259,14 @@ exports.hasActiveFilters = hasActiveFilters;
|
|
|
232
259
|
exports.isUnresolved = isUnresolved;
|
|
233
260
|
exports.matchMentionsInBody = matchMentionsInBody;
|
|
234
261
|
exports.nextChecklistStatus = nextChecklistStatus;
|
|
262
|
+
exports.resolveCommentStage = resolveCommentStage;
|
|
235
263
|
exports.resolveFacetOption = resolveFacetOption;
|
|
236
264
|
exports.resolveStatusConfig = resolveStatusConfig;
|
|
237
265
|
exports.selectAnchorThreads = selectAnchorThreads;
|
|
266
|
+
exports.selectCommentThreadsByStage = selectCommentThreadsByStage;
|
|
238
267
|
exports.selectUnresolvedThreads = selectUnresolvedThreads;
|
|
239
268
|
exports.sortReplies = sortReplies;
|
|
269
|
+
exports.statusForCommentStage = statusForCommentStage;
|
|
240
270
|
exports.toneToBadgeTone = toneToBadgeTone;
|
|
241
271
|
exports.truncatePlain = truncatePlain;
|
|
242
272
|
//# 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 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((root) => root.status == null || isUnresolved(config, root.status))\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;\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.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":";;;AAqBO,SAAS,kBAAkB,QAAsC;;AACtE,WAAO,sCAAQ,SAAR,mBAAc,WAAU;AACjC;AAGO,SAAS,gBAAgB,MAA0C;AACxE,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,oBACd,QACA,OACiC;AACjC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AACtD;AAGO,SAAS,mBACd,OACA,OACgC;AAChC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AACpD;AAGO,SAAS,mBAAmB,QAA2C;;AAC5E,QAAM,aAAa,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU;AAC3D,UAAQ,mBAAc,OAAO,SAAS,CAAC,MAA/B,mBAAmC;AAC7C;AAGO,SAAS,aACd,QACA,QACS;;AACT,WAAO,yBAAoB,QAAQ,MAAM,MAAlC,mBAAqC,eAAc;AAC5D;AAGO,SAAS,SAAS,UAAgC;AACvD,SAAO,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAC3C;AAGO,SAAS,cAAc,UAA6C;AACzE,QAAM,0BAAU,IAAA;AAChB,aAAW,SAAS,UAAU;AAC5B,QAAI,CAAC,MAAM,SAAU;AACrB,UAAM,QAAQ,IAAI,IAAI,MAAM,QAAQ,KAAK,CAAA;AACzC,UAAM,KAAK,KAAK;AAChB,QAAI,IAAI,MAAM,UAAU,KAAK;AAAA,EAC/B;AACA,SAAO;AACT;AAOO,SAAS,oBACd,UACA,QACW;AACX,QAAM,QAAQ,SAAS;AAAA,IACrB,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,UAAUA,kCAAqB;AAAA,EAAA;AAE1D,QAAM,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO,EAAE,EAAE,CAAC,CAAC;AAClD,QAAM,cAAyB,CAAA;AAE/B,WAAS,OAAO,MAAM,QAAQ;AAC5B,WAAO;AACP,eAAW,KAAK,UAAU;AACxB,UAAI,CAAC,EAAE,YAAY,IAAI,IAAI,OAAO,EAAE,EAAE,CAAC,EAAG;AAC1C,UAAI,CAAC,IAAI,IAAI,OAAO,EAAE,QAAQ,CAAC,EAAG;AAClC,UAAI,IAAI,OAAO,EAAE,EAAE,CAAC;AACpB,kBAAY,KAAK,CAAC;AAClB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC,GAAG,OAAO,GAAG,WAAW;AAClC;AAMO,SAAS,wBACd,UACA,QACW;AACX,QAAM,OAAO,IAAI;AAAA,IACf,SAAS,QAAQ,EACd,OAAO,CAAC,SAAS,KAAK,UAAU,QAAQ,aAAa,QAAQ,KAAK,MAAM,CAAC,EACzE,IAAI,CAAC,SAAS,OAAO,KAAK,EAAE,CAAC;AAAA,EAAA;AAElC,SAAO,SAAS,OAAO,CAAC,MAAM,KAAK,IAAI,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;AACpE;AAGO,SAAS,YAAY,SAA+B;AACzD,SAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AACjC,QAAI,EAAE,cAAc,EAAE;AACpB,aAAO,EAAE,UAAU,cAAc,EAAE,SAAS;AAC9C,WAAO,OAAO,EAAE,EAAE,EAAE,cAAc,OAAO,EAAE,EAAE,CAAC;AAAA,EAChD,CAAC;AACH;AAGO,SAAS,aACd,UACA,UACwB;;AACxB,QAAM,0BAAU,IAAA;AAChB,aAAW,QAAQ,SAAS,QAAQ,GAAG;AACrC,UAAM,QAAM,UAAK,WAAL,mBAAc,cAAa;AACvC,UAAM,QAAQ,IAAI,IAAI,GAAG,KAAK,CAAA;AAC9B,UAAM,KAAK,IAAI;AACf,QAAI,IAAI,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAGO,SAAS,mBACd,UAC+B;AAC/B,QAAM,SAAwC,CAAA;AAC9C,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,EAAE,UAAUA,aAAAA;AACxB,WAAO,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK;AAAA,EACrC;AACA,SAAO;AACT;AAGO,SAAS,iBACd,UAC0C;AAC1C,QAAM,UAA4C,CAAA;AAClD,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,EAAE,UAAUA,aAAAA;AACxB,KAAC,gCAAiB,CAAA,IAAI,KAAK,CAAC;AAAA,EAC9B;AACA,QAAM,OAAiD,CAAA;AACvD,aAAW,CAAC,QAAQ,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACrD,UAAM,UAAU;AAAA,MACd,GAAG,IAAI;AAAA,QACL,MAAM,IAAI,CAAC,MAAA;;AAAM,yBAAE,WAAF,mBAAU;AAAA,SAAI,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAAA;AAAA,IACjE;AAEF,UAAM,SAAS,MAAM;AAAA,MAAO,CAAC,GAAG,OAC7B,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,IAAI;AAAA,IAAA;AAEpE,SAAK,MAAM,IAAI;AAAA,MACb,OAAO,MAAM;AAAA,MACb;AAAA,MACA,GAAI,OAAO,UAAU,OAAO,EAAE,cAAc,OAAO,WAAW,CAAA;AAAA,IAAC;AAAA,EAEnE;AACA,SAAO;AACT;AAGO,SAAS,sBACd,QACA,QACA,YAAY,KACJ;AACR,MAAI,QAAQ;AACZ,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,QAAQ,UAAU,IAAI,WAAW,SAAS,SAAS,EAAG,UAAS;AAAA,EACrE;AACA,SAAO;AACT;AAGO,SAAS,oBACd,SACA,QAAQC,sCACA;AACR,QAAM,MAAM,MAAM,QAAQ,OAAO;AACjC,MAAI,QAAQ,GAAI,QAAO,MAAM,CAAC,KAAK;AACnC,SAAO,OAAO,MAAM,KAAK,MAAM,MAAM,KAAK;AAC5C;AAGO,SAAS,cAAc,MAAc,MAAM,IAAY;AAC5D,QAAM,QAAQ,KAAK,QAAQ,mBAAmB,EAAE,EAAE,KAAA;AAClD,SAAO,MAAM,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM;AAC1D;AAEA,SAAS,aAAa,SAAsC;;AAC1D,WAAO,aAAQ,WAAR,mBAAgB,SAAQ;AACjC;AAOO,SAAS,oBACd,UACA,SACA,QACW;AACX,QAAM,aAAa,OAAO,UAAU,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,GAAG;AACxD,QAAM,qCAAqB,IAAA;AAE3B,aAAW,QAAQ,SAAS,QAAQ,GAAG;AACrC,QACE,QAAQ,SAAS,OAAO,MACvB,KAAK,UAAU,QAAQ,CAAC,QAAQ,SAAS,IAAI,KAAK,MAAM,IACzD;AACA;AAAA,IACF;AACA,QACE,QAAQ,cACR,QAAQ,eAAe,SACvB,aAAa,IAAI,MAAM,QAAQ,YAC/B;AACA;AAAA,IACF;AACA,UAAM,aAAa,UAAU,MAAM,CAAC,QAAQ;;AAC1C,YAAM,WAAW,QAAQ,OAAO,GAAG;AACnC,UAAI,CAAC,YAAY,SAAS,SAAS,EAAG,QAAO;AAC7C,YAAM,SAAQ,UAAK,WAAL,mBAAc;AAC5B,aAAO,SAAS,QAAQ,SAAS,IAAI,KAAK;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,WAAY;AACjB,mBAAe,IAAI,KAAK,EAAE;AAAA,EAC5B;AAEA,SAAO,SAAS;AAAA,IAAO,CAAC,MACtB,EAAE,WAAW,eAAe,IAAI,EAAE,QAAQ,IAAI,eAAe,IAAI,EAAE,EAAE;AAAA,EAAA;AAEzE;AAGO,SAAS,iBAAiB,SAAkC;AACjE,MAAI,QAAQ,SAAS,OAAO,EAAG,QAAO;AACtC,MAAI,QAAQ,cAAc,QAAQ,eAAe,MAAO,QAAO;AAC/D,SAAO,OAAO,OAAO,QAAQ,MAAM,EAAE,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC;AACjE;AAGO,SAAS,sBAAsC;AACpD,SAAO,EAAE,UAAU,oBAAI,IAAA,GAAO,QAAQ,CAAA,GAAI,YAAY,MAAA;AACxD;AAGO,SAAS,oBACd,MACA,cACkB;AAClB,QAAM,QAAQ,KAAK,YAAA;AACnB,SAAO,aACJ,OAAO,CAAC,MAAM,MAAM,SAAS,IAAI,EAAE,KAAK,YAAA,CAAa,EAAE,CAAC,EACxD,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,EAAE,KAAA,EAAO;AAC1D;AAgBO,SAAS,wBACd,UACA,QACA,IACoB;AACpB,QAAM,WAA+B,CAAA;AACrC,MAAI,GAAG,eAAgB,UAAS,iBAAiB,GAAG;AACpD,MAAI,GAAG,SAAU,UAAS,WAAW,GAAG;AACxC,MAAI,GAAG,mBAAmB;AACxB,UAAM,SAAS,GAAG;AAClB,aAAS,oBAAoB,CAAC,IAAI,UAAU;;AAC1C,YAAM,QAAO,oBAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAhC,mBAAmC,cAAnC,mBAA+C;AAC5D,UAAI,CAAC,KAAM;AACX,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA,oBAAoB,KAAK,QAAQ,OAAO,oBAAoB;AAAA,MAAA;AAAA,IAEhE;AAAA,EACF;AACA,MAAI,GAAG,SAAS;AACd,UAAM,QAAQ,GAAG;AACjB,UAAM,YAAY,GAAG;AACrB,aAAS,UAAU,OAAO,QAAQ,SAAS;AACzC,YAAM,WAAW,oBAAoB,MAAM,OAAO,gBAAgB,CAAA,CAAE;AACpE,YAAM,MAAM;AAAA,QACV,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,QAAQ,OAAO,UAAU;AAAA,QACzB,GAAI,SAAS,SAAS,IAAI,EAAE,SAAA,IAAa,CAAA;AAAA,MAAC,CAC3C;AACD,iBAAW,WAAW,SAAU,wCAAY,SAAS,EAAE;IACzD;AAAA,EACF;AACA,SAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { BadgeTone } from '../data/Badge';
|
|
2
|
-
import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentTone } from './comment-types';
|
|
2
|
+
import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentStatusStage, 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,6 +12,10 @@ 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;
|
|
15
19
|
/** Returns the root (non-reply) comments in input order. */
|
|
16
20
|
export declare function getRoots(comments: Comment[]): Comment[];
|
|
17
21
|
/** Indexes replies by their parent id. */
|
|
@@ -27,6 +31,8 @@ export declare function selectAnchorThreads(comments: Comment[], anchor: Comment
|
|
|
27
31
|
* been acted on, so it counts as unresolved; replies follow their root.
|
|
28
32
|
*/
|
|
29
33
|
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[];
|
|
30
36
|
/** Stable reply ordering: by creation time, then id. */
|
|
31
37
|
export declare function sortReplies(replies: Comment[]): Comment[];
|
|
32
38
|
/** Groups root comments by the value of a facet key (missing → ""). */
|
|
@@ -55,7 +61,8 @@ export declare function emptyCommentFilters(): CommentFilters;
|
|
|
55
61
|
export declare function matchMentionsInBody(body: string, mentionables: CommentMentionable[]): CommentMention[];
|
|
56
62
|
/** Per-id handlers consumed by CommentThreadList. */
|
|
57
63
|
export type ThreadListHandlers = {
|
|
58
|
-
onUpdateStatus?: (id: string, status: string) => void
|
|
64
|
+
onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
|
|
65
|
+
onClose?: (id: string) => void | Promise<void>;
|
|
59
66
|
onChecklistToggle?: (id: string, index: number) => void;
|
|
60
67
|
onDelete?: (id: string) => void;
|
|
61
68
|
onReply?: (parent: Comment, body: string) => void | Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAEjB,MAAM,iBAAiB,CAAC;AAEzB,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAEtE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAexE;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,mBAAmB,GAAG,SAAS,CAGjC;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,kBAAkB,GAAG,SAAS,CAGhC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAG5E;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAET;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAEvD;AAED,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CASzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,EAAE,CAkBX;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,22 @@ 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
|
+
}
|
|
39
55
|
function getRoots(comments) {
|
|
40
56
|
return comments.filter((c) => !c.parentId);
|
|
41
57
|
}
|
|
@@ -69,10 +85,20 @@ function selectAnchorThreads(comments, anchor) {
|
|
|
69
85
|
}
|
|
70
86
|
function selectUnresolvedThreads(comments, config) {
|
|
71
87
|
const keep = new Set(
|
|
72
|
-
getRoots(comments).filter(
|
|
88
|
+
getRoots(comments).filter(
|
|
89
|
+
(root) => root.status == null || isUnresolved(config, root.status)
|
|
90
|
+
).map((root) => String(root.id))
|
|
73
91
|
);
|
|
74
92
|
return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
|
|
75
93
|
}
|
|
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
|
+
}
|
|
76
102
|
function sortReplies(replies) {
|
|
77
103
|
return [...replies].sort((a, b) => {
|
|
78
104
|
if (a.createdAt !== b.createdAt)
|
|
@@ -185,6 +211,7 @@ function matchMentionsInBody(body, mentionables) {
|
|
|
185
211
|
function buildThreadListHandlers(comments, config, cb) {
|
|
186
212
|
const handlers = {};
|
|
187
213
|
if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;
|
|
214
|
+
if (cb.onClose) handlers.onClose = cb.onClose;
|
|
188
215
|
if (cb.onDelete) handlers.onDelete = cb.onDelete;
|
|
189
216
|
if (cb.onChecklistToggle) {
|
|
190
217
|
const toggle = cb.onChecklistToggle;
|
|
@@ -231,11 +258,14 @@ export {
|
|
|
231
258
|
isUnresolved,
|
|
232
259
|
matchMentionsInBody,
|
|
233
260
|
nextChecklistStatus,
|
|
261
|
+
resolveCommentStage,
|
|
234
262
|
resolveFacetOption,
|
|
235
263
|
resolveStatusConfig,
|
|
236
264
|
selectAnchorThreads,
|
|
265
|
+
selectCommentThreadsByStage,
|
|
237
266
|
selectUnresolvedThreads,
|
|
238
267
|
sortReplies,
|
|
268
|
+
statusForCommentStage,
|
|
239
269
|
toneToBadgeTone,
|
|
240
270
|
truncatePlain
|
|
241
271
|
};
|
|
@@ -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 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((root) => root.status == null || isUnresolved(config, root.status))\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;\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.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 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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"}
|
package/dist/comments.cjs
CHANGED
|
@@ -33,11 +33,14 @@ exports.hasActiveFilters = commentUtils.hasActiveFilters;
|
|
|
33
33
|
exports.isUnresolved = commentUtils.isUnresolved;
|
|
34
34
|
exports.matchMentionsInBody = commentUtils.matchMentionsInBody;
|
|
35
35
|
exports.nextChecklistStatus = commentUtils.nextChecklistStatus;
|
|
36
|
+
exports.resolveCommentStage = commentUtils.resolveCommentStage;
|
|
36
37
|
exports.resolveFacetOption = commentUtils.resolveFacetOption;
|
|
37
38
|
exports.resolveStatusConfig = commentUtils.resolveStatusConfig;
|
|
38
39
|
exports.selectAnchorThreads = commentUtils.selectAnchorThreads;
|
|
40
|
+
exports.selectCommentThreadsByStage = commentUtils.selectCommentThreadsByStage;
|
|
39
41
|
exports.selectUnresolvedThreads = commentUtils.selectUnresolvedThreads;
|
|
40
42
|
exports.sortReplies = commentUtils.sortReplies;
|
|
43
|
+
exports.statusForCommentStage = commentUtils.statusForCommentStage;
|
|
41
44
|
exports.toneToBadgeTone = commentUtils.toneToBadgeTone;
|
|
42
45
|
exports.truncatePlain = commentUtils.truncatePlain;
|
|
43
46
|
exports.CommentMarkdown = CommentMarkdown.CommentMarkdown;
|
package/dist/comments.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/comments.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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, resolveFacetOption, resolveStatusConfig, selectAnchorThreads, selectUnresolvedThreads, sortReplies, toneToBadgeTone, truncatePlain } from "./comments/comment-utils.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";
|
|
3
3
|
import { CommentMarkdown } from "./comments/CommentMarkdown.js";
|
|
4
4
|
import { CommentAuthorAvatar } from "./comments/CommentAuthor.js";
|
|
5
5
|
import { MentionTextarea } from "./comments/MentionTextarea.js";
|
|
@@ -46,11 +46,14 @@ export {
|
|
|
46
46
|
isUnresolved,
|
|
47
47
|
matchMentionsInBody,
|
|
48
48
|
nextChecklistStatus,
|
|
49
|
+
resolveCommentStage,
|
|
49
50
|
resolveFacetOption,
|
|
50
51
|
resolveStatusConfig,
|
|
51
52
|
selectAnchorThreads,
|
|
53
|
+
selectCommentThreadsByStage,
|
|
52
54
|
selectUnresolvedThreads,
|
|
53
55
|
sortReplies,
|
|
56
|
+
statusForCommentStage,
|
|
54
57
|
strictAnchorResolver,
|
|
55
58
|
toneToBadgeTone,
|
|
56
59
|
truncatePlain,
|
|
@@ -101,7 +101,10 @@ function ComboboxMenu({
|
|
|
101
101
|
mode: mode ?? "neutral",
|
|
102
102
|
togglePosition: "right",
|
|
103
103
|
interactive: false,
|
|
104
|
-
label:
|
|
104
|
+
label: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.LabelIcon, { icon: option.icon, className: "shrink-0 text-sm text-muted-foreground" }),
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
|
|
107
|
+
] }),
|
|
105
108
|
onModeChange: (next) => onSetMode(option.value, next),
|
|
106
109
|
className: "w-full justify-between"
|
|
107
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboboxMenu.cjs","sources":["../../src/components/ComboboxMenu.tsx"],"sourcesContent":["import { Fragment, type ReactNode, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon } from \"../data/Icon\";\nimport { UiCheck } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { ComboboxOption, ComboboxTriStateMode } from \"./combobox-types\";\nimport type { ComboboxMenuPosition } from \"../lib/combobox\";\n\nexport function ComboboxMenu({\n customEntry,\n filtered,\n floatingZ,\n footer,\n hasOptions,\n highlighted,\n isSelected,\n listId,\n listRef,\n loading,\n menuPos,\n modes,\n multiple,\n onHighlight,\n onSelect,\n onSetMode,\n tristate,\n}: {\n customEntry: ComboboxOption | null;\n filtered: ComboboxOption[];\n floatingZ: number;\n footer: ReactNode;\n /** Whether the control has an option set at all (before filtering). */\n hasOptions: boolean;\n highlighted: number;\n isSelected: (value: string) => boolean;\n listId: string | undefined;\n listRef: RefObject<HTMLDivElement | null>;\n loading: boolean | undefined;\n menuPos: ComboboxMenuPosition;\n modes: Record<string, ComboboxTriStateMode>;\n multiple: boolean;\n onHighlight: (index: number) => void;\n onSelect: (option: ComboboxOption) => void;\n onSetMode: (value: string, mode: string) => void;\n tristate: boolean;\n}) {\n const mobile = menuPos.strategy === \"mobile\";\n const listbox = (\n <div\n id={listId}\n ref={listRef}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n style={{\n position: mobile ? \"absolute\" : \"fixed\",\n ...(menuPos.top != null\n ? { top: menuPos.top }\n : { bottom: menuPos.bottom }),\n left: menuPos.left,\n ...(mobile ? { width: menuPos.width } : { minWidth: menuPos.width }),\n maxWidth: menuPos.maxWidth,\n maxHeight: menuPos.maxHeight,\n zIndex: mobile ? undefined : floatingZ,\n pointerEvents: mobile ? \"auto\" : undefined,\n }}\n className={cn(\n \"overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n !mobile && \"w-max\",\n )}\n >\n {loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n Loading…\n </div>\n )}\n {/* \"No results\" answers \"did my query match anything?\" — a control with\n no option set at all (a free tag list) was never asked the question, so\n it says nothing rather than reporting an empty search. */}\n {!loading && filtered.length === 0 && !customEntry && hasOptions && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n No results\n </div>\n )}\n {filtered.map((option, index) => {\n const selected = isSelected(option.value);\n const mode = tristate ? modes[option.value] : undefined;\n const showHeader =\n option.group != null && option.group !== filtered[index - 1]?.group;\n return (\n <Fragment key={option.value}>\n {showHeader && (\n <div\n role=\"presentation\"\n className=\"select-none px-2 pb-1 pt-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {option.group}\n </div>\n )}\n <div\n id={listId ? `${listId}-${index}` : undefined}\n role=\"option\"\n aria-selected={tristate ? mode != null : selected}\n aria-disabled={option.disabled}\n {...(mode != null\n ? {\n \"aria-label\": `${option.label}, ${\n mode === \"include\" ? \"included\" : \"excluded\"\n }`,\n }\n : {})}\n {...(option.title !== undefined ? { title: option.title } : {})}\n {...(tristate ? { \"data-filter-option\": option.value } : {})}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!tristate && !option.disabled) onSelect(option);\n }}\n {...(tristate\n ? {\n onClick: () => {\n if (!option.disabled) onSelect(option);\n },\n }\n : {})}\n onMouseEnter={() => onHighlight(index)}\n className={cn(\n \"flex cursor-pointer gap-2 rounded-sm px-2 py-1.5 text-sm\",\n option.description ? \"items-start\" : \"items-center\",\n index === highlighted && \"bg-accent\",\n selected && !tristate && \"font-medium\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {tristate ? (\n <div className=\"min-w-0 flex-1\">\n <FilterPill\n mode={mode ?? \"neutral\"}\n togglePosition=\"right\"\n interactive={false}\n label={option.label}\n onModeChange={(next) => onSetMode(option.value, next)}\n className=\"w-full justify-between\"\n />\n <ComboboxOptionDescription description={option.description} />\n </div>\n ) : (\n <>\n <Icon\n icon={UiCheck}\n className={cn(\n \"shrink-0 text-xs\",\n option.description && \"mt-1\",\n selected ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <LabelIcon\n icon={option.icon}\n className={cn(\n \"text-sm text-muted-foreground\",\n option.description && \"mt-1\",\n )}\n />\n <ComboboxOptionText option={option} />\n {option.trailing != null && (\n // Capped and truncating rather than `shrink-0`: in a menu\n // clamped by the viewport edge the label is what identifies\n // the option, so the preview yields first.\n <span className=\"ml-auto min-w-0 max-w-[55%] truncate pl-2 text-right text-xs text-muted-foreground\">\n {option.trailing}\n </span>\n )}\n </>\n )}\n </div>\n </Fragment>\n );\n })}\n {customEntry && (\n <div\n id={listId ? `${listId}-${filtered.length}` : undefined}\n role=\"option\"\n aria-selected={false}\n data-filter-add-custom={customEntry.value}\n title={customEntry.title}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onSelect(customEntry)}\n onMouseEnter={() => onHighlight(filtered.length)}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-primary\",\n highlighted === filtered.length && \"bg-accent\",\n )}\n >\n <LabelIcon\n icon={customEntry.icon}\n className=\"text-sm text-muted-foreground\"\n />\n <span className=\"min-w-0 truncate\">{customEntry.label}</span>\n </div>\n )}\n {footer != null && (\n <div className=\"select-none px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n );\n return createPortal(\n mobile ? (\n <div\n data-slot=\"combobox-viewport-layer\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: floatingZ,\n pointerEvents: \"none\",\n }}\n >\n {listbox}\n </div>\n ) : (\n listbox\n ),\n document.body,\n );\n}\n\nfunction ComboboxOptionText({ option }: { option: ComboboxOption }) {\n return (\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n <ComboboxOptionDescription description={option.description} />\n </span>\n );\n}\n\nfunction ComboboxOptionDescription({\n description,\n}: {\n description: string | undefined;\n}) {\n return description ? (\n <span className=\"mt-0.5 block whitespace-pre-line text-xs font-normal leading-4 text-muted-foreground\">\n {description}\n </span>\n ) : null;\n}\n"],"names":["jsxs","cn","jsx","Fragment","FilterPill","Icon","UiCheck","LabelIcon","createPortal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ComboboxMenu.cjs","sources":["../../src/components/ComboboxMenu.tsx"],"sourcesContent":["import { Fragment, type ReactNode, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon } from \"../data/Icon\";\nimport { UiCheck } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { ComboboxOption, ComboboxTriStateMode } from \"./combobox-types\";\nimport type { ComboboxMenuPosition } from \"../lib/combobox\";\n\nexport function ComboboxMenu({\n customEntry,\n filtered,\n floatingZ,\n footer,\n hasOptions,\n highlighted,\n isSelected,\n listId,\n listRef,\n loading,\n menuPos,\n modes,\n multiple,\n onHighlight,\n onSelect,\n onSetMode,\n tristate,\n}: {\n customEntry: ComboboxOption | null;\n filtered: ComboboxOption[];\n floatingZ: number;\n footer: ReactNode;\n /** Whether the control has an option set at all (before filtering). */\n hasOptions: boolean;\n highlighted: number;\n isSelected: (value: string) => boolean;\n listId: string | undefined;\n listRef: RefObject<HTMLDivElement | null>;\n loading: boolean | undefined;\n menuPos: ComboboxMenuPosition;\n modes: Record<string, ComboboxTriStateMode>;\n multiple: boolean;\n onHighlight: (index: number) => void;\n onSelect: (option: ComboboxOption) => void;\n onSetMode: (value: string, mode: string) => void;\n tristate: boolean;\n}) {\n const mobile = menuPos.strategy === \"mobile\";\n const listbox = (\n <div\n id={listId}\n ref={listRef}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n style={{\n position: mobile ? \"absolute\" : \"fixed\",\n ...(menuPos.top != null\n ? { top: menuPos.top }\n : { bottom: menuPos.bottom }),\n left: menuPos.left,\n ...(mobile ? { width: menuPos.width } : { minWidth: menuPos.width }),\n maxWidth: menuPos.maxWidth,\n maxHeight: menuPos.maxHeight,\n zIndex: mobile ? undefined : floatingZ,\n pointerEvents: mobile ? \"auto\" : undefined,\n }}\n className={cn(\n \"overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n !mobile && \"w-max\",\n )}\n >\n {loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n Loading…\n </div>\n )}\n {/* \"No results\" answers \"did my query match anything?\" — a control with\n no option set at all (a free tag list) was never asked the question, so\n it says nothing rather than reporting an empty search. */}\n {!loading && filtered.length === 0 && !customEntry && hasOptions && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n No results\n </div>\n )}\n {filtered.map((option, index) => {\n const selected = isSelected(option.value);\n const mode = tristate ? modes[option.value] : undefined;\n const showHeader =\n option.group != null && option.group !== filtered[index - 1]?.group;\n return (\n <Fragment key={option.value}>\n {showHeader && (\n <div\n role=\"presentation\"\n className=\"select-none px-2 pb-1 pt-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {option.group}\n </div>\n )}\n <div\n id={listId ? `${listId}-${index}` : undefined}\n role=\"option\"\n aria-selected={tristate ? mode != null : selected}\n aria-disabled={option.disabled}\n {...(mode != null\n ? {\n \"aria-label\": `${option.label}, ${\n mode === \"include\" ? \"included\" : \"excluded\"\n }`,\n }\n : {})}\n {...(option.title !== undefined ? { title: option.title } : {})}\n {...(tristate ? { \"data-filter-option\": option.value } : {})}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!tristate && !option.disabled) onSelect(option);\n }}\n {...(tristate\n ? {\n onClick: () => {\n if (!option.disabled) onSelect(option);\n },\n }\n : {})}\n onMouseEnter={() => onHighlight(index)}\n className={cn(\n \"flex cursor-pointer gap-2 rounded-sm px-2 py-1.5 text-sm\",\n option.description ? \"items-start\" : \"items-center\",\n index === highlighted && \"bg-accent\",\n selected && !tristate && \"font-medium\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {tristate ? (\n <div className=\"min-w-0 flex-1\">\n <FilterPill\n mode={mode ?? \"neutral\"}\n togglePosition=\"right\"\n interactive={false}\n label={\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <LabelIcon icon={option.icon} className=\"shrink-0 text-sm text-muted-foreground\" />\n <span className=\"truncate\">{option.label}</span>\n </span>\n }\n onModeChange={(next) => onSetMode(option.value, next)}\n className=\"w-full justify-between\"\n />\n <ComboboxOptionDescription description={option.description} />\n </div>\n ) : (\n <>\n <Icon\n icon={UiCheck}\n className={cn(\n \"shrink-0 text-xs\",\n option.description && \"mt-1\",\n selected ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <LabelIcon\n icon={option.icon}\n className={cn(\n \"text-sm text-muted-foreground\",\n option.description && \"mt-1\",\n )}\n />\n <ComboboxOptionText option={option} />\n {option.trailing != null && (\n // Capped and truncating rather than `shrink-0`: in a menu\n // clamped by the viewport edge the label is what identifies\n // the option, so the preview yields first.\n <span className=\"ml-auto min-w-0 max-w-[55%] truncate pl-2 text-right text-xs text-muted-foreground\">\n {option.trailing}\n </span>\n )}\n </>\n )}\n </div>\n </Fragment>\n );\n })}\n {customEntry && (\n <div\n id={listId ? `${listId}-${filtered.length}` : undefined}\n role=\"option\"\n aria-selected={false}\n data-filter-add-custom={customEntry.value}\n title={customEntry.title}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onSelect(customEntry)}\n onMouseEnter={() => onHighlight(filtered.length)}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-primary\",\n highlighted === filtered.length && \"bg-accent\",\n )}\n >\n <LabelIcon\n icon={customEntry.icon}\n className=\"text-sm text-muted-foreground\"\n />\n <span className=\"min-w-0 truncate\">{customEntry.label}</span>\n </div>\n )}\n {footer != null && (\n <div className=\"select-none px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n );\n return createPortal(\n mobile ? (\n <div\n data-slot=\"combobox-viewport-layer\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: floatingZ,\n pointerEvents: \"none\",\n }}\n >\n {listbox}\n </div>\n ) : (\n listbox\n ),\n document.body,\n );\n}\n\nfunction ComboboxOptionText({ option }: { option: ComboboxOption }) {\n return (\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n <ComboboxOptionDescription description={option.description} />\n </span>\n );\n}\n\nfunction ComboboxOptionDescription({\n description,\n}: {\n description: string | undefined;\n}) {\n return description ? (\n <span className=\"mt-0.5 block whitespace-pre-line text-xs font-normal leading-4 text-muted-foreground\">\n {description}\n </span>\n ) : null;\n}\n"],"names":["jsxs","cn","jsx","Fragment","FilterPill","LabelIcon","Icon","UiCheck","createPortal"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboboxMenu.d.ts","sourceRoot":"","sources":["../../src/components/ComboboxMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMjE,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,QAAQ,EACR,SAAS,EACT,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB,+
|
|
1
|
+
{"version":3,"file":"ComboboxMenu.d.ts","sourceRoot":"","sources":["../../src/components/ComboboxMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMjE,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,QAAQ,EACR,SAAS,EACT,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1C,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB,+BAuLA"}
|
|
@@ -2,7 +2,7 @@ import { jsxs, jsx, Fragment as Fragment$1 } from "react/jsx-runtime";
|
|
|
2
2
|
import { Fragment } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { FilterPill } from "../data/FilterPill.js";
|
|
5
|
-
import {
|
|
5
|
+
import { LabelIcon, Icon } from "../data/Icon.js";
|
|
6
6
|
import { cn } from "../lib/utils.js";
|
|
7
7
|
import { UiCheck } from "../icons/components/UiCheck.js";
|
|
8
8
|
function ComboboxMenu({
|
|
@@ -99,7 +99,10 @@ function ComboboxMenu({
|
|
|
99
99
|
mode: mode ?? "neutral",
|
|
100
100
|
togglePosition: "right",
|
|
101
101
|
interactive: false,
|
|
102
|
-
label:
|
|
102
|
+
label: /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [
|
|
103
|
+
/* @__PURE__ */ jsx(LabelIcon, { icon: option.icon, className: "shrink-0 text-sm text-muted-foreground" }),
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: option.label })
|
|
105
|
+
] }),
|
|
103
106
|
onModeChange: (next) => onSetMode(option.value, next),
|
|
104
107
|
className: "w-full justify-between"
|
|
105
108
|
}
|