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