@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.cjs
CHANGED
|
@@ -35,12 +35,16 @@ __export(react_exports, {
|
|
|
35
35
|
CaptureToolbarAction: () => CaptureToolbarAction,
|
|
36
36
|
CategorySelect: () => CategorySelect,
|
|
37
37
|
CategoryTagPicker: () => CategoryTagPicker,
|
|
38
|
+
CommentComposer: () => CommentComposer,
|
|
39
|
+
CommentThread: () => CommentThread,
|
|
38
40
|
DefaultEntityIcon: () => DefaultEntityIcon,
|
|
39
41
|
DoorRef: () => DoorRef,
|
|
40
42
|
PrimaryEntityProvider: () => PrimaryEntityProvider,
|
|
41
43
|
UniversalAssociationPicker: () => UniversalAssociationPicker,
|
|
42
44
|
UnresolvedRef: () => UnresolvedRef,
|
|
43
45
|
attachedKey: () => attachedKey,
|
|
46
|
+
buildCommentTree: () => buildCommentTree,
|
|
47
|
+
formatRelativeTime: () => formatRelativeTime,
|
|
44
48
|
getContentRoleMeta: () => getContentRoleMeta,
|
|
45
49
|
useAssociationCandidates: () => useAssociationCandidates,
|
|
46
50
|
useAssociationEntitySelectAdapter: () => useAssociationEntitySelectAdapter,
|
|
@@ -48,6 +52,7 @@ __export(react_exports, {
|
|
|
48
52
|
useAssociationsStore: () => useAssociationsStore,
|
|
49
53
|
useAssociationsUiPorts: () => useAssociationsUiPorts,
|
|
50
54
|
useCategories: () => useCategories,
|
|
55
|
+
useComments: () => useComments,
|
|
51
56
|
useContainerLinks: () => useContainerLinks,
|
|
52
57
|
useContainerLinksAdapter: () => useContainerLinksAdapter,
|
|
53
58
|
useEntityRelationships: () => useEntityRelationships,
|
|
@@ -71,6 +76,7 @@ function AssociationsProvider({
|
|
|
71
76
|
capture,
|
|
72
77
|
pickerOverrides,
|
|
73
78
|
entityDoors,
|
|
79
|
+
authorDisplay,
|
|
74
80
|
children
|
|
75
81
|
}) {
|
|
76
82
|
const notifierWarned = (0, import_react.useRef)(false);
|
|
@@ -84,7 +90,8 @@ function AssociationsProvider({
|
|
|
84
90
|
...windowShell !== void 0 ? { windowShell } : {},
|
|
85
91
|
...capture !== void 0 ? { capture } : {},
|
|
86
92
|
...pickerOverrides !== void 0 ? { pickerOverrides } : {},
|
|
87
|
-
...entityDoors !== void 0 ? { entityDoors } : {}
|
|
93
|
+
...entityDoors !== void 0 ? { entityDoors } : {},
|
|
94
|
+
...authorDisplay !== void 0 ? { authorDisplay } : {}
|
|
88
95
|
},
|
|
89
96
|
children
|
|
90
97
|
}
|
|
@@ -824,6 +831,12 @@ var IDLE_CATEGORIES = Object.freeze({
|
|
|
824
831
|
fetchedAt: null,
|
|
825
832
|
error: null
|
|
826
833
|
});
|
|
834
|
+
var IDLE_COMMENTS = Object.freeze({
|
|
835
|
+
status: "idle",
|
|
836
|
+
comments: Object.freeze([]),
|
|
837
|
+
fetchedAt: null,
|
|
838
|
+
error: null
|
|
839
|
+
});
|
|
827
840
|
|
|
828
841
|
// src/react/hooks/useAssociations.ts
|
|
829
842
|
var noopSubscribe = () => () => {
|
|
@@ -1302,8 +1315,74 @@ function currentUserIdOrNull(store) {
|
|
|
1302
1315
|
}
|
|
1303
1316
|
}
|
|
1304
1317
|
|
|
1305
|
-
// src/react/hooks/
|
|
1318
|
+
// src/react/hooks/useComments.ts
|
|
1306
1319
|
var import_react8 = require("react");
|
|
1320
|
+
var noopSubscribe3 = () => () => {
|
|
1321
|
+
};
|
|
1322
|
+
function useComments(args) {
|
|
1323
|
+
const { token, id, autoLoad = true } = args;
|
|
1324
|
+
const store = useAssociationsStore();
|
|
1325
|
+
const key = token && id ? associationsKey(token, id) : null;
|
|
1326
|
+
const entry = (0, import_react8.useSyncExternalStore)(
|
|
1327
|
+
key ? (cb) => store.subscribeComments(key, cb) : noopSubscribe3,
|
|
1328
|
+
() => store.getComments(token, id ?? ""),
|
|
1329
|
+
() => store.getComments(token, id ?? "")
|
|
1330
|
+
);
|
|
1331
|
+
const loadedKey = (0, import_react8.useRef)(null);
|
|
1332
|
+
(0, import_react8.useEffect)(() => {
|
|
1333
|
+
if (!autoLoad || !key || !id) return;
|
|
1334
|
+
if (loadedKey.current === key) return;
|
|
1335
|
+
loadedKey.current = key;
|
|
1336
|
+
void store.loadComments(token, id);
|
|
1337
|
+
}, [autoLoad, store, key, token, id]);
|
|
1338
|
+
let currentUserId = null;
|
|
1339
|
+
try {
|
|
1340
|
+
currentUserId = store.identity.requireUserId();
|
|
1341
|
+
} catch {
|
|
1342
|
+
currentUserId = null;
|
|
1343
|
+
}
|
|
1344
|
+
return {
|
|
1345
|
+
comments: entry.comments,
|
|
1346
|
+
status: entry.status,
|
|
1347
|
+
error: entry.error,
|
|
1348
|
+
fetchedAt: entry.fetchedAt,
|
|
1349
|
+
add: async (body, opts) => {
|
|
1350
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1351
|
+
return store.addComment({
|
|
1352
|
+
entityType: token,
|
|
1353
|
+
entityId: id,
|
|
1354
|
+
body,
|
|
1355
|
+
...opts?.parentId !== void 0 ? { parentId: opts.parentId } : {},
|
|
1356
|
+
...opts?.orgId !== void 0 ? { orgId: opts.orgId } : {}
|
|
1357
|
+
});
|
|
1358
|
+
},
|
|
1359
|
+
edit: async (commentId, body) => {
|
|
1360
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1361
|
+
return store.editComment({
|
|
1362
|
+
entityType: token,
|
|
1363
|
+
entityId: id,
|
|
1364
|
+
id: commentId,
|
|
1365
|
+
body
|
|
1366
|
+
});
|
|
1367
|
+
},
|
|
1368
|
+
remove: async (commentId) => {
|
|
1369
|
+
if (!id) return { ok: false, error: "Missing entity id" };
|
|
1370
|
+
return store.deleteComment({
|
|
1371
|
+
entityType: token,
|
|
1372
|
+
entityId: id,
|
|
1373
|
+
id: commentId
|
|
1374
|
+
});
|
|
1375
|
+
},
|
|
1376
|
+
reload: async () => {
|
|
1377
|
+
if (!id) return;
|
|
1378
|
+
await store.loadComments(token, id, { force: true });
|
|
1379
|
+
},
|
|
1380
|
+
currentUserId
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/react/hooks/useAssociationEntitySelect.ts
|
|
1385
|
+
var import_react9 = require("react");
|
|
1307
1386
|
function useAssociationEntitySelectAdapter(args) {
|
|
1308
1387
|
const { token, container, activeId, onActiveChange, createColumns } = args;
|
|
1309
1388
|
const store = useAssociationsStore();
|
|
@@ -1317,10 +1396,10 @@ function useAssociationEntitySelectAdapter(args) {
|
|
|
1317
1396
|
const { titleFor } = useEntityTitles(
|
|
1318
1397
|
rows.map((r) => ({ token, id: r.resourceId, label: r.label }))
|
|
1319
1398
|
);
|
|
1320
|
-
const [titleOverrides, setTitleOverrides] = (0,
|
|
1399
|
+
const [titleOverrides, setTitleOverrides] = (0, import_react9.useState)(
|
|
1321
1400
|
{}
|
|
1322
1401
|
);
|
|
1323
|
-
const [localActiveId, setLocalActiveId] = (0,
|
|
1402
|
+
const [localActiveId, setLocalActiveId] = (0, import_react9.useState)(null);
|
|
1324
1403
|
const items = rows.map((r) => ({
|
|
1325
1404
|
id: r.resourceId,
|
|
1326
1405
|
title: titleOverrides[r.resourceId] ?? titleFor({ token, id: r.resourceId, label: r.label })
|
|
@@ -1377,9 +1456,9 @@ function useAssociationEntitySelectAdapter(args) {
|
|
|
1377
1456
|
}
|
|
1378
1457
|
|
|
1379
1458
|
// src/react/components/PrimaryEntityContext.tsx
|
|
1380
|
-
var
|
|
1459
|
+
var import_react10 = require("react");
|
|
1381
1460
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1382
|
-
var PrimaryEntityCtx = (0,
|
|
1461
|
+
var PrimaryEntityCtx = (0, import_react10.createContext)(null);
|
|
1383
1462
|
function PrimaryEntityProvider({
|
|
1384
1463
|
value,
|
|
1385
1464
|
children
|
|
@@ -1387,11 +1466,11 @@ function PrimaryEntityProvider({
|
|
|
1387
1466
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryEntityCtx.Provider, { value, children });
|
|
1388
1467
|
}
|
|
1389
1468
|
function usePrimaryEntity() {
|
|
1390
|
-
return (0,
|
|
1469
|
+
return (0, import_react10.useContext)(PrimaryEntityCtx);
|
|
1391
1470
|
}
|
|
1392
1471
|
|
|
1393
1472
|
// src/react/components/AssociationWindow.tsx
|
|
1394
|
-
var
|
|
1473
|
+
var import_react11 = require("react");
|
|
1395
1474
|
var import_design_system = require("@ai-matrx/design-system");
|
|
1396
1475
|
|
|
1397
1476
|
// src/react/icons.tsx
|
|
@@ -1522,7 +1601,7 @@ function AssociationWindow({
|
|
|
1522
1601
|
subtitle,
|
|
1523
1602
|
children
|
|
1524
1603
|
}) {
|
|
1525
|
-
const instanceId = (0,
|
|
1604
|
+
const instanceId = (0, import_react11.useId)();
|
|
1526
1605
|
const { windowShell } = useAssociationsUiPorts();
|
|
1527
1606
|
if (!open) return null;
|
|
1528
1607
|
const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex h-full min-h-0 flex-col gap-2 p-3", children: [
|
|
@@ -1574,7 +1653,7 @@ function AssociationWindow({
|
|
|
1574
1653
|
}
|
|
1575
1654
|
|
|
1576
1655
|
// src/react/components/AssociationPicker.tsx
|
|
1577
|
-
var
|
|
1656
|
+
var import_react12 = require("react");
|
|
1578
1657
|
var import_design_system2 = require("@ai-matrx/design-system");
|
|
1579
1658
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1580
1659
|
function AssociationPicker(props) {
|
|
@@ -1643,8 +1722,8 @@ function EntityCandidateList({
|
|
|
1643
1722
|
}) {
|
|
1644
1723
|
const store = useAssociationsStore();
|
|
1645
1724
|
const notifier = useNotifier();
|
|
1646
|
-
const [search, setSearch] = (0,
|
|
1647
|
-
const [busyId, setBusyId] = (0,
|
|
1725
|
+
const [search, setSearch] = (0, import_react12.useState)("");
|
|
1726
|
+
const [busyId, setBusyId] = (0, import_react12.useState)(null);
|
|
1648
1727
|
const info = store.registry.getEntityInfo(token);
|
|
1649
1728
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
1650
1729
|
const { candidates, loading, error, reload } = useAssociationCandidates({
|
|
@@ -1750,9 +1829,9 @@ function CreateAndAttachFooter({
|
|
|
1750
1829
|
const store = useAssociationsStore();
|
|
1751
1830
|
const notifier = useNotifier();
|
|
1752
1831
|
const info = store.registry.getEntityInfo(token);
|
|
1753
|
-
const [editing, setEditing] = (0,
|
|
1754
|
-
const [name, setName] = (0,
|
|
1755
|
-
const [busy, setBusy] = (0,
|
|
1832
|
+
const [editing, setEditing] = (0, import_react12.useState)(false);
|
|
1833
|
+
const [name, setName] = (0, import_react12.useState)("");
|
|
1834
|
+
const [busy, setBusy] = (0, import_react12.useState)(false);
|
|
1756
1835
|
const submit = async () => {
|
|
1757
1836
|
const title = name.trim();
|
|
1758
1837
|
if (!title || busy) return;
|
|
@@ -1860,7 +1939,7 @@ function ListMessage({ children }) {
|
|
|
1860
1939
|
}
|
|
1861
1940
|
|
|
1862
1941
|
// src/react/components/UniversalAssociationPicker.tsx
|
|
1863
|
-
var
|
|
1942
|
+
var import_react13 = require("react");
|
|
1864
1943
|
var import_design_system3 = require("@ai-matrx/design-system");
|
|
1865
1944
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1866
1945
|
function attachedKey(token, id) {
|
|
@@ -1872,9 +1951,9 @@ function UniversalAssociationPicker(props) {
|
|
|
1872
1951
|
const { entityDoors, pickerOverrides } = useAssociationsUiPorts();
|
|
1873
1952
|
const notifier = useNotifier();
|
|
1874
1953
|
const tokens = props.tokens ?? store.registry.curatedTokens();
|
|
1875
|
-
const [query, setQuery] = (0,
|
|
1876
|
-
const [browseToken, setBrowseToken] = (0,
|
|
1877
|
-
const [busyKey, setBusyKey] = (0,
|
|
1954
|
+
const [query, setQuery] = (0, import_react13.useState)("");
|
|
1955
|
+
const [browseToken, setBrowseToken] = (0, import_react13.useState)(null);
|
|
1956
|
+
const [busyKey, setBusyKey] = (0, import_react13.useState)(null);
|
|
1878
1957
|
const { results, loading, isRecents } = useUniversalEntitySearch({
|
|
1879
1958
|
query,
|
|
1880
1959
|
tokens,
|
|
@@ -2045,7 +2124,7 @@ function UniversalAssociationPicker(props) {
|
|
|
2045
2124
|
}
|
|
2046
2125
|
|
|
2047
2126
|
// src/react/components/AttachedItemsSheet.tsx
|
|
2048
|
-
var
|
|
2127
|
+
var import_react14 = require("react");
|
|
2049
2128
|
var import_design_system5 = require("@ai-matrx/design-system");
|
|
2050
2129
|
|
|
2051
2130
|
// src/react/components/entityDoors.tsx
|
|
@@ -2161,11 +2240,11 @@ function AttachedItemsBody({
|
|
|
2161
2240
|
const notifier = useNotifier();
|
|
2162
2241
|
const info = store.registry.getEntityInfo(token);
|
|
2163
2242
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
2164
|
-
const [titles, setTitles] = (0,
|
|
2165
|
-
const [titleError, setTitleError] = (0,
|
|
2166
|
-
const [busyId, setBusyId] = (0,
|
|
2243
|
+
const [titles, setTitles] = (0, import_react14.useState)(null);
|
|
2244
|
+
const [titleError, setTitleError] = (0, import_react14.useState)(null);
|
|
2245
|
+
const [busyId, setBusyId] = (0, import_react14.useState)(null);
|
|
2167
2246
|
const idKey = links.map((l) => l.resourceId).sort().join(",");
|
|
2168
|
-
(0,
|
|
2247
|
+
(0, import_react14.useEffect)(() => {
|
|
2169
2248
|
if (!enabled) return;
|
|
2170
2249
|
const ids = idKey ? idKey.split(",") : [];
|
|
2171
2250
|
if (ids.length === 0) {
|
|
@@ -2278,7 +2357,7 @@ function AttachedItemsBody({
|
|
|
2278
2357
|
}
|
|
2279
2358
|
|
|
2280
2359
|
// src/react/components/AssociationCard.tsx
|
|
2281
|
-
var
|
|
2360
|
+
var import_react15 = require("react");
|
|
2282
2361
|
var import_design_system6 = require("@ai-matrx/design-system");
|
|
2283
2362
|
|
|
2284
2363
|
// src/react/contentRoles.ts
|
|
@@ -2338,11 +2417,11 @@ function AssociationCard({
|
|
|
2338
2417
|
const store = useAssociationsStore();
|
|
2339
2418
|
const fromCtx = usePrimaryEntity();
|
|
2340
2419
|
const container = containerProp ?? fromCtx;
|
|
2341
|
-
const [open, setOpen] = (0,
|
|
2420
|
+
const [open, setOpen] = (0, import_react15.useState)(false);
|
|
2342
2421
|
const info = store.registry.getEntityInfo(token);
|
|
2343
2422
|
const role = getContentRoleMeta(info.contentRole);
|
|
2344
2423
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
2345
|
-
const [listOpen, setListOpen] = (0,
|
|
2424
|
+
const [listOpen, setListOpen] = (0, import_react15.useState)(false);
|
|
2346
2425
|
const { status, countFor, attachedIdsFor, linksFor, attach, detach } = useContainerLinks({
|
|
2347
2426
|
containerType: container?.type ?? "organization",
|
|
2348
2427
|
containerId: container?.id ?? null,
|
|
@@ -2496,7 +2575,7 @@ function AssociationCardGrid({
|
|
|
2496
2575
|
}
|
|
2497
2576
|
|
|
2498
2577
|
// src/react/components/AssociationList.tsx
|
|
2499
|
-
var
|
|
2578
|
+
var import_react16 = require("react");
|
|
2500
2579
|
var import_design_system8 = require("@ai-matrx/design-system");
|
|
2501
2580
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2502
2581
|
function useContainerLinksAdapter(container, tokens) {
|
|
@@ -2536,9 +2615,9 @@ function AssociationList(props) {
|
|
|
2536
2615
|
const adapter = props.adapter ?? defaultAdapter;
|
|
2537
2616
|
const variant = props.variant ?? "full";
|
|
2538
2617
|
const tokenFilter = props.tokens ?? null;
|
|
2539
|
-
const [pickerToken, setPickerToken] = (0,
|
|
2540
|
-
const [showUniversal, setShowUniversal] = (0,
|
|
2541
|
-
const [removingKeys, setRemovingKeys] = (0,
|
|
2618
|
+
const [pickerToken, setPickerToken] = (0, import_react16.useState)(null);
|
|
2619
|
+
const [showUniversal, setShowUniversal] = (0, import_react16.useState)(false);
|
|
2620
|
+
const [removingKeys, setRemovingKeys] = (0, import_react16.useState)(/* @__PURE__ */ new Set());
|
|
2542
2621
|
const rows = tokenFilter ? adapter.rows.filter((r) => tokenFilter.includes(r.token)) : adapter.rows;
|
|
2543
2622
|
const visibleRows = rows.filter((r) => !removingKeys.has(r.key));
|
|
2544
2623
|
const { titleFor } = useEntityTitles(
|
|
@@ -2850,7 +2929,7 @@ function titleize(token) {
|
|
|
2850
2929
|
}
|
|
2851
2930
|
|
|
2852
2931
|
// src/react/components/AssociationCaptureToolbar.tsx
|
|
2853
|
-
var
|
|
2932
|
+
var import_react17 = require("react");
|
|
2854
2933
|
var import_design_system9 = require("@ai-matrx/design-system");
|
|
2855
2934
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2856
2935
|
function AssociationCaptureToolbar({
|
|
@@ -2870,13 +2949,13 @@ function AssociationCaptureToolbar({
|
|
|
2870
2949
|
const store = useAssociationsStore();
|
|
2871
2950
|
const { capture } = useAssociationsUiPorts();
|
|
2872
2951
|
const notifier = useNotifier();
|
|
2873
|
-
const fileInputRef = (0,
|
|
2874
|
-
const [isUploading, setIsUploading] = (0,
|
|
2875
|
-
const [isDragOver, setIsDragOver] = (0,
|
|
2876
|
-
const [isPicking, setIsPicking] = (0,
|
|
2877
|
-
const [namingDoc, setNamingDoc] = (0,
|
|
2878
|
-
const [docName, setDocName] = (0,
|
|
2879
|
-
const [creatingDoc, setCreatingDoc] = (0,
|
|
2952
|
+
const fileInputRef = (0, import_react17.useRef)(null);
|
|
2953
|
+
const [isUploading, setIsUploading] = (0, import_react17.useState)(false);
|
|
2954
|
+
const [isDragOver, setIsDragOver] = (0, import_react17.useState)(false);
|
|
2955
|
+
const [isPicking, setIsPicking] = (0, import_react17.useState)(false);
|
|
2956
|
+
const [namingDoc, setNamingDoc] = (0, import_react17.useState)(false);
|
|
2957
|
+
const [docName, setDocName] = (0, import_react17.useState)("");
|
|
2958
|
+
const [creatingDoc, setCreatingDoc] = (0, import_react17.useState)(false);
|
|
2880
2959
|
const actions = {
|
|
2881
2960
|
upload: (showActions?.upload ?? true) && Boolean(capture?.requestUpload),
|
|
2882
2961
|
addFile: (showActions?.addFile ?? true) && Boolean(capture?.openFilePicker),
|
|
@@ -3184,7 +3263,7 @@ function CaptureToolbarAction({
|
|
|
3184
3263
|
}
|
|
3185
3264
|
|
|
3186
3265
|
// src/react/components/AssociationEntitySelect.tsx
|
|
3187
|
-
var
|
|
3266
|
+
var import_react18 = require("react");
|
|
3188
3267
|
var import_design_system10 = require("@ai-matrx/design-system");
|
|
3189
3268
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3190
3269
|
function AssociationEntitySelect({
|
|
@@ -3208,11 +3287,11 @@ function AssociationEntitySelect({
|
|
|
3208
3287
|
const { items, activeId } = adapter;
|
|
3209
3288
|
const active = items.find((i) => i.id === activeId) ?? null;
|
|
3210
3289
|
const activeIndex = active ? items.indexOf(active) : -1;
|
|
3211
|
-
const [open, setOpen] = (0,
|
|
3212
|
-
const [query, setQuery] = (0,
|
|
3213
|
-
const [creating, setCreating] = (0,
|
|
3214
|
-
const [draftName, setDraftName] = (0,
|
|
3215
|
-
const [busy, setBusy] = (0,
|
|
3290
|
+
const [open, setOpen] = (0, import_react18.useState)(false);
|
|
3291
|
+
const [query, setQuery] = (0, import_react18.useState)("");
|
|
3292
|
+
const [creating, setCreating] = (0, import_react18.useState)(false);
|
|
3293
|
+
const [draftName, setDraftName] = (0, import_react18.useState)("");
|
|
3294
|
+
const [busy, setBusy] = (0, import_react18.useState)(false);
|
|
3216
3295
|
const entityLabel = info.label.toLowerCase();
|
|
3217
3296
|
const close = () => {
|
|
3218
3297
|
setOpen(false);
|
|
@@ -3435,7 +3514,7 @@ function AssociationEntitySelect({
|
|
|
3435
3514
|
}
|
|
3436
3515
|
|
|
3437
3516
|
// src/react/components/CategorySelect.tsx
|
|
3438
|
-
var
|
|
3517
|
+
var import_react19 = require("react");
|
|
3439
3518
|
var import_design_system11 = require("@ai-matrx/design-system");
|
|
3440
3519
|
|
|
3441
3520
|
// src/core/categoryHierarchy.ts
|
|
@@ -3530,7 +3609,7 @@ function CategorySelect({
|
|
|
3530
3609
|
reload
|
|
3531
3610
|
} = useCategories({ dimension });
|
|
3532
3611
|
const hierarchy = buildCategoryHierarchy(categories);
|
|
3533
|
-
const [createParentId, setCreateParentId] = (0,
|
|
3612
|
+
const [createParentId, setCreateParentId] = (0, import_react19.useState)(ROOT_PARENT);
|
|
3534
3613
|
const options = [
|
|
3535
3614
|
...allowNone ? [{ value: NONE, label: "None", keywords: "none clear empty" }] : [],
|
|
3536
3615
|
...hierarchy.hasHierarchy ? hierarchy.items.map(({ category, depth, parent }) => ({
|
|
@@ -3647,7 +3726,7 @@ function CategorySelect({
|
|
|
3647
3726
|
}
|
|
3648
3727
|
|
|
3649
3728
|
// src/react/components/CategoryTagPicker.tsx
|
|
3650
|
-
var
|
|
3729
|
+
var import_react20 = require("react");
|
|
3651
3730
|
var import_design_system12 = require("@ai-matrx/design-system");
|
|
3652
3731
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3653
3732
|
var ROOT_PARENT2 = "__root__";
|
|
@@ -3665,11 +3744,11 @@ function CategoryTagPicker({
|
|
|
3665
3744
|
}) {
|
|
3666
3745
|
const store = useAssociationsStore();
|
|
3667
3746
|
const notifier = useNotifier();
|
|
3668
|
-
const [open, setOpen] = (0,
|
|
3669
|
-
const [creating, setCreating] = (0,
|
|
3670
|
-
const [writing, setWriting] = (0,
|
|
3671
|
-
const [search, setSearch] = (0,
|
|
3672
|
-
const [createParentId, setCreateParentId] = (0,
|
|
3747
|
+
const [open, setOpen] = (0, import_react20.useState)(false);
|
|
3748
|
+
const [creating, setCreating] = (0, import_react20.useState)(false);
|
|
3749
|
+
const [writing, setWriting] = (0, import_react20.useState)(false);
|
|
3750
|
+
const [search, setSearch] = (0, import_react20.useState)("");
|
|
3751
|
+
const [createParentId, setCreateParentId] = (0, import_react20.useState)(ROOT_PARENT2);
|
|
3673
3752
|
const {
|
|
3674
3753
|
categories,
|
|
3675
3754
|
create: createCategory,
|
|
@@ -3902,4 +3981,409 @@ function CategoryTagPicker({
|
|
|
3902
3981
|
] })
|
|
3903
3982
|
] });
|
|
3904
3983
|
}
|
|
3984
|
+
|
|
3985
|
+
// src/react/components/CommentThread.tsx
|
|
3986
|
+
var import_react21 = require("react");
|
|
3987
|
+
var import_design_system13 = require("@ai-matrx/design-system");
|
|
3988
|
+
|
|
3989
|
+
// src/react/relativeTime.ts
|
|
3990
|
+
var UNITS = [
|
|
3991
|
+
{ unit: "year", ms: 365 * 24 * 60 * 60 * 1e3 },
|
|
3992
|
+
{ unit: "month", ms: 30 * 24 * 60 * 60 * 1e3 },
|
|
3993
|
+
{ unit: "week", ms: 7 * 24 * 60 * 60 * 1e3 },
|
|
3994
|
+
{ unit: "day", ms: 24 * 60 * 60 * 1e3 },
|
|
3995
|
+
{ unit: "hour", ms: 60 * 60 * 1e3 },
|
|
3996
|
+
{ unit: "minute", ms: 60 * 1e3 }
|
|
3997
|
+
];
|
|
3998
|
+
function formatRelativeTime(iso, now = Date.now()) {
|
|
3999
|
+
const t = Date.parse(iso);
|
|
4000
|
+
if (Number.isNaN(t)) return iso;
|
|
4001
|
+
const delta = t - now;
|
|
4002
|
+
const magnitude = Math.abs(delta);
|
|
4003
|
+
if (magnitude < 60 * 1e3) return "just now";
|
|
4004
|
+
const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
|
|
4005
|
+
for (const { unit, ms } of UNITS) {
|
|
4006
|
+
if (magnitude >= ms) {
|
|
4007
|
+
return rtf.format(Math.trunc(delta / ms), unit);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
return "just now";
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// src/react/components/CommentThread.tsx
|
|
4014
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4015
|
+
var AUTHOR_DOOR_TOKEN = "user";
|
|
4016
|
+
function resolveAuthor(comment, authorDisplay) {
|
|
4017
|
+
const base2 = {
|
|
4018
|
+
userId: comment.createdBy,
|
|
4019
|
+
email: comment.author.email,
|
|
4020
|
+
displayName: comment.author.displayName,
|
|
4021
|
+
avatarUrl: comment.author.avatarUrl
|
|
4022
|
+
};
|
|
4023
|
+
const enriched = authorDisplay ? authorDisplay(base2) : null;
|
|
4024
|
+
const name = enriched?.displayName ?? base2.displayName ?? base2.email ?? "Unknown user";
|
|
4025
|
+
return {
|
|
4026
|
+
name,
|
|
4027
|
+
avatarUrl: enriched?.avatarUrl !== void 0 && enriched?.avatarUrl !== null ? enriched.avatarUrl : base2.avatarUrl,
|
|
4028
|
+
userId: comment.createdBy
|
|
4029
|
+
};
|
|
4030
|
+
}
|
|
4031
|
+
function AuthorAvatar({ author }) {
|
|
4032
|
+
if (author.avatarUrl) {
|
|
4033
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4034
|
+
"img",
|
|
4035
|
+
{
|
|
4036
|
+
src: author.avatarUrl,
|
|
4037
|
+
alt: "",
|
|
4038
|
+
className: "h-6 w-6 shrink-0 rounded-full object-cover"
|
|
4039
|
+
}
|
|
4040
|
+
);
|
|
4041
|
+
}
|
|
4042
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4043
|
+
"span",
|
|
4044
|
+
{
|
|
4045
|
+
"aria-hidden": true,
|
|
4046
|
+
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",
|
|
4047
|
+
children: author.name.trim().charAt(0) || "?"
|
|
4048
|
+
}
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
function AuthorName({
|
|
4052
|
+
author,
|
|
4053
|
+
entityDoors
|
|
4054
|
+
}) {
|
|
4055
|
+
const DoorRef2 = entityDoors?.EntityRef;
|
|
4056
|
+
if (DoorRef2 && author.userId) {
|
|
4057
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4058
|
+
DoorRef2,
|
|
4059
|
+
{
|
|
4060
|
+
token: AUTHOR_DOOR_TOKEN,
|
|
4061
|
+
id: author.userId,
|
|
4062
|
+
name: author.name,
|
|
4063
|
+
showIcon: false,
|
|
4064
|
+
className: "text-sm font-medium text-foreground"
|
|
4065
|
+
}
|
|
4066
|
+
);
|
|
4067
|
+
}
|
|
4068
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sm font-medium text-foreground", children: author.name });
|
|
4069
|
+
}
|
|
4070
|
+
function CommentComposer({
|
|
4071
|
+
onSubmit,
|
|
4072
|
+
placeholder = "Write a comment\u2026",
|
|
4073
|
+
submitLabel = "Comment",
|
|
4074
|
+
initialValue = "",
|
|
4075
|
+
trailing,
|
|
4076
|
+
autoFocus = false
|
|
4077
|
+
}) {
|
|
4078
|
+
const notifier = useNotifier();
|
|
4079
|
+
const [body, setBody] = (0, import_react21.useState)(initialValue);
|
|
4080
|
+
const [submitting, setSubmitting] = (0, import_react21.useState)(false);
|
|
4081
|
+
const [error, setError] = (0, import_react21.useState)(null);
|
|
4082
|
+
async function submit(e) {
|
|
4083
|
+
e?.preventDefault();
|
|
4084
|
+
const trimmed = body.trim();
|
|
4085
|
+
if (!trimmed || submitting) return;
|
|
4086
|
+
setSubmitting(true);
|
|
4087
|
+
setError(null);
|
|
4088
|
+
const res = await onSubmit(trimmed);
|
|
4089
|
+
setSubmitting(false);
|
|
4090
|
+
if (!res.ok) {
|
|
4091
|
+
const msg = res.error ?? "Could not save the comment";
|
|
4092
|
+
setError(msg);
|
|
4093
|
+
notifier.error(msg);
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
setBody("");
|
|
4097
|
+
}
|
|
4098
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("form", { onSubmit: submit, className: "flex flex-col gap-1.5", children: [
|
|
4099
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4100
|
+
"textarea",
|
|
4101
|
+
{
|
|
4102
|
+
value: body,
|
|
4103
|
+
onChange: (e) => setBody(e.target.value),
|
|
4104
|
+
onKeyDown: (e) => {
|
|
4105
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void submit();
|
|
4106
|
+
},
|
|
4107
|
+
placeholder,
|
|
4108
|
+
rows: 2,
|
|
4109
|
+
autoFocus,
|
|
4110
|
+
"aria-label": placeholder,
|
|
4111
|
+
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"
|
|
4112
|
+
}
|
|
4113
|
+
),
|
|
4114
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { role: "alert", className: "text-xs text-destructive", children: error }),
|
|
4115
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4116
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_design_system13.Button, { type: "submit", size: "sm", disabled: !body.trim() || submitting, children: [
|
|
4117
|
+
submitting && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SpinnerIcon, { className: "mr-1 h-3 w-3 animate-spin" }),
|
|
4118
|
+
submitLabel
|
|
4119
|
+
] }),
|
|
4120
|
+
trailing
|
|
4121
|
+
] })
|
|
4122
|
+
] });
|
|
4123
|
+
}
|
|
4124
|
+
function CommentItem({
|
|
4125
|
+
comment,
|
|
4126
|
+
childrenOf,
|
|
4127
|
+
depth,
|
|
4128
|
+
thread,
|
|
4129
|
+
orgId
|
|
4130
|
+
}) {
|
|
4131
|
+
const { authorDisplay, entityDoors } = useAssociationsUiPorts();
|
|
4132
|
+
const notifier = useNotifier();
|
|
4133
|
+
const [replying, setReplying] = (0, import_react21.useState)(false);
|
|
4134
|
+
const [editing, setEditing] = (0, import_react21.useState)(false);
|
|
4135
|
+
const [confirmingDelete, setConfirmingDelete] = (0, import_react21.useState)(false);
|
|
4136
|
+
const [deleting, setDeleting] = (0, import_react21.useState)(false);
|
|
4137
|
+
const author = resolveAuthor(comment, authorDisplay);
|
|
4138
|
+
const isOwn = thread.currentUserId !== null && comment.createdBy === thread.currentUserId;
|
|
4139
|
+
const edited = comment.updatedAt !== comment.createdAt;
|
|
4140
|
+
const replies = childrenOf.get(comment.id) ?? [];
|
|
4141
|
+
async function confirmDelete() {
|
|
4142
|
+
setDeleting(true);
|
|
4143
|
+
const res = await thread.remove(comment.id);
|
|
4144
|
+
setDeleting(false);
|
|
4145
|
+
setConfirmingDelete(false);
|
|
4146
|
+
if (!res.ok) {
|
|
4147
|
+
notifier.error(res.error ?? "Could not delete the comment");
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4151
|
+
"div",
|
|
4152
|
+
{
|
|
4153
|
+
"data-comment-id": comment.id,
|
|
4154
|
+
className: (0, import_design_system13.cn)(
|
|
4155
|
+
"flex flex-col gap-1",
|
|
4156
|
+
depth > 0 && "border-l border-border pl-3"
|
|
4157
|
+
),
|
|
4158
|
+
children: [
|
|
4159
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4160
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuthorAvatar, { author }),
|
|
4161
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuthorName, { author, entityDoors }),
|
|
4162
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4163
|
+
"span",
|
|
4164
|
+
{
|
|
4165
|
+
className: "text-xs text-muted-foreground",
|
|
4166
|
+
title: comment.createdAt,
|
|
4167
|
+
children: formatRelativeTime(comment.createdAt)
|
|
4168
|
+
}
|
|
4169
|
+
),
|
|
4170
|
+
edited && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4171
|
+
"span",
|
|
4172
|
+
{
|
|
4173
|
+
className: "text-xs text-muted-foreground",
|
|
4174
|
+
title: comment.updatedAt,
|
|
4175
|
+
children: "(edited)"
|
|
4176
|
+
}
|
|
4177
|
+
)
|
|
4178
|
+
] }),
|
|
4179
|
+
editing ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "pl-8", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4180
|
+
CommentComposer,
|
|
4181
|
+
{
|
|
4182
|
+
initialValue: comment.body,
|
|
4183
|
+
submitLabel: "Save",
|
|
4184
|
+
placeholder: "Edit your comment\u2026",
|
|
4185
|
+
autoFocus: true,
|
|
4186
|
+
onSubmit: async (body) => {
|
|
4187
|
+
const res = await thread.edit(comment.id, body);
|
|
4188
|
+
if (res.ok) setEditing(false);
|
|
4189
|
+
return res;
|
|
4190
|
+
},
|
|
4191
|
+
trailing: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4192
|
+
import_design_system13.Button,
|
|
4193
|
+
{
|
|
4194
|
+
type: "button",
|
|
4195
|
+
size: "sm",
|
|
4196
|
+
variant: "ghost",
|
|
4197
|
+
onClick: () => setEditing(false),
|
|
4198
|
+
children: "Cancel"
|
|
4199
|
+
}
|
|
4200
|
+
)
|
|
4201
|
+
}
|
|
4202
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "whitespace-pre-wrap pl-8 text-sm text-foreground", children: comment.body }),
|
|
4203
|
+
!editing && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex items-center gap-1 pl-7", children: confirmingDelete ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4204
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-destructive", children: "Deletes this comment for everyone." }),
|
|
4205
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4206
|
+
import_design_system13.Button,
|
|
4207
|
+
{
|
|
4208
|
+
type: "button",
|
|
4209
|
+
size: "sm",
|
|
4210
|
+
variant: "destructive",
|
|
4211
|
+
disabled: deleting,
|
|
4212
|
+
onClick: () => void confirmDelete(),
|
|
4213
|
+
children: [
|
|
4214
|
+
deleting && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SpinnerIcon, { className: "mr-1 h-3 w-3 animate-spin" }),
|
|
4215
|
+
"Delete"
|
|
4216
|
+
]
|
|
4217
|
+
}
|
|
4218
|
+
),
|
|
4219
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4220
|
+
import_design_system13.Button,
|
|
4221
|
+
{
|
|
4222
|
+
type: "button",
|
|
4223
|
+
size: "sm",
|
|
4224
|
+
variant: "ghost",
|
|
4225
|
+
disabled: deleting,
|
|
4226
|
+
onClick: () => setConfirmingDelete(false),
|
|
4227
|
+
children: "Cancel"
|
|
4228
|
+
}
|
|
4229
|
+
)
|
|
4230
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4231
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4232
|
+
"button",
|
|
4233
|
+
{
|
|
4234
|
+
type: "button",
|
|
4235
|
+
onClick: () => setReplying((v) => !v),
|
|
4236
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4237
|
+
children: "Reply"
|
|
4238
|
+
}
|
|
4239
|
+
),
|
|
4240
|
+
isOwn && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4241
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4242
|
+
"button",
|
|
4243
|
+
{
|
|
4244
|
+
type: "button",
|
|
4245
|
+
onClick: () => setEditing(true),
|
|
4246
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4247
|
+
children: "Edit"
|
|
4248
|
+
}
|
|
4249
|
+
),
|
|
4250
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4251
|
+
"button",
|
|
4252
|
+
{
|
|
4253
|
+
type: "button",
|
|
4254
|
+
onClick: () => setConfirmingDelete(true),
|
|
4255
|
+
className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-destructive",
|
|
4256
|
+
children: "Delete"
|
|
4257
|
+
}
|
|
4258
|
+
)
|
|
4259
|
+
] })
|
|
4260
|
+
] }) }),
|
|
4261
|
+
replying && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-start gap-2 pl-8 pt-1", children: [
|
|
4262
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CornerDownRightIcon, { className: "mt-2 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
4263
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4264
|
+
CommentComposer,
|
|
4265
|
+
{
|
|
4266
|
+
submitLabel: "Reply",
|
|
4267
|
+
placeholder: `Reply to ${author.name}\u2026`,
|
|
4268
|
+
autoFocus: true,
|
|
4269
|
+
onSubmit: async (body) => {
|
|
4270
|
+
const res = await thread.add(body, {
|
|
4271
|
+
parentId: comment.id,
|
|
4272
|
+
orgId: orgId ?? null
|
|
4273
|
+
});
|
|
4274
|
+
if (res.ok) setReplying(false);
|
|
4275
|
+
return res;
|
|
4276
|
+
},
|
|
4277
|
+
trailing: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4278
|
+
import_design_system13.Button,
|
|
4279
|
+
{
|
|
4280
|
+
type: "button",
|
|
4281
|
+
size: "sm",
|
|
4282
|
+
variant: "ghost",
|
|
4283
|
+
onClick: () => setReplying(false),
|
|
4284
|
+
children: "Cancel"
|
|
4285
|
+
}
|
|
4286
|
+
)
|
|
4287
|
+
}
|
|
4288
|
+
) })
|
|
4289
|
+
] }),
|
|
4290
|
+
replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex flex-col gap-3 pl-8 pt-1", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4291
|
+
CommentItem,
|
|
4292
|
+
{
|
|
4293
|
+
comment: reply,
|
|
4294
|
+
childrenOf,
|
|
4295
|
+
depth: depth + 1,
|
|
4296
|
+
thread,
|
|
4297
|
+
orgId
|
|
4298
|
+
},
|
|
4299
|
+
reply.id
|
|
4300
|
+
)) })
|
|
4301
|
+
]
|
|
4302
|
+
}
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
4305
|
+
function buildCommentTree(comments) {
|
|
4306
|
+
const byId = new Set(comments.map((c) => c.id));
|
|
4307
|
+
const roots = [];
|
|
4308
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
4309
|
+
for (const c of comments) {
|
|
4310
|
+
if (c.parentId && byId.has(c.parentId)) {
|
|
4311
|
+
const bucket = childrenOf.get(c.parentId);
|
|
4312
|
+
if (bucket) bucket.push(c);
|
|
4313
|
+
else childrenOf.set(c.parentId, [c]);
|
|
4314
|
+
} else {
|
|
4315
|
+
roots.push(c);
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
return { roots, childrenOf };
|
|
4319
|
+
}
|
|
4320
|
+
function CommentThread({
|
|
4321
|
+
token,
|
|
4322
|
+
id,
|
|
4323
|
+
orgId,
|
|
4324
|
+
showHeader = true,
|
|
4325
|
+
className
|
|
4326
|
+
}) {
|
|
4327
|
+
const thread = useComments({ token, id });
|
|
4328
|
+
const { roots, childrenOf } = buildCommentTree(thread.comments);
|
|
4329
|
+
const loading = (thread.status === "loading" || thread.status === "idle") && thread.comments.length === 0;
|
|
4330
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: (0, import_design_system13.cn)("flex flex-col gap-3", className), children: [
|
|
4331
|
+
showHeader && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "flex items-center gap-2", children: [
|
|
4332
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-semibold text-foreground", children: "Comments" }),
|
|
4333
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-muted-foreground tabular-nums", children: thread.comments.length }),
|
|
4334
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4335
|
+
"button",
|
|
4336
|
+
{
|
|
4337
|
+
type: "button",
|
|
4338
|
+
onClick: () => void thread.reload(),
|
|
4339
|
+
"aria-label": "Refresh comments",
|
|
4340
|
+
title: "Refresh comments",
|
|
4341
|
+
className: "ml-auto flex h-6 w-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4342
|
+
children: thread.status === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefreshIcon, { className: "h-3.5 w-3.5" })
|
|
4343
|
+
}
|
|
4344
|
+
)
|
|
4345
|
+
] }),
|
|
4346
|
+
thread.error && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4347
|
+
"div",
|
|
4348
|
+
{
|
|
4349
|
+
role: "alert",
|
|
4350
|
+
className: "flex items-center gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
|
|
4351
|
+
children: [
|
|
4352
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "min-w-0 flex-1", children: thread.error }),
|
|
4353
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4354
|
+
import_design_system13.Button,
|
|
4355
|
+
{
|
|
4356
|
+
type: "button",
|
|
4357
|
+
size: "sm",
|
|
4358
|
+
variant: "outline",
|
|
4359
|
+
disabled: thread.status === "loading",
|
|
4360
|
+
onClick: () => void thread.reload(),
|
|
4361
|
+
children: "Retry"
|
|
4362
|
+
}
|
|
4363
|
+
)
|
|
4364
|
+
]
|
|
4365
|
+
}
|
|
4366
|
+
),
|
|
4367
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-3", "aria-hidden": true, "data-testid": "comments-loading", children: [
|
|
4368
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Skeleton, { className: "h-12 w-full" }),
|
|
4369
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Skeleton, { className: "h-12 w-4/5" })
|
|
4370
|
+
] }) : roots.length === 0 && !thread.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-sm text-muted-foreground", children: "No comments yet." }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex flex-col gap-4", children: roots.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4371
|
+
CommentItem,
|
|
4372
|
+
{
|
|
4373
|
+
comment,
|
|
4374
|
+
childrenOf,
|
|
4375
|
+
depth: 0,
|
|
4376
|
+
thread,
|
|
4377
|
+
orgId
|
|
4378
|
+
},
|
|
4379
|
+
comment.id
|
|
4380
|
+
)) }),
|
|
4381
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4382
|
+
CommentComposer,
|
|
4383
|
+
{
|
|
4384
|
+
onSubmit: (body) => thread.add(body, { orgId: orgId ?? null })
|
|
4385
|
+
}
|
|
4386
|
+
)
|
|
4387
|
+
] });
|
|
4388
|
+
}
|
|
3905
4389
|
//# sourceMappingURL=index.cjs.map
|