@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,227 +1,114 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
|
-
import { Badge } from "../data/Badge.js";
|
|
5
4
|
import { Icon } from "../data/Icon.js";
|
|
6
5
|
import { DropdownMenu } from "../overlay/DropdownMenu.js";
|
|
7
6
|
import { parseTimestamp, formatRelativeTime } from "../data/cells/timestamp-format.js";
|
|
8
7
|
import { CommentAuthorAvatar } from "./CommentAuthor.js";
|
|
8
|
+
import { StatusChip, RatingChip, FacetBadges, ChecklistChips, RefChips } from "./CommentCardParts.js";
|
|
9
9
|
import { CommentMarkdown } from "./CommentMarkdown.js";
|
|
10
|
-
import {
|
|
10
|
+
import { authorDisplayName, truncatePlain, resolveCommentStage, statusForCommentStage } from "./comment-utils.js";
|
|
11
11
|
import { UiCheck } from "../icons/components/UiCheck.js";
|
|
12
|
-
import {
|
|
12
|
+
import { UiLock } from "../icons/components/UiLock.js";
|
|
13
|
+
import { UiRestart } from "../icons/components/UiRestart.js";
|
|
13
14
|
import { UiDotsVertical } from "../icons/components/UiDotsVertical.js";
|
|
14
|
-
import { UiThumbsUp } from "../icons/components/UiThumbsUp.js";
|
|
15
|
-
import { UiThumbsDown } from "../icons/components/UiThumbsDown.js";
|
|
16
|
-
import { UiClose } from "../icons/components/UiClose.js";
|
|
17
15
|
import { UiTrash } from "../icons/components/UiTrash.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
(candidate) => candidate.value.toLowerCase() === completionName || candidate.label.toLowerCase() === completionName
|
|
22
|
-
);
|
|
23
|
-
if (status) return status;
|
|
24
|
-
}
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
function completionActionLabel(status) {
|
|
28
|
-
return status.value.toLowerCase() === "closed" || status.label.toLowerCase() === "closed" ? "Close comment" : "Resolve comment";
|
|
29
|
-
}
|
|
30
|
-
function RatingChip({ rating }) {
|
|
31
|
-
if (!rating) return null;
|
|
32
|
-
const positive = rating === "positive";
|
|
33
|
-
return /* @__PURE__ */ jsx(
|
|
34
|
-
Badge,
|
|
35
|
-
{
|
|
36
|
-
variant: "soft",
|
|
37
|
-
tone: positive ? "success" : "danger",
|
|
38
|
-
size: "xs",
|
|
39
|
-
icon: positive ? UiThumbsUp : UiThumbsDown,
|
|
40
|
-
children: positive ? "Positive rating" : "Negative rating"
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
function StatusChip({
|
|
45
|
-
status,
|
|
46
|
-
config,
|
|
47
|
-
showLabel
|
|
48
|
-
}) {
|
|
49
|
-
const cfg = resolveStatusConfig(config, status);
|
|
50
|
-
if (!cfg) return null;
|
|
51
|
-
return /* @__PURE__ */ jsx(
|
|
52
|
-
Badge,
|
|
53
|
-
{
|
|
54
|
-
variant: "soft",
|
|
55
|
-
tone: toneToBadgeTone(cfg.tone),
|
|
56
|
-
size: "xs",
|
|
57
|
-
...cfg.icon ? { icon: cfg.icon } : {},
|
|
58
|
-
children: showLabel ? cfg.label : null
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
function FacetBadges({
|
|
63
|
-
comment,
|
|
64
|
-
config,
|
|
65
|
-
compact
|
|
66
|
-
}) {
|
|
67
|
-
const facets = config.facets ?? [];
|
|
68
|
-
const chips = facets.flatMap((facet) => {
|
|
69
|
-
var _a;
|
|
70
|
-
const value = (_a = comment.facets) == null ? void 0 : _a[facet.key];
|
|
71
|
-
const option = resolveFacetOption(facet, value);
|
|
72
|
-
if (!option) return [];
|
|
73
|
-
return [
|
|
74
|
-
/* @__PURE__ */ jsx(
|
|
75
|
-
Badge,
|
|
76
|
-
{
|
|
77
|
-
variant: "soft",
|
|
78
|
-
tone: toneToBadgeTone(option.tone),
|
|
79
|
-
size: "xs",
|
|
80
|
-
children: compact ? option.short ?? option.label : option.label
|
|
81
|
-
},
|
|
82
|
-
facet.key
|
|
83
|
-
)
|
|
84
|
-
];
|
|
85
|
-
});
|
|
86
|
-
return chips.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: chips }) : null;
|
|
87
|
-
}
|
|
88
|
-
function ChecklistChips({
|
|
89
|
-
comment,
|
|
90
|
-
onChecklistToggle
|
|
91
|
-
}) {
|
|
92
|
-
if (!comment.checklist || comment.checklist.length === 0) return null;
|
|
93
|
-
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: comment.checklist.map((item, i) => {
|
|
94
|
-
const done = /done|complete|resolved/i.test(item.status);
|
|
95
|
-
const active = /progress|doing/i.test(item.status);
|
|
96
|
-
return /* @__PURE__ */ jsx(
|
|
97
|
-
"button",
|
|
98
|
-
{
|
|
99
|
-
type: "button",
|
|
100
|
-
onClick: () => onChecklistToggle == null ? void 0 : onChecklistToggle(i),
|
|
101
|
-
disabled: !onChecklistToggle,
|
|
102
|
-
className: cn(
|
|
103
|
-
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity",
|
|
104
|
-
onChecklistToggle && "cursor-pointer hover:opacity-80",
|
|
105
|
-
done ? "bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300" : active ? "bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300" : "bg-muted text-muted-foreground"
|
|
106
|
-
),
|
|
107
|
-
children: item.label
|
|
108
|
-
},
|
|
109
|
-
i
|
|
110
|
-
);
|
|
111
|
-
}) });
|
|
112
|
-
}
|
|
113
|
-
function RefChips({ comment }) {
|
|
114
|
-
if (!comment.refs || comment.refs.length === 0) return null;
|
|
115
|
-
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: comment.refs.map((group, i) => /* @__PURE__ */ jsxs(
|
|
116
|
-
"span",
|
|
117
|
-
{
|
|
118
|
-
className: "inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
|
|
119
|
-
children: [
|
|
120
|
-
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
121
|
-
group.label,
|
|
122
|
-
":"
|
|
123
|
-
] }),
|
|
124
|
-
/* @__PURE__ */ jsx("span", { className: group.mono ? "font-mono" : void 0, children: group.items.join(", ") })
|
|
125
|
-
]
|
|
126
|
-
},
|
|
127
|
-
i
|
|
128
|
-
)) });
|
|
129
|
-
}
|
|
130
|
-
function statusMenuItems(comment, config, isReply, opts) {
|
|
16
|
+
import { UiCopy } from "../icons/components/UiCopy.js";
|
|
17
|
+
import { UiFullscreen } from "../icons/components/UiFullscreen.js";
|
|
18
|
+
function actionMenuItems(isReply, opts) {
|
|
131
19
|
const items = [];
|
|
20
|
+
if (opts.onDelete) {
|
|
21
|
+
items.push({ label: "Delete", icon: UiTrash, onSelect: opts.onDelete });
|
|
22
|
+
}
|
|
23
|
+
if (!isReply) items.push(...opts.statusActions ?? []);
|
|
24
|
+
if (opts.onCopy) {
|
|
25
|
+
items.push({ label: "Copy", icon: UiCopy, onSelect: opts.onCopy });
|
|
26
|
+
}
|
|
132
27
|
if (opts.onMaximize) {
|
|
133
28
|
items.push({
|
|
134
|
-
label: "
|
|
29
|
+
label: "Maximise",
|
|
135
30
|
icon: UiFullscreen,
|
|
136
31
|
onSelect: opts.onMaximize
|
|
137
32
|
});
|
|
138
33
|
}
|
|
139
|
-
if (opts.onCollapse) {
|
|
140
|
-
items.push({ label: "Collapse", icon: UiClose, onSelect: opts.onCollapse });
|
|
141
|
-
}
|
|
142
|
-
if (!isReply && opts.onUpdateStatus) {
|
|
143
|
-
for (const status of config.statuses) {
|
|
144
|
-
if (status.value === opts.completionStatus) continue;
|
|
145
|
-
const current = status.value === comment.status;
|
|
146
|
-
items.push({
|
|
147
|
-
label: /* @__PURE__ */ jsxs(
|
|
148
|
-
"span",
|
|
149
|
-
{
|
|
150
|
-
className: cn(
|
|
151
|
-
"inline-flex items-center gap-1.5",
|
|
152
|
-
current && "font-semibold"
|
|
153
|
-
),
|
|
154
|
-
children: [
|
|
155
|
-
/* @__PURE__ */ jsx(StatusChip, { status: status.value, config }),
|
|
156
|
-
status.label
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
),
|
|
160
|
-
onSelect: () => {
|
|
161
|
-
var _a;
|
|
162
|
-
return (_a = opts.onUpdateStatus) == null ? void 0 : _a.call(opts, status.value);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (opts.onDelete) {
|
|
168
|
-
items.push({ label: "Delete", icon: UiTrash, onSelect: opts.onDelete });
|
|
169
|
-
}
|
|
170
34
|
return items;
|
|
171
35
|
}
|
|
172
|
-
function IconAction({
|
|
173
|
-
icon,
|
|
174
|
-
label,
|
|
175
|
-
onClick,
|
|
176
|
-
className
|
|
177
|
-
}) {
|
|
178
|
-
return /* @__PURE__ */ jsx(
|
|
179
|
-
"button",
|
|
180
|
-
{
|
|
181
|
-
type: "button",
|
|
182
|
-
onClick,
|
|
183
|
-
"aria-label": label,
|
|
184
|
-
title: label,
|
|
185
|
-
className: cn(
|
|
186
|
-
"rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
187
|
-
className
|
|
188
|
-
),
|
|
189
|
-
children: /* @__PURE__ */ jsx(Icon, { icon, className: "text-sm" })
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
36
|
function CommentBody({
|
|
194
37
|
comment,
|
|
195
38
|
config,
|
|
196
39
|
renderBody,
|
|
197
40
|
onUpdateStatus,
|
|
41
|
+
onClose,
|
|
198
42
|
onDelete,
|
|
43
|
+
onCopy,
|
|
199
44
|
onReply,
|
|
200
45
|
onChecklistToggle,
|
|
201
|
-
onCollapse,
|
|
202
46
|
onMaximize
|
|
203
47
|
}) {
|
|
204
|
-
const [
|
|
48
|
+
const [actionError, setActionError] = useState("");
|
|
205
49
|
const isReply = Boolean(comment.parentId);
|
|
206
50
|
const date = parseTimestamp(comment.createdAt);
|
|
207
|
-
const
|
|
208
|
-
const
|
|
51
|
+
const resolved = statusForCommentStage(config, "resolved");
|
|
52
|
+
const active = statusForCommentStage(config, "active");
|
|
53
|
+
const closed = statusForCommentStage(config, "closed");
|
|
54
|
+
const stage = resolveCommentStage(config, comment.status);
|
|
209
55
|
const updateStatus = async (status) => {
|
|
210
|
-
|
|
56
|
+
setActionError("");
|
|
211
57
|
try {
|
|
212
58
|
await (onUpdateStatus == null ? void 0 : onUpdateStatus(status));
|
|
213
59
|
} catch (error) {
|
|
214
|
-
|
|
215
|
-
error instanceof Error ? error.message : "Unexpected error"
|
|
60
|
+
setActionError(
|
|
61
|
+
`Couldn't update comment: ${error instanceof Error ? error.message : "Unexpected error"}`
|
|
216
62
|
);
|
|
217
63
|
}
|
|
218
64
|
};
|
|
219
|
-
const
|
|
220
|
-
|
|
65
|
+
const close = async () => {
|
|
66
|
+
setActionError("");
|
|
67
|
+
try {
|
|
68
|
+
await (onClose == null ? void 0 : onClose());
|
|
69
|
+
} catch (error) {
|
|
70
|
+
setActionError(
|
|
71
|
+
`Couldn't close comment: ${error instanceof Error ? error.message : "Unexpected error"}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const copy = async () => {
|
|
76
|
+
setActionError("");
|
|
77
|
+
try {
|
|
78
|
+
await (onCopy == null ? void 0 : onCopy());
|
|
79
|
+
} catch (error) {
|
|
80
|
+
setActionError(
|
|
81
|
+
`Couldn't copy comment: ${error instanceof Error ? error.message : "Unexpected error"}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const menuItems = actionMenuItems(isReply, {
|
|
221
86
|
...onDelete ? { onDelete } : {},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
87
|
+
statusActions: [
|
|
88
|
+
...stage === "active" && onUpdateStatus && resolved ? [
|
|
89
|
+
{
|
|
90
|
+
label: "Resolve",
|
|
91
|
+
icon: UiCheck,
|
|
92
|
+
onSelect: () => void updateStatus(resolved.value)
|
|
93
|
+
}
|
|
94
|
+
] : [],
|
|
95
|
+
...stage === "resolved" && onClose && closed ? [
|
|
96
|
+
{
|
|
97
|
+
label: "Close",
|
|
98
|
+
icon: UiLock,
|
|
99
|
+
onSelect: () => void close()
|
|
100
|
+
}
|
|
101
|
+
] : [],
|
|
102
|
+
...(stage === "resolved" || stage === "closed") && onUpdateStatus && active ? [
|
|
103
|
+
{
|
|
104
|
+
label: "Reopen",
|
|
105
|
+
icon: UiRestart,
|
|
106
|
+
onSelect: () => void updateStatus(active.value)
|
|
107
|
+
}
|
|
108
|
+
] : []
|
|
109
|
+
],
|
|
110
|
+
...onCopy ? { onCopy: () => void copy() } : {},
|
|
111
|
+
...onMaximize ? { onMaximize } : {}
|
|
225
112
|
});
|
|
226
113
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
|
|
227
114
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
|
|
@@ -239,50 +126,37 @@ function CommentBody({
|
|
|
239
126
|
)
|
|
240
127
|
] }),
|
|
241
128
|
!isReply && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap gap-1", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(StatusChip, { comment, config }),
|
|
242
130
|
/* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
|
|
243
131
|
/* @__PURE__ */ jsx(FacetBadges, { comment, config })
|
|
244
132
|
] })
|
|
245
133
|
] }),
|
|
246
|
-
/* @__PURE__ */
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
role: "button",
|
|
265
|
-
tabIndex: 0,
|
|
266
|
-
"aria-label": "Comment actions",
|
|
267
|
-
className: "inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
268
|
-
children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
|
|
269
|
-
}
|
|
270
|
-
),
|
|
271
|
-
items: menuItems
|
|
272
|
-
}
|
|
273
|
-
)
|
|
274
|
-
] })
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-0.5", children: menuItems.length > 0 && /* @__PURE__ */ jsx(
|
|
135
|
+
DropdownMenu,
|
|
136
|
+
{
|
|
137
|
+
align: "right",
|
|
138
|
+
menuClassName: "min-w-[150px]",
|
|
139
|
+
trigger: /* @__PURE__ */ jsx(
|
|
140
|
+
"span",
|
|
141
|
+
{
|
|
142
|
+
role: "button",
|
|
143
|
+
tabIndex: 0,
|
|
144
|
+
"aria-label": "Comment actions",
|
|
145
|
+
className: "inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
146
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
items: menuItems
|
|
150
|
+
}
|
|
151
|
+
) })
|
|
275
152
|
] }),
|
|
276
153
|
comment.body.trim() && /* @__PURE__ */ jsx("div", { className: "text-[13px] leading-5 text-foreground", children: renderBody ? renderBody(comment.body) : /* @__PURE__ */ jsx(CommentMarkdown, { text: comment.body }) }),
|
|
277
|
-
|
|
154
|
+
actionError && /* @__PURE__ */ jsx(
|
|
278
155
|
"div",
|
|
279
156
|
{
|
|
280
157
|
role: "alert",
|
|
281
158
|
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
|
|
282
|
-
children:
|
|
283
|
-
"Couldn't update comment: ",
|
|
284
|
-
statusError
|
|
285
|
-
]
|
|
159
|
+
children: actionError
|
|
286
160
|
}
|
|
287
161
|
),
|
|
288
162
|
!isReply && /* @__PURE__ */ jsx(
|
|
@@ -305,31 +179,41 @@ function CommentBody({
|
|
|
305
179
|
] });
|
|
306
180
|
}
|
|
307
181
|
function CommentCard(props) {
|
|
308
|
-
const { comment, config, compact, defaultExpanded
|
|
182
|
+
const { comment, config, compact, defaultExpanded } = props;
|
|
309
183
|
const [expanded, setExpanded] = useState(defaultExpanded ?? false);
|
|
310
|
-
const [
|
|
184
|
+
const [collapsedActionError, setCollapsedActionError] = useState("");
|
|
311
185
|
const isReply = Boolean(comment.parentId);
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (!completion) return;
|
|
317
|
-
setCollapsedStatusError("");
|
|
186
|
+
const body = /* @__PURE__ */ jsx(CommentBody, { ...props });
|
|
187
|
+
async function runCollapsedAction(action) {
|
|
188
|
+
var _a, _b;
|
|
189
|
+
setCollapsedActionError("");
|
|
318
190
|
try {
|
|
319
|
-
|
|
191
|
+
if (action === "close") {
|
|
192
|
+
await ((_a = props.onClose) == null ? void 0 : _a.call(props));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const next = statusForCommentStage(
|
|
196
|
+
config,
|
|
197
|
+
action === "resolve" ? "resolved" : "active"
|
|
198
|
+
);
|
|
199
|
+
if (next) await ((_b = props.onUpdateStatus) == null ? void 0 : _b.call(props, next.value));
|
|
320
200
|
} catch (error) {
|
|
321
|
-
|
|
322
|
-
error instanceof Error ? error.message : "Unexpected error"
|
|
201
|
+
setCollapsedActionError(
|
|
202
|
+
`${action === "close" ? "Couldn't close comment" : "Couldn't update comment"}: ${error instanceof Error ? error.message : "Unexpected error"}`
|
|
323
203
|
);
|
|
324
204
|
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
205
|
+
}
|
|
206
|
+
async function copyCollapsed() {
|
|
207
|
+
var _a;
|
|
208
|
+
setCollapsedActionError("");
|
|
209
|
+
try {
|
|
210
|
+
await ((_a = props.onCopy) == null ? void 0 : _a.call(props));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
setCollapsedActionError(
|
|
213
|
+
`Couldn't copy comment: ${error instanceof Error ? error.message : "Unexpected error"}`
|
|
214
|
+
);
|
|
331
215
|
}
|
|
332
|
-
|
|
216
|
+
}
|
|
333
217
|
if (!expanded) {
|
|
334
218
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
335
219
|
/* @__PURE__ */ jsxs(
|
|
@@ -356,27 +240,11 @@ function CommentCard(props) {
|
|
|
356
240
|
/* @__PURE__ */ jsx(CommentAuthorAvatar, { author: comment.author, bare: true }),
|
|
357
241
|
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] font-semibold text-foreground", children: authorDisplayName(comment.author) }),
|
|
358
242
|
!isReply && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
243
|
+
/* @__PURE__ */ jsx(StatusChip, { comment, config }),
|
|
359
244
|
/* @__PURE__ */ jsx(RatingChip, { rating: comment.rating }),
|
|
360
245
|
/* @__PURE__ */ jsx(FacetBadges, { comment, config, compact: true })
|
|
361
246
|
] }),
|
|
362
247
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[12px] text-foreground/80", children: comment.body.trim() ? truncatePlain(comment.body) : comment.rating === "positive" ? "Positive rating" : comment.rating === "negative" ? "Negative rating" : "Empty comment" }),
|
|
363
|
-
canComplete && /* @__PURE__ */ jsx(
|
|
364
|
-
"span",
|
|
365
|
-
{
|
|
366
|
-
role: "presentation",
|
|
367
|
-
onClick: (e) => e.stopPropagation(),
|
|
368
|
-
onKeyDown: (e) => e.stopPropagation(),
|
|
369
|
-
children: /* @__PURE__ */ jsx(
|
|
370
|
-
IconAction,
|
|
371
|
-
{
|
|
372
|
-
icon: UiCheck,
|
|
373
|
-
label: completionActionLabel(completion),
|
|
374
|
-
onClick: () => void completeCollapsed(),
|
|
375
|
-
className: "hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300"
|
|
376
|
-
}
|
|
377
|
-
)
|
|
378
|
-
}
|
|
379
|
-
),
|
|
380
248
|
/* @__PURE__ */ jsx(
|
|
381
249
|
"span",
|
|
382
250
|
{
|
|
@@ -397,16 +265,34 @@ function CommentCard(props) {
|
|
|
397
265
|
children: /* @__PURE__ */ jsx(Icon, { icon: UiDotsVertical, className: "text-sm" })
|
|
398
266
|
}
|
|
399
267
|
),
|
|
400
|
-
items:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
268
|
+
items: actionMenuItems(isReply, {
|
|
269
|
+
...props.onDelete ? { onDelete: props.onDelete } : {},
|
|
270
|
+
statusActions: [
|
|
271
|
+
...resolveCommentStage(config, comment.status) === "active" && props.onUpdateStatus && statusForCommentStage(config, "resolved") ? [
|
|
272
|
+
{
|
|
273
|
+
label: "Resolve",
|
|
274
|
+
icon: UiCheck,
|
|
275
|
+
onSelect: () => void runCollapsedAction("resolve")
|
|
276
|
+
}
|
|
277
|
+
] : [],
|
|
278
|
+
...resolveCommentStage(config, comment.status) === "resolved" && props.onClose && statusForCommentStage(config, "closed") ? [
|
|
279
|
+
{
|
|
280
|
+
label: "Close",
|
|
281
|
+
icon: UiLock,
|
|
282
|
+
onSelect: () => void runCollapsedAction("close")
|
|
283
|
+
}
|
|
284
|
+
] : [],
|
|
285
|
+
...(resolveCommentStage(config, comment.status) === "resolved" || resolveCommentStage(config, comment.status) === "closed") && props.onUpdateStatus && statusForCommentStage(config, "active") ? [
|
|
286
|
+
{
|
|
287
|
+
label: "Reopen",
|
|
288
|
+
icon: UiRestart,
|
|
289
|
+
onSelect: () => void runCollapsedAction("reopen")
|
|
290
|
+
}
|
|
291
|
+
] : []
|
|
292
|
+
],
|
|
293
|
+
...props.onCopy ? { onCopy: () => void copyCollapsed() } : {},
|
|
294
|
+
...props.onMaximize ? { onMaximize: props.onMaximize } : {}
|
|
295
|
+
})
|
|
410
296
|
}
|
|
411
297
|
)
|
|
412
298
|
}
|
|
@@ -414,15 +300,12 @@ function CommentCard(props) {
|
|
|
414
300
|
]
|
|
415
301
|
}
|
|
416
302
|
),
|
|
417
|
-
|
|
303
|
+
collapsedActionError && /* @__PURE__ */ jsx(
|
|
418
304
|
"div",
|
|
419
305
|
{
|
|
420
306
|
role: "alert",
|
|
421
307
|
className: "rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive",
|
|
422
|
-
children:
|
|
423
|
-
"Couldn't update comment: ",
|
|
424
|
-
collapsedStatusError
|
|
425
|
-
]
|
|
308
|
+
children: collapsedActionError
|
|
426
309
|
}
|
|
427
310
|
)
|
|
428
311
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Badge } from \"../data/Badge\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiClose,\n UiDotsVertical,\n UiFullscreen,\n UiThumbsDown,\n UiThumbsUp,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n isUnresolved,\n resolveFacetOption,\n resolveStatusConfig,\n toneToBadgeTone,\n truncatePlain,\n} from \"./comment-utils\";\nimport type {\n Comment,\n CommentConfig,\n CommentRating,\n CommentStatusConfig,\n} from \"./comment-types\";\n\nfunction completionStatus(config: CommentConfig) {\n for (const completionName of [\"resolved\", \"closed\"]) {\n const status = config.statuses.find(\n (candidate) =>\n candidate.value.toLowerCase() === completionName ||\n candidate.label.toLowerCase() === completionName,\n );\n if (status) return status;\n }\n return undefined;\n}\n\nfunction completionActionLabel(status: CommentStatusConfig) {\n return status.value.toLowerCase() === \"closed\" ||\n status.label.toLowerCase() === \"closed\"\n ? \"Close comment\"\n : \"Resolve comment\";\n}\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n /**\n * Open this card's thread in a larger view. Owned by the caller because a\n * card knows only itself, while maximizing shows the root and its replies.\n * Omit to drop the action (e.g. inside an already-maximized thread).\n */\n onMaximize?: () => void;\n};\n\nfunction RatingChip({ rating }: { rating: CommentRating | undefined }) {\n if (!rating) return null;\n const positive = rating === \"positive\";\n return (\n <Badge\n variant=\"soft\"\n tone={positive ? \"success\" : \"danger\"}\n size=\"xs\"\n icon={positive ? UiThumbsUp : UiThumbsDown}\n >\n {positive ? \"Positive rating\" : \"Negative rating\"}\n </Badge>\n );\n}\n\nfunction StatusChip({\n status,\n config,\n showLabel,\n}: {\n status: string | undefined;\n config: CommentConfig;\n showLabel?: boolean;\n}) {\n const cfg = resolveStatusConfig(config, status);\n if (!cfg) return null;\n return (\n <Badge\n variant=\"soft\"\n tone={toneToBadgeTone(cfg.tone)}\n size=\"xs\"\n {...(cfg.icon ? { icon: cfg.icon } : {})}\n >\n {showLabel ? cfg.label : null}\n </Badge>\n );\n}\n\nfunction FacetBadges({\n comment,\n config,\n compact,\n}: {\n comment: Comment;\n config: CommentConfig;\n compact?: boolean;\n}) {\n const facets = config.facets ?? [];\n const chips = facets.flatMap((facet) => {\n const value = comment.facets?.[facet.key];\n const option = resolveFacetOption(facet, value);\n if (!option) return [];\n return [\n <Badge\n key={facet.key}\n variant=\"soft\"\n tone={toneToBadgeTone(option.tone)}\n size=\"xs\"\n >\n {compact ? (option.short ?? option.label) : option.label}\n </Badge>,\n ];\n });\n return chips.length > 0 ? <>{chips}</> : null;\n}\n\nfunction ChecklistChips({\n comment,\n onChecklistToggle,\n}: {\n comment: Comment;\n onChecklistToggle?: (index: number) => void;\n}) {\n if (!comment.checklist || comment.checklist.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {comment.checklist.map((item, i) => {\n const done = /done|complete|resolved/i.test(item.status);\n const active = /progress|doing/i.test(item.status);\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => onChecklistToggle?.(i)}\n disabled={!onChecklistToggle}\n className={cn(\n \"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] transition-opacity\",\n onChecklistToggle && \"cursor-pointer hover:opacity-80\",\n done\n ? \"bg-green-100 text-green-800 line-through dark:bg-green-500/20 dark:text-green-300\"\n : active\n ? \"bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300\"\n : \"bg-muted text-muted-foreground\",\n )}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nfunction RefChips({ comment }: { comment: Comment }) {\n if (!comment.refs || comment.refs.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {comment.refs.map((group, i) => (\n <span\n key={i}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\"\n >\n <span className=\"font-medium\">{group.label}:</span>\n <span className={group.mono ? \"font-mono\" : undefined}>\n {group.items.join(\", \")}\n </span>\n </span>\n ))}\n </div>\n );\n}\n\nfunction statusMenuItems(\n comment: Comment,\n config: CommentConfig,\n isReply: boolean,\n opts: {\n onUpdateStatus?: (status: string) => void;\n onDelete?: () => void;\n onCollapse?: () => void;\n onMaximize?: () => void;\n completionStatus?: string;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onMaximize) {\n items.push({\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n if (opts.onCollapse) {\n items.push({ label: \"Collapse\", icon: UiClose, onSelect: opts.onCollapse });\n }\n if (!isReply && opts.onUpdateStatus) {\n for (const status of config.statuses) {\n if (status.value === opts.completionStatus) continue;\n const current = status.value === comment.status;\n items.push({\n label: (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5\",\n current && \"font-semibold\",\n )}\n >\n <StatusChip status={status.value} config={config} />\n {status.label}\n </span>\n ),\n onSelect: () => opts.onUpdateStatus?.(status.value),\n });\n }\n }\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n return items;\n}\n\nfunction IconAction({\n icon,\n label,\n onClick,\n className,\n}: {\n icon: StaticIconComponent;\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n \"rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\",\n className,\n )}\n >\n <Icon icon={icon} className=\"text-sm\" />\n </button>\n );\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onDelete,\n onReply,\n onChecklistToggle,\n onCollapse,\n onMaximize,\n}: CommentCardProps & { onCollapse?: () => void }) {\n const [statusError, setStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const updateStatus = async (status: string) => {\n setStatusError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n const menuItems = statusMenuItems(comment, config, isReply, {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status) => void updateStatus(status) }\n : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onCollapse ? { onCollapse } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n ...(completion ? { completionStatus: completion.value } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {canComplete && (\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void updateStatus(completion.value)}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n )}\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n {comment.body.trim() && (\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n )}\n {statusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {statusError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. Offers a Maximize action when `onMaximize` is supplied — the owner\n * enlarges the whole thread. Fully controlled — all mutations are delegated to\n * the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded, onMaximize } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [collapsedStatusError, setCollapsedStatusError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const completion = completionStatus(config);\n const canComplete =\n !isReply &&\n props.onUpdateStatus != null &&\n completion != null &&\n isUnresolved(config, comment.status);\n const completeCollapsed = async () => {\n if (!completion) return;\n setCollapsedStatusError(\"\");\n try {\n await props.onUpdateStatus?.(completion.value);\n } catch (error) {\n setCollapsedStatusError(\n error instanceof Error ? error.message : \"Unexpected error\",\n );\n }\n };\n\n const body = (\n <CommentBody\n {...props}\n {...(expanded ? { onCollapse: () => setExpanded(false) } : {})}\n />\n );\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <>\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} compact />\n </>\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {comment.body.trim()\n ? truncatePlain(comment.body)\n : comment.rating === \"positive\"\n ? \"Positive rating\"\n : comment.rating === \"negative\"\n ? \"Negative rating\"\n : \"Empty comment\"}\n </span>\n {canComplete && (\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <IconAction\n icon={UiCheck}\n label={completionActionLabel(completion)}\n onClick={() => void completeCollapsed()}\n className=\"hover:bg-green-100 hover:text-green-700 dark:hover:bg-green-500/20 dark:hover:text-green-300\"\n />\n </span>\n )}\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={[\n { label: \"Expand\", onSelect: () => setExpanded(true) },\n ...(onMaximize\n ? [\n {\n label: \"Maximize\",\n icon: UiFullscreen,\n onSelect: onMaximize,\n },\n ]\n : []),\n ]}\n />\n </span>\n </div>\n {collapsedStatusError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n Couldn't update comment: {collapsedStatusError}\n </div>\n )}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentCard.js","sources":["../../src/comments/CommentCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { DropdownMenu, type DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport {\n parseTimestamp,\n formatRelativeTime,\n} from \"../data/cells/timestamp-format\";\nimport {\n UiCheck,\n UiCopy,\n UiDotsVertical,\n UiFullscreen,\n UiLock,\n UiRestart,\n UiTrash,\n} from \"../icons\";\nimport { CommentAuthorAvatar } from \"./CommentAuthor\";\nimport {\n ChecklistChips,\n FacetBadges,\n RatingChip,\n RefChips,\n StatusChip,\n} from \"./CommentCardParts\";\nimport { CommentMarkdown } from \"./CommentMarkdown\";\nimport {\n authorDisplayName,\n resolveCommentStage,\n statusForCommentStage,\n truncatePlain,\n} from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentCardProps = {\n comment: Comment;\n config: CommentConfig;\n /** Tighter padding for dense rails. */\n compact?: boolean;\n /** Render expanded on first mount. */\n defaultExpanded?: boolean;\n /** Custom body renderer; defaults to the lightweight CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n /** Change this comment's status (roots only). */\n onUpdateStatus?: (status: string) => void | Promise<void>;\n /** Close a resolved root comment. */\n onClose?: () => void | Promise<void>;\n /** Delete this comment. */\n onDelete?: () => void;\n /** Copy this comment's whole thread in the caller's canonical format. */\n onCopy?: () => void | Promise<void>;\n /** Begin a reply to this comment. */\n onReply?: () => void;\n /** Advance the checklist item at `index` to its next status. */\n onChecklistToggle?: (index: number) => void;\n /**\n * Open this card's thread in a larger view. Owned by the caller because a\n * card knows only itself, while maximizing shows the root and its replies.\n * Omit to drop the action (e.g. inside an already-maximized thread).\n */\n onMaximize?: () => void;\n};\n\nfunction actionMenuItems(\n isReply: boolean,\n opts: {\n onDelete?: () => void;\n statusActions?: DropdownMenuItem[];\n onCopy?: () => void;\n onMaximize?: () => void;\n },\n): DropdownMenuItem[] {\n const items: DropdownMenuItem[] = [];\n if (opts.onDelete) {\n items.push({ label: \"Delete\", icon: UiTrash, onSelect: opts.onDelete });\n }\n if (!isReply) items.push(...(opts.statusActions ?? []));\n if (opts.onCopy) {\n items.push({ label: \"Copy\", icon: UiCopy, onSelect: opts.onCopy });\n }\n if (opts.onMaximize) {\n items.push({\n label: \"Maximise\",\n icon: UiFullscreen,\n onSelect: opts.onMaximize,\n });\n }\n return items;\n}\n\nfunction CommentBody({\n comment,\n config,\n renderBody,\n onUpdateStatus,\n onClose,\n onDelete,\n onCopy,\n onReply,\n onChecklistToggle,\n onMaximize,\n}: CommentCardProps) {\n const [actionError, setActionError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const date = parseTimestamp(comment.createdAt);\n const resolved = statusForCommentStage(config, \"resolved\");\n const active = statusForCommentStage(config, \"active\");\n const closed = statusForCommentStage(config, \"closed\");\n const stage = resolveCommentStage(config, comment.status);\n const updateStatus = async (status: string) => {\n setActionError(\"\");\n try {\n await onUpdateStatus?.(status);\n } catch (error) {\n setActionError(\n `Couldn't update comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const close = async () => {\n setActionError(\"\");\n try {\n await onClose?.();\n } catch (error) {\n setActionError(\n `Couldn't close comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const copy = async () => {\n setActionError(\"\");\n try {\n await onCopy?.();\n } catch (error) {\n setActionError(\n `Couldn't copy comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n };\n const menuItems = actionMenuItems(isReply, {\n ...(onDelete ? { onDelete } : {}),\n statusActions: [\n ...(stage === \"active\" && onUpdateStatus && resolved\n ? [\n {\n label: \"Resolve\",\n icon: UiCheck,\n onSelect: () => void updateStatus(resolved.value),\n },\n ]\n : []),\n ...(stage === \"resolved\" && onClose && closed\n ? [\n {\n label: \"Close\",\n icon: UiLock,\n onSelect: () => void close(),\n },\n ]\n : []),\n ...((stage === \"resolved\" || stage === \"closed\") &&\n onUpdateStatus &&\n active\n ? [\n {\n label: \"Reopen\",\n icon: UiRestart,\n onSelect: () => void updateStatus(active.value),\n },\n ]\n : []),\n ],\n ...(onCopy ? { onCopy: () => void copy() } : {}),\n ...(onMaximize ? { onMaximize } : {}),\n });\n\n return (\n <div className=\"space-y-2.5\">\n <div className=\"flex items-start gap-2.5\">\n <CommentAuthorAvatar author={comment.author} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"truncate text-xs font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {date && (\n <span\n title={date.toLocaleString()}\n className=\"shrink-0 text-[10px] text-muted-foreground\"\n >\n {formatRelativeTime(date)}\n </span>\n )}\n </div>\n {!isReply && (\n <div className=\"mt-1 flex flex-wrap gap-1\">\n <StatusChip comment={comment} config={config} />\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} />\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {menuItems.length > 0 && (\n <DropdownMenu\n align=\"right\"\n menuClassName=\"min-w-[150px]\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"inline-flex rounded-full p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={menuItems}\n />\n )}\n </div>\n </div>\n {comment.body.trim() && (\n <div className=\"text-[13px] leading-5 text-foreground\">\n {renderBody ? (\n renderBody(comment.body)\n ) : (\n <CommentMarkdown text={comment.body} />\n )}\n </div>\n )}\n {actionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n {actionError}\n </div>\n )}\n {!isReply && (\n <ChecklistChips\n comment={comment}\n {...(onChecklistToggle ? { onChecklistToggle } : {})}\n />\n )}\n {!isReply && <RefChips comment={comment} />}\n {onReply && (\n <button\n type=\"button\"\n onClick={onReply}\n className=\"-ml-2 rounded px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary/10\"\n >\n Reply\n </button>\n )}\n </div>\n );\n}\n\n/**\n * A single comment, collapsible between a compact one-line preview and a full\n * card. Offers a Maximise action when `onMaximize` is supplied — the owner\n * enlarges the whole thread. Fully controlled — all mutations are delegated to\n * the supplied callbacks.\n */\nexport function CommentCard(props: CommentCardProps) {\n const { comment, config, compact, defaultExpanded } = props;\n const [expanded, setExpanded] = useState(defaultExpanded ?? false);\n const [collapsedActionError, setCollapsedActionError] = useState(\"\");\n const isReply = Boolean(comment.parentId);\n const body = <CommentBody {...props} />;\n\n async function runCollapsedAction(action: \"resolve\" | \"close\" | \"reopen\") {\n setCollapsedActionError(\"\");\n try {\n if (action === \"close\") {\n await props.onClose?.();\n return;\n }\n const next = statusForCommentStage(\n config,\n action === \"resolve\" ? \"resolved\" : \"active\",\n );\n if (next) await props.onUpdateStatus?.(next.value);\n } catch (error) {\n setCollapsedActionError(\n `${action === \"close\" ? \"Couldn't close comment\" : \"Couldn't update comment\"}: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n }\n\n async function copyCollapsed() {\n setCollapsedActionError(\"\");\n try {\n await props.onCopy?.();\n } catch (error) {\n setCollapsedActionError(\n `Couldn't copy comment: ${error instanceof Error ? error.message : \"Unexpected error\"}`,\n );\n }\n }\n\n if (!expanded) {\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpanded(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setExpanded(true);\n }\n }}\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs transition-colors hover:bg-muted/50\",\n !isReply && \"border border-border bg-background shadow-sm\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/30\",\n )}\n >\n <CommentAuthorAvatar author={comment.author} bare />\n <span className=\"shrink-0 text-[11px] font-semibold text-foreground\">\n {authorDisplayName(comment.author)}\n </span>\n {!isReply && (\n <>\n <StatusChip comment={comment} config={config} />\n <RatingChip rating={comment.rating} />\n <FacetBadges comment={comment} config={config} compact />\n </>\n )}\n <span className=\"min-w-0 flex-1 truncate text-[12px] text-foreground/80\">\n {comment.body.trim()\n ? truncatePlain(comment.body)\n : comment.rating === \"positive\"\n ? \"Positive rating\"\n : comment.rating === \"negative\"\n ? \"Negative rating\"\n : \"Empty comment\"}\n </span>\n <span\n role=\"presentation\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <DropdownMenu\n align=\"right\"\n trigger={\n <span\n role=\"button\"\n tabIndex={0}\n aria-label=\"Comment actions\"\n className=\"rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:opacity-100 group-hover/comment:opacity-70\"\n >\n <Icon icon={UiDotsVertical} className=\"text-sm\" />\n </span>\n }\n items={actionMenuItems(isReply, {\n ...(props.onDelete ? { onDelete: props.onDelete } : {}),\n statusActions: [\n ...(resolveCommentStage(config, comment.status) ===\n \"active\" &&\n props.onUpdateStatus &&\n statusForCommentStage(config, \"resolved\")\n ? [\n {\n label: \"Resolve\",\n icon: UiCheck,\n onSelect: () => void runCollapsedAction(\"resolve\"),\n },\n ]\n : []),\n ...(resolveCommentStage(config, comment.status) ===\n \"resolved\" &&\n props.onClose &&\n statusForCommentStage(config, \"closed\")\n ? [\n {\n label: \"Close\",\n icon: UiLock,\n onSelect: () => void runCollapsedAction(\"close\"),\n },\n ]\n : []),\n ...((resolveCommentStage(config, comment.status) ===\n \"resolved\" ||\n resolveCommentStage(config, comment.status) === \"closed\") &&\n props.onUpdateStatus &&\n statusForCommentStage(config, \"active\")\n ? [\n {\n label: \"Reopen\",\n icon: UiRestart,\n onSelect: () => void runCollapsedAction(\"reopen\"),\n },\n ]\n : []),\n ],\n ...(props.onCopy ? { onCopy: () => void copyCollapsed() } : {}),\n ...(props.onMaximize ? { onMaximize: props.onMaximize } : {}),\n })}\n />\n </span>\n </div>\n {collapsedActionError && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/30 bg-destructive/5 px-2.5 py-2 text-xs text-destructive\"\n >\n {collapsedActionError}\n </div>\n )}\n </>\n );\n }\n\n return (\n <>\n <div\n data-testid=\"comment-card\"\n data-comment-kind={isReply ? \"reply\" : \"root\"}\n data-comment-id={comment.id}\n className={cn(\n \"group/comment rounded-xl bg-background px-3.5 py-3 text-xs\",\n !isReply &&\n \"border border-border/80 shadow-sm transition-shadow hover:shadow-md\",\n compact && \"px-2.5 py-2.5\",\n isReply && \"ml-4 border-l-2 border-l-border bg-muted/20 shadow-none\",\n )}\n >\n {body}\n </div>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|