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