@ai-matrx/associations 0.4.0 → 0.5.1
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/CHANGELOG.md +69 -0
- package/README.md +3 -3
- package/dist/core/index.cjs +246 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +67 -3
- package/dist/core/index.d.ts +67 -3
- package/dist/core/index.js +246 -3
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -3
- package/dist/index.d.ts +124 -3
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +538 -53
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +253 -5
- package/dist/react/index.d.ts +253 -5
- package/dist/react/index.js +490 -5
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
package/dist/react/index.js
CHANGED
|
@@ -13,6 +13,7 @@ function AssociationsProvider({
|
|
|
13
13
|
capture,
|
|
14
14
|
pickerOverrides,
|
|
15
15
|
entityDoors,
|
|
16
|
+
authorDisplay,
|
|
16
17
|
children
|
|
17
18
|
}) {
|
|
18
19
|
const notifierWarned = useRef(false);
|
|
@@ -26,7 +27,8 @@ function AssociationsProvider({
|
|
|
26
27
|
...windowShell !== void 0 ? { windowShell } : {},
|
|
27
28
|
...capture !== void 0 ? { capture } : {},
|
|
28
29
|
...pickerOverrides !== void 0 ? { pickerOverrides } : {},
|
|
29
|
-
...entityDoors !== void 0 ? { entityDoors } : {}
|
|
30
|
+
...entityDoors !== void 0 ? { entityDoors } : {},
|
|
31
|
+
...authorDisplay !== void 0 ? { authorDisplay } : {}
|
|
30
32
|
},
|
|
31
33
|
children
|
|
32
34
|
}
|
|
@@ -721,6 +723,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
721
723
|
"workflow",
|
|
722
724
|
"workflow_card",
|
|
723
725
|
"workflow_checkpoint",
|
|
726
|
+
"workflow_comparison",
|
|
724
727
|
"workflow_definition_version",
|
|
725
728
|
"workflow_idempotency",
|
|
726
729
|
"workflow_job",
|
|
@@ -766,6 +769,12 @@ var IDLE_CATEGORIES = Object.freeze({
|
|
|
766
769
|
fetchedAt: null,
|
|
767
770
|
error: null
|
|
768
771
|
});
|
|
772
|
+
var IDLE_COMMENTS = Object.freeze({
|
|
773
|
+
status: "idle",
|
|
774
|
+
comments: Object.freeze([]),
|
|
775
|
+
fetchedAt: null,
|
|
776
|
+
error: null
|
|
777
|
+
});
|
|
769
778
|
|
|
770
779
|
// src/react/hooks/useAssociations.ts
|
|
771
780
|
var noopSubscribe = () => () => {
|
|
@@ -1250,6 +1259,72 @@ function currentUserIdOrNull(store) {
|
|
|
1250
1259
|
}
|
|
1251
1260
|
}
|
|
1252
1261
|
|
|
1262
|
+
// src/react/hooks/useComments.ts
|
|
1263
|
+
import { useEffect as useEffect7, useRef as useRef6, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
1264
|
+
var noopSubscribe3 = () => () => {
|
|
1265
|
+
};
|
|
1266
|
+
function useComments(args) {
|
|
1267
|
+
const { token, id, autoLoad = true } = args;
|
|
1268
|
+
const store = useAssociationsStore();
|
|
1269
|
+
const key = token && id ? associationsKey(token, id) : null;
|
|
1270
|
+
const entry = useSyncExternalStore3(
|
|
1271
|
+
key ? (cb) => store.subscribeComments(key, cb) : noopSubscribe3,
|
|
1272
|
+
() => store.getComments(token, id ?? ""),
|
|
1273
|
+
() => store.getComments(token, id ?? "")
|
|
1274
|
+
);
|
|
1275
|
+
const loadedKey = useRef6(null);
|
|
1276
|
+
useEffect7(() => {
|
|
1277
|
+
if (!autoLoad || !key || !id) return;
|
|
1278
|
+
if (loadedKey.current === key) return;
|
|
1279
|
+
loadedKey.current = key;
|
|
1280
|
+
void store.loadComments(token, id);
|
|
1281
|
+
}, [autoLoad, store, key, token, id]);
|
|
1282
|
+
let currentUserId = null;
|
|
1283
|
+
try {
|
|
1284
|
+
currentUserId = store.identity.requireUserId();
|
|
1285
|
+
} catch {
|
|
1286
|
+
currentUserId = null;
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
comments: entry.comments,
|
|
1290
|
+
status: entry.status,
|
|
1291
|
+
error: entry.error,
|
|
1292
|
+
fetchedAt: entry.fetchedAt,
|
|
1293
|
+
add: async (body, opts) => {
|
|
1294
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1295
|
+
return store.addComment({
|
|
1296
|
+
entityType: token,
|
|
1297
|
+
entityId: id,
|
|
1298
|
+
body,
|
|
1299
|
+
...opts?.parentId !== void 0 ? { parentId: opts.parentId } : {},
|
|
1300
|
+
...opts?.orgId !== void 0 ? { orgId: opts.orgId } : {}
|
|
1301
|
+
});
|
|
1302
|
+
},
|
|
1303
|
+
edit: async (commentId, body) => {
|
|
1304
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1305
|
+
return store.editComment({
|
|
1306
|
+
entityType: token,
|
|
1307
|
+
entityId: id,
|
|
1308
|
+
id: commentId,
|
|
1309
|
+
body
|
|
1310
|
+
});
|
|
1311
|
+
},
|
|
1312
|
+
remove: async (commentId) => {
|
|
1313
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1314
|
+
return store.deleteComment({
|
|
1315
|
+
entityType: token,
|
|
1316
|
+
entityId: id,
|
|
1317
|
+
id: commentId
|
|
1318
|
+
});
|
|
1319
|
+
},
|
|
1320
|
+
reload: async () => {
|
|
1321
|
+
if (!id) return;
|
|
1322
|
+
await store.loadComments(token, id, { force: true });
|
|
1323
|
+
},
|
|
1324
|
+
currentUserId
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1253
1328
|
// src/react/hooks/useAssociationEntitySelect.ts
|
|
1254
1329
|
import { useState as useState5 } from "react";
|
|
1255
1330
|
function useAssociationEntitySelectAdapter(args) {
|
|
@@ -1993,7 +2068,7 @@ function UniversalAssociationPicker(props) {
|
|
|
1993
2068
|
}
|
|
1994
2069
|
|
|
1995
2070
|
// src/react/components/AttachedItemsSheet.tsx
|
|
1996
|
-
import { useEffect as
|
|
2071
|
+
import { useEffect as useEffect8, useState as useState8 } from "react";
|
|
1997
2072
|
import { cn as cn5 } from "@ai-matrx/design-system";
|
|
1998
2073
|
|
|
1999
2074
|
// src/react/components/entityDoors.tsx
|
|
@@ -2113,7 +2188,7 @@ function AttachedItemsBody({
|
|
|
2113
2188
|
const [titleError, setTitleError] = useState8(null);
|
|
2114
2189
|
const [busyId, setBusyId] = useState8(null);
|
|
2115
2190
|
const idKey = links.map((l) => l.resourceId).sort().join(",");
|
|
2116
|
-
|
|
2191
|
+
useEffect8(() => {
|
|
2117
2192
|
if (!enabled) return;
|
|
2118
2193
|
const ids = idKey ? idKey.split(",") : [];
|
|
2119
2194
|
if (ids.length === 0) {
|
|
@@ -2798,7 +2873,7 @@ function titleize(token) {
|
|
|
2798
2873
|
}
|
|
2799
2874
|
|
|
2800
2875
|
// src/react/components/AssociationCaptureToolbar.tsx
|
|
2801
|
-
import { useRef as
|
|
2876
|
+
import { useRef as useRef7, useState as useState11 } from "react";
|
|
2802
2877
|
import { Button, cn as cn9, Input as Input3 } from "@ai-matrx/design-system";
|
|
2803
2878
|
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2804
2879
|
function AssociationCaptureToolbar({
|
|
@@ -2818,7 +2893,7 @@ function AssociationCaptureToolbar({
|
|
|
2818
2893
|
const store = useAssociationsStore();
|
|
2819
2894
|
const { capture } = useAssociationsUiPorts();
|
|
2820
2895
|
const notifier = useNotifier();
|
|
2821
|
-
const fileInputRef =
|
|
2896
|
+
const fileInputRef = useRef7(null);
|
|
2822
2897
|
const [isUploading, setIsUploading] = useState11(false);
|
|
2823
2898
|
const [isDragOver, setIsDragOver] = useState11(false);
|
|
2824
2899
|
const [isPicking, setIsPicking] = useState11(false);
|
|
@@ -3887,6 +3962,411 @@ function CategoryTagPicker({
|
|
|
3887
3962
|
] })
|
|
3888
3963
|
] });
|
|
3889
3964
|
}
|
|
3965
|
+
|
|
3966
|
+
// src/react/components/CommentThread.tsx
|
|
3967
|
+
import { useState as useState15 } from "react";
|
|
3968
|
+
import { Button as Button3, Skeleton as Skeleton2, cn as cn12 } from "@ai-matrx/design-system";
|
|
3969
|
+
|
|
3970
|
+
// src/react/relativeTime.ts
|
|
3971
|
+
var UNITS = [
|
|
3972
|
+
{ unit: "year", ms: 365 * 24 * 60 * 60 * 1e3 },
|
|
3973
|
+
{ unit: "month", ms: 30 * 24 * 60 * 60 * 1e3 },
|
|
3974
|
+
{ unit: "week", ms: 7 * 24 * 60 * 60 * 1e3 },
|
|
3975
|
+
{ unit: "day", ms: 24 * 60 * 60 * 1e3 },
|
|
3976
|
+
{ unit: "hour", ms: 60 * 60 * 1e3 },
|
|
3977
|
+
{ unit: "minute", ms: 60 * 1e3 }
|
|
3978
|
+
];
|
|
3979
|
+
function formatRelativeTime(iso, now = Date.now()) {
|
|
3980
|
+
const t = Date.parse(iso);
|
|
3981
|
+
if (Number.isNaN(t)) return iso;
|
|
3982
|
+
const delta = t - now;
|
|
3983
|
+
const magnitude = Math.abs(delta);
|
|
3984
|
+
if (magnitude < 60 * 1e3) return "just now";
|
|
3985
|
+
const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
|
|
3986
|
+
for (const { unit, ms } of UNITS) {
|
|
3987
|
+
if (magnitude >= ms) {
|
|
3988
|
+
return rtf.format(Math.trunc(delta / ms), unit);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
return "just now";
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// src/react/components/CommentThread.tsx
|
|
3995
|
+
import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3996
|
+
var AUTHOR_DOOR_TOKEN = "user";
|
|
3997
|
+
function resolveAuthor(comment, authorDisplay) {
|
|
3998
|
+
const base2 = {
|
|
3999
|
+
userId: comment.createdBy,
|
|
4000
|
+
email: comment.author.email,
|
|
4001
|
+
displayName: comment.author.displayName,
|
|
4002
|
+
avatarUrl: comment.author.avatarUrl
|
|
4003
|
+
};
|
|
4004
|
+
const enriched = authorDisplay ? authorDisplay(base2) : null;
|
|
4005
|
+
const name = enriched?.displayName ?? base2.displayName ?? base2.email ?? "Unknown user";
|
|
4006
|
+
return {
|
|
4007
|
+
name,
|
|
4008
|
+
avatarUrl: enriched?.avatarUrl !== void 0 && enriched?.avatarUrl !== null ? enriched.avatarUrl : base2.avatarUrl,
|
|
4009
|
+
userId: comment.createdBy
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
function AuthorAvatar({ author }) {
|
|
4013
|
+
if (author.avatarUrl) {
|
|
4014
|
+
return /* @__PURE__ */ jsx16(
|
|
4015
|
+
"img",
|
|
4016
|
+
{
|
|
4017
|
+
src: author.avatarUrl,
|
|
4018
|
+
alt: "",
|
|
4019
|
+
className: "h-6 w-6 shrink-0 rounded-full object-cover"
|
|
4020
|
+
}
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
return /* @__PURE__ */ jsx16(
|
|
4024
|
+
"span",
|
|
4025
|
+
{
|
|
4026
|
+
"aria-hidden": true,
|
|
4027
|
+
className: "flex h-6 w-6 shrink-0 select-none items-center justify-center rounded-full bg-muted text-[11px] font-medium uppercase text-muted-foreground",
|
|
4028
|
+
children: author.name.trim().charAt(0) || "?"
|
|
4029
|
+
}
|
|
4030
|
+
);
|
|
4031
|
+
}
|
|
4032
|
+
function AuthorName({
|
|
4033
|
+
author,
|
|
4034
|
+
entityDoors
|
|
4035
|
+
}) {
|
|
4036
|
+
const DoorRef2 = entityDoors?.EntityRef;
|
|
4037
|
+
if (DoorRef2 && author.userId) {
|
|
4038
|
+
return /* @__PURE__ */ jsx16(
|
|
4039
|
+
DoorRef2,
|
|
4040
|
+
{
|
|
4041
|
+
token: AUTHOR_DOOR_TOKEN,
|
|
4042
|
+
id: author.userId,
|
|
4043
|
+
name: author.name,
|
|
4044
|
+
showIcon: false,
|
|
4045
|
+
className: "text-sm font-medium text-foreground"
|
|
4046
|
+
}
|
|
4047
|
+
);
|
|
4048
|
+
}
|
|
4049
|
+
return /* @__PURE__ */ jsx16("span", { className: "text-sm font-medium text-foreground", children: author.name });
|
|
4050
|
+
}
|
|
4051
|
+
function CommentComposer({
|
|
4052
|
+
onSubmit,
|
|
4053
|
+
placeholder = "Write a comment\u2026",
|
|
4054
|
+
submitLabel = "Comment",
|
|
4055
|
+
initialValue = "",
|
|
4056
|
+
trailing,
|
|
4057
|
+
autoFocus = false
|
|
4058
|
+
}) {
|
|
4059
|
+
const notifier = useNotifier();
|
|
4060
|
+
const [body, setBody] = useState15(initialValue);
|
|
4061
|
+
const [submitting, setSubmitting] = useState15(false);
|
|
4062
|
+
const [error, setError] = useState15(null);
|
|
4063
|
+
async function submit(e) {
|
|
4064
|
+
e?.preventDefault();
|
|
4065
|
+
const trimmed = body.trim();
|
|
4066
|
+
if (!trimmed || submitting) return;
|
|
4067
|
+
setSubmitting(true);
|
|
4068
|
+
setError(null);
|
|
4069
|
+
const res = await onSubmit(trimmed);
|
|
4070
|
+
setSubmitting(false);
|
|
4071
|
+
if (!res.ok) {
|
|
4072
|
+
const msg = res.error ?? "Could not save the comment";
|
|
4073
|
+
setError(msg);
|
|
4074
|
+
notifier.error(msg);
|
|
4075
|
+
return;
|
|
4076
|
+
}
|
|
4077
|
+
setBody("");
|
|
4078
|
+
}
|
|
4079
|
+
return /* @__PURE__ */ jsxs14("form", { onSubmit: submit, className: "flex flex-col gap-1.5", children: [
|
|
4080
|
+
/* @__PURE__ */ jsx16(
|
|
4081
|
+
"textarea",
|
|
4082
|
+
{
|
|
4083
|
+
value: body,
|
|
4084
|
+
onChange: (e) => setBody(e.target.value),
|
|
4085
|
+
onKeyDown: (e) => {
|
|
4086
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void submit();
|
|
4087
|
+
},
|
|
4088
|
+
placeholder,
|
|
4089
|
+
rows: 2,
|
|
4090
|
+
autoFocus,
|
|
4091
|
+
"aria-label": placeholder,
|
|
4092
|
+
className: "w-full resize-y rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
4093
|
+
}
|
|
4094
|
+
),
|
|
4095
|
+
error && /* @__PURE__ */ jsx16("p", { role: "alert", className: "text-xs text-destructive", children: error }),
|
|
4096
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
4097
|
+
/* @__PURE__ */ jsxs14(Button3, { type: "submit", size: "sm", disabled: !body.trim() || submitting, children: [
|
|
4098
|
+
submitting && /* @__PURE__ */ jsx16(SpinnerIcon, { className: "mr-1 h-3 w-3 animate-spin" }),
|
|
4099
|
+
submitLabel
|
|
4100
|
+
] }),
|
|
4101
|
+
trailing
|
|
4102
|
+
] })
|
|
4103
|
+
] });
|
|
4104
|
+
}
|
|
4105
|
+
function CommentItem({
|
|
4106
|
+
comment,
|
|
4107
|
+
childrenOf,
|
|
4108
|
+
depth,
|
|
4109
|
+
thread,
|
|
4110
|
+
orgId
|
|
4111
|
+
}) {
|
|
4112
|
+
const { authorDisplay, entityDoors } = useAssociationsUiPorts();
|
|
4113
|
+
const notifier = useNotifier();
|
|
4114
|
+
const [replying, setReplying] = useState15(false);
|
|
4115
|
+
const [editing, setEditing] = useState15(false);
|
|
4116
|
+
const [confirmingDelete, setConfirmingDelete] = useState15(false);
|
|
4117
|
+
const [deleting, setDeleting] = useState15(false);
|
|
4118
|
+
const author = resolveAuthor(comment, authorDisplay);
|
|
4119
|
+
const isOwn = thread.currentUserId !== null && comment.createdBy === thread.currentUserId;
|
|
4120
|
+
const edited = comment.updatedAt !== comment.createdAt;
|
|
4121
|
+
const replies = childrenOf.get(comment.id) ?? [];
|
|
4122
|
+
async function confirmDelete() {
|
|
4123
|
+
setDeleting(true);
|
|
4124
|
+
const res = await thread.remove(comment.id);
|
|
4125
|
+
setDeleting(false);
|
|
4126
|
+
setConfirmingDelete(false);
|
|
4127
|
+
if (!res.ok) {
|
|
4128
|
+
notifier.error(res.error ?? "Could not delete the comment");
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
return /* @__PURE__ */ jsxs14(
|
|
4132
|
+
"div",
|
|
4133
|
+
{
|
|
4134
|
+
"data-comment-id": comment.id,
|
|
4135
|
+
className: cn12(
|
|
4136
|
+
"flex flex-col gap-1",
|
|
4137
|
+
depth > 0 && "border-l border-border pl-3"
|
|
4138
|
+
),
|
|
4139
|
+
children: [
|
|
4140
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
4141
|
+
/* @__PURE__ */ jsx16(AuthorAvatar, { author }),
|
|
4142
|
+
/* @__PURE__ */ jsx16(AuthorName, { author, entityDoors }),
|
|
4143
|
+
/* @__PURE__ */ jsx16(
|
|
4144
|
+
"span",
|
|
4145
|
+
{
|
|
4146
|
+
className: "text-xs text-muted-foreground",
|
|
4147
|
+
title: comment.createdAt,
|
|
4148
|
+
children: formatRelativeTime(comment.createdAt)
|
|
4149
|
+
}
|
|
4150
|
+
),
|
|
4151
|
+
edited && /* @__PURE__ */ jsx16(
|
|
4152
|
+
"span",
|
|
4153
|
+
{
|
|
4154
|
+
className: "text-xs text-muted-foreground",
|
|
4155
|
+
title: comment.updatedAt,
|
|
4156
|
+
children: "(edited)"
|
|
4157
|
+
}
|
|
4158
|
+
)
|
|
4159
|
+
] }),
|
|
4160
|
+
editing ? /* @__PURE__ */ jsx16("div", { className: "pl-8", children: /* @__PURE__ */ jsx16(
|
|
4161
|
+
CommentComposer,
|
|
4162
|
+
{
|
|
4163
|
+
initialValue: comment.body,
|
|
4164
|
+
submitLabel: "Save",
|
|
4165
|
+
placeholder: "Edit your comment\u2026",
|
|
4166
|
+
autoFocus: true,
|
|
4167
|
+
onSubmit: async (body) => {
|
|
4168
|
+
const res = await thread.edit(comment.id, body);
|
|
4169
|
+
if (res.ok) setEditing(false);
|
|
4170
|
+
return res;
|
|
4171
|
+
},
|
|
4172
|
+
trailing: /* @__PURE__ */ jsx16(
|
|
4173
|
+
Button3,
|
|
4174
|
+
{
|
|
4175
|
+
type: "button",
|
|
4176
|
+
size: "sm",
|
|
4177
|
+
variant: "ghost",
|
|
4178
|
+
onClick: () => setEditing(false),
|
|
4179
|
+
children: "Cancel"
|
|
4180
|
+
}
|
|
4181
|
+
)
|
|
4182
|
+
}
|
|
4183
|
+
) }) : /* @__PURE__ */ jsx16("p", { className: "whitespace-pre-wrap pl-8 text-sm text-foreground", children: comment.body }),
|
|
4184
|
+
!editing && /* @__PURE__ */ jsx16("div", { className: "flex items-center gap-1 pl-7", children: confirmingDelete ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
4185
|
+
/* @__PURE__ */ jsx16("span", { className: "text-xs text-destructive", children: "Deletes this comment for everyone." }),
|
|
4186
|
+
/* @__PURE__ */ jsxs14(
|
|
4187
|
+
Button3,
|
|
4188
|
+
{
|
|
4189
|
+
type: "button",
|
|
4190
|
+
size: "sm",
|
|
4191
|
+
variant: "destructive",
|
|
4192
|
+
disabled: deleting,
|
|
4193
|
+
onClick: () => void confirmDelete(),
|
|
4194
|
+
children: [
|
|
4195
|
+
deleting && /* @__PURE__ */ jsx16(SpinnerIcon, { className: "mr-1 h-3 w-3 animate-spin" }),
|
|
4196
|
+
"Delete"
|
|
4197
|
+
]
|
|
4198
|
+
}
|
|
4199
|
+
),
|
|
4200
|
+
/* @__PURE__ */ jsx16(
|
|
4201
|
+
Button3,
|
|
4202
|
+
{
|
|
4203
|
+
type: "button",
|
|
4204
|
+
size: "sm",
|
|
4205
|
+
variant: "ghost",
|
|
4206
|
+
disabled: deleting,
|
|
4207
|
+
onClick: () => setConfirmingDelete(false),
|
|
4208
|
+
children: "Cancel"
|
|
4209
|
+
}
|
|
4210
|
+
)
|
|
4211
|
+
] }) : /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
4212
|
+
/* @__PURE__ */ jsx16(
|
|
4213
|
+
"button",
|
|
4214
|
+
{
|
|
4215
|
+
type: "button",
|
|
4216
|
+
onClick: () => setReplying((v) => !v),
|
|
4217
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4218
|
+
children: "Reply"
|
|
4219
|
+
}
|
|
4220
|
+
),
|
|
4221
|
+
isOwn && /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
4222
|
+
/* @__PURE__ */ jsx16(
|
|
4223
|
+
"button",
|
|
4224
|
+
{
|
|
4225
|
+
type: "button",
|
|
4226
|
+
onClick: () => setEditing(true),
|
|
4227
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4228
|
+
children: "Edit"
|
|
4229
|
+
}
|
|
4230
|
+
),
|
|
4231
|
+
/* @__PURE__ */ jsx16(
|
|
4232
|
+
"button",
|
|
4233
|
+
{
|
|
4234
|
+
type: "button",
|
|
4235
|
+
onClick: () => setConfirmingDelete(true),
|
|
4236
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-destructive",
|
|
4237
|
+
children: "Delete"
|
|
4238
|
+
}
|
|
4239
|
+
)
|
|
4240
|
+
] })
|
|
4241
|
+
] }) }),
|
|
4242
|
+
replying && /* @__PURE__ */ jsxs14("div", { className: "flex items-start gap-2 pl-8 pt-1", children: [
|
|
4243
|
+
/* @__PURE__ */ jsx16(CornerDownRightIcon, { className: "mt-2 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
4244
|
+
/* @__PURE__ */ jsx16("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx16(
|
|
4245
|
+
CommentComposer,
|
|
4246
|
+
{
|
|
4247
|
+
submitLabel: "Reply",
|
|
4248
|
+
placeholder: `Reply to ${author.name}\u2026`,
|
|
4249
|
+
autoFocus: true,
|
|
4250
|
+
onSubmit: async (body) => {
|
|
4251
|
+
const res = await thread.add(body, {
|
|
4252
|
+
parentId: comment.id,
|
|
4253
|
+
orgId: orgId ?? null
|
|
4254
|
+
});
|
|
4255
|
+
if (res.ok) setReplying(false);
|
|
4256
|
+
return res;
|
|
4257
|
+
},
|
|
4258
|
+
trailing: /* @__PURE__ */ jsx16(
|
|
4259
|
+
Button3,
|
|
4260
|
+
{
|
|
4261
|
+
type: "button",
|
|
4262
|
+
size: "sm",
|
|
4263
|
+
variant: "ghost",
|
|
4264
|
+
onClick: () => setReplying(false),
|
|
4265
|
+
children: "Cancel"
|
|
4266
|
+
}
|
|
4267
|
+
)
|
|
4268
|
+
}
|
|
4269
|
+
) })
|
|
4270
|
+
] }),
|
|
4271
|
+
replies.length > 0 && /* @__PURE__ */ jsx16("div", { className: "flex flex-col gap-3 pl-8 pt-1", children: replies.map((reply) => /* @__PURE__ */ jsx16(
|
|
4272
|
+
CommentItem,
|
|
4273
|
+
{
|
|
4274
|
+
comment: reply,
|
|
4275
|
+
childrenOf,
|
|
4276
|
+
depth: depth + 1,
|
|
4277
|
+
thread,
|
|
4278
|
+
orgId
|
|
4279
|
+
},
|
|
4280
|
+
reply.id
|
|
4281
|
+
)) })
|
|
4282
|
+
]
|
|
4283
|
+
}
|
|
4284
|
+
);
|
|
4285
|
+
}
|
|
4286
|
+
function buildCommentTree(comments) {
|
|
4287
|
+
const byId = new Set(comments.map((c) => c.id));
|
|
4288
|
+
const roots = [];
|
|
4289
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
4290
|
+
for (const c of comments) {
|
|
4291
|
+
if (c.parentId && byId.has(c.parentId)) {
|
|
4292
|
+
const bucket = childrenOf.get(c.parentId);
|
|
4293
|
+
if (bucket) bucket.push(c);
|
|
4294
|
+
else childrenOf.set(c.parentId, [c]);
|
|
4295
|
+
} else {
|
|
4296
|
+
roots.push(c);
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
return { roots, childrenOf };
|
|
4300
|
+
}
|
|
4301
|
+
function CommentThread({
|
|
4302
|
+
token,
|
|
4303
|
+
id,
|
|
4304
|
+
orgId,
|
|
4305
|
+
showHeader = true,
|
|
4306
|
+
className
|
|
4307
|
+
}) {
|
|
4308
|
+
const thread = useComments({ token, id });
|
|
4309
|
+
const { roots, childrenOf } = buildCommentTree(thread.comments);
|
|
4310
|
+
const loading = (thread.status === "loading" || thread.status === "idle") && thread.comments.length === 0;
|
|
4311
|
+
return /* @__PURE__ */ jsxs14("section", { className: cn12("flex flex-col gap-3", className), children: [
|
|
4312
|
+
showHeader && /* @__PURE__ */ jsxs14("header", { className: "flex items-center gap-2", children: [
|
|
4313
|
+
/* @__PURE__ */ jsx16("h3", { className: "text-sm font-semibold text-foreground", children: "Comments" }),
|
|
4314
|
+
/* @__PURE__ */ jsx16("span", { className: "text-xs text-muted-foreground tabular-nums", children: thread.comments.length }),
|
|
4315
|
+
/* @__PURE__ */ jsx16(
|
|
4316
|
+
"button",
|
|
4317
|
+
{
|
|
4318
|
+
type: "button",
|
|
4319
|
+
onClick: () => void thread.reload(),
|
|
4320
|
+
"aria-label": "Refresh comments",
|
|
4321
|
+
title: "Refresh comments",
|
|
4322
|
+
className: "ml-auto flex h-6 w-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4323
|
+
children: thread.status === "loading" ? /* @__PURE__ */ jsx16(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx16(RefreshIcon, { className: "h-3.5 w-3.5" })
|
|
4324
|
+
}
|
|
4325
|
+
)
|
|
4326
|
+
] }),
|
|
4327
|
+
thread.error && /* @__PURE__ */ jsxs14(
|
|
4328
|
+
"div",
|
|
4329
|
+
{
|
|
4330
|
+
role: "alert",
|
|
4331
|
+
className: "flex items-center gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
|
|
4332
|
+
children: [
|
|
4333
|
+
/* @__PURE__ */ jsx16("span", { className: "min-w-0 flex-1", children: thread.error }),
|
|
4334
|
+
/* @__PURE__ */ jsx16(
|
|
4335
|
+
Button3,
|
|
4336
|
+
{
|
|
4337
|
+
type: "button",
|
|
4338
|
+
size: "sm",
|
|
4339
|
+
variant: "outline",
|
|
4340
|
+
disabled: thread.status === "loading",
|
|
4341
|
+
onClick: () => void thread.reload(),
|
|
4342
|
+
children: "Retry"
|
|
4343
|
+
}
|
|
4344
|
+
)
|
|
4345
|
+
]
|
|
4346
|
+
}
|
|
4347
|
+
),
|
|
4348
|
+
loading ? /* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-3", "aria-hidden": true, "data-testid": "comments-loading", children: [
|
|
4349
|
+
/* @__PURE__ */ jsx16(Skeleton2, { className: "h-12 w-full" }),
|
|
4350
|
+
/* @__PURE__ */ jsx16(Skeleton2, { className: "h-12 w-4/5" })
|
|
4351
|
+
] }) : roots.length === 0 && !thread.error ? /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground", children: "No comments yet." }) : /* @__PURE__ */ jsx16("div", { className: "flex flex-col gap-4", children: roots.map((comment) => /* @__PURE__ */ jsx16(
|
|
4352
|
+
CommentItem,
|
|
4353
|
+
{
|
|
4354
|
+
comment,
|
|
4355
|
+
childrenOf,
|
|
4356
|
+
depth: 0,
|
|
4357
|
+
thread,
|
|
4358
|
+
orgId
|
|
4359
|
+
},
|
|
4360
|
+
comment.id
|
|
4361
|
+
)) }),
|
|
4362
|
+
/* @__PURE__ */ jsx16(
|
|
4363
|
+
CommentComposer,
|
|
4364
|
+
{
|
|
4365
|
+
onSubmit: (body) => thread.add(body, { orgId: orgId ?? null })
|
|
4366
|
+
}
|
|
4367
|
+
)
|
|
4368
|
+
] });
|
|
4369
|
+
}
|
|
3890
4370
|
export {
|
|
3891
4371
|
AssociationCandidateBody,
|
|
3892
4372
|
AssociationCaptureToolbar,
|
|
@@ -3902,12 +4382,16 @@ export {
|
|
|
3902
4382
|
CaptureToolbarAction,
|
|
3903
4383
|
CategorySelect,
|
|
3904
4384
|
CategoryTagPicker,
|
|
4385
|
+
CommentComposer,
|
|
4386
|
+
CommentThread,
|
|
3905
4387
|
DefaultEntityIcon,
|
|
3906
4388
|
DoorRef,
|
|
3907
4389
|
PrimaryEntityProvider,
|
|
3908
4390
|
UniversalAssociationPicker,
|
|
3909
4391
|
UnresolvedRef,
|
|
3910
4392
|
attachedKey,
|
|
4393
|
+
buildCommentTree,
|
|
4394
|
+
formatRelativeTime,
|
|
3911
4395
|
getContentRoleMeta,
|
|
3912
4396
|
useAssociationCandidates,
|
|
3913
4397
|
useAssociationEntitySelectAdapter,
|
|
@@ -3915,6 +4399,7 @@ export {
|
|
|
3915
4399
|
useAssociationsStore,
|
|
3916
4400
|
useAssociationsUiPorts,
|
|
3917
4401
|
useCategories,
|
|
4402
|
+
useComments,
|
|
3918
4403
|
useContainerLinks,
|
|
3919
4404
|
useContainerLinksAdapter,
|
|
3920
4405
|
useEntityRelationships,
|