@ai-matrx/associations 0.3.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 +116 -0
- package/README.md +3 -3
- package/dist/core/index.cjs +315 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +85 -3
- package/dist/core/index.d.ts +85 -3
- package/dist/core/index.js +315 -3
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +11 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -3
- package/dist/index.d.ts +156 -3
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +1270 -39
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +336 -5
- package/dist/react/index.d.ts +336 -5
- package/dist/react/index.js +1273 -5
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
package/dist/react/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(react_exports, {
|
|
|
25
25
|
AssociationCaptureToolbar: () => AssociationCaptureToolbar,
|
|
26
26
|
AssociationCard: () => AssociationCard,
|
|
27
27
|
AssociationCardGrid: () => AssociationCardGrid,
|
|
28
|
+
AssociationEntitySelect: () => AssociationEntitySelect,
|
|
28
29
|
AssociationList: () => AssociationList,
|
|
29
30
|
AssociationPicker: () => AssociationPicker,
|
|
30
31
|
AssociationWindow: () => AssociationWindow,
|
|
@@ -32,12 +33,18 @@ __export(react_exports, {
|
|
|
32
33
|
AttachedItemsSheet: () => AttachedItemsSheet,
|
|
33
34
|
CONTENT_ROLES: () => CONTENT_ROLES,
|
|
34
35
|
CaptureToolbarAction: () => CaptureToolbarAction,
|
|
36
|
+
CategorySelect: () => CategorySelect,
|
|
37
|
+
CategoryTagPicker: () => CategoryTagPicker,
|
|
38
|
+
CommentComposer: () => CommentComposer,
|
|
39
|
+
CommentThread: () => CommentThread,
|
|
35
40
|
DefaultEntityIcon: () => DefaultEntityIcon,
|
|
36
41
|
DoorRef: () => DoorRef,
|
|
37
42
|
PrimaryEntityProvider: () => PrimaryEntityProvider,
|
|
38
43
|
UniversalAssociationPicker: () => UniversalAssociationPicker,
|
|
39
44
|
UnresolvedRef: () => UnresolvedRef,
|
|
40
45
|
attachedKey: () => attachedKey,
|
|
46
|
+
buildCommentTree: () => buildCommentTree,
|
|
47
|
+
formatRelativeTime: () => formatRelativeTime,
|
|
41
48
|
getContentRoleMeta: () => getContentRoleMeta,
|
|
42
49
|
useAssociationCandidates: () => useAssociationCandidates,
|
|
43
50
|
useAssociationEntitySelectAdapter: () => useAssociationEntitySelectAdapter,
|
|
@@ -45,6 +52,7 @@ __export(react_exports, {
|
|
|
45
52
|
useAssociationsStore: () => useAssociationsStore,
|
|
46
53
|
useAssociationsUiPorts: () => useAssociationsUiPorts,
|
|
47
54
|
useCategories: () => useCategories,
|
|
55
|
+
useComments: () => useComments,
|
|
48
56
|
useContainerLinks: () => useContainerLinks,
|
|
49
57
|
useContainerLinksAdapter: () => useContainerLinksAdapter,
|
|
50
58
|
useEntityRelationships: () => useEntityRelationships,
|
|
@@ -68,6 +76,7 @@ function AssociationsProvider({
|
|
|
68
76
|
capture,
|
|
69
77
|
pickerOverrides,
|
|
70
78
|
entityDoors,
|
|
79
|
+
authorDisplay,
|
|
71
80
|
children
|
|
72
81
|
}) {
|
|
73
82
|
const notifierWarned = (0, import_react.useRef)(false);
|
|
@@ -81,7 +90,8 @@ function AssociationsProvider({
|
|
|
81
90
|
...windowShell !== void 0 ? { windowShell } : {},
|
|
82
91
|
...capture !== void 0 ? { capture } : {},
|
|
83
92
|
...pickerOverrides !== void 0 ? { pickerOverrides } : {},
|
|
84
|
-
...entityDoors !== void 0 ? { entityDoors } : {}
|
|
93
|
+
...entityDoors !== void 0 ? { entityDoors } : {},
|
|
94
|
+
...authorDisplay !== void 0 ? { authorDisplay } : {}
|
|
85
95
|
},
|
|
86
96
|
children
|
|
87
97
|
}
|
|
@@ -558,6 +568,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
558
568
|
"platform_actor_session",
|
|
559
569
|
"platform_actor_token",
|
|
560
570
|
"platform_actor_token_event",
|
|
571
|
+
"platform_continued_access",
|
|
561
572
|
"platform_outcome_event",
|
|
562
573
|
"platform_outsider_consumer",
|
|
563
574
|
"platform_saved_view",
|
|
@@ -586,6 +597,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
586
597
|
"research_tag",
|
|
587
598
|
"research_template",
|
|
588
599
|
"research_topic",
|
|
600
|
+
"route_manifest_entry",
|
|
589
601
|
"rulebook",
|
|
590
602
|
"sandbox_instance",
|
|
591
603
|
"sch_agent_task",
|
|
@@ -664,6 +676,7 @@ var ENTITY_TYPE_TOKENS = [
|
|
|
664
676
|
"seo_topic",
|
|
665
677
|
"seo_web_analytics_daily",
|
|
666
678
|
"shared_canvas_item",
|
|
679
|
+
"short_link",
|
|
667
680
|
"skill",
|
|
668
681
|
"skill_render_definition",
|
|
669
682
|
"sms_consent",
|
|
@@ -818,6 +831,12 @@ var IDLE_CATEGORIES = Object.freeze({
|
|
|
818
831
|
fetchedAt: null,
|
|
819
832
|
error: null
|
|
820
833
|
});
|
|
834
|
+
var IDLE_COMMENTS = Object.freeze({
|
|
835
|
+
status: "idle",
|
|
836
|
+
comments: Object.freeze([]),
|
|
837
|
+
fetchedAt: null,
|
|
838
|
+
error: null
|
|
839
|
+
});
|
|
821
840
|
|
|
822
841
|
// src/react/hooks/useAssociations.ts
|
|
823
842
|
var noopSubscribe = () => () => {
|
|
@@ -1296,8 +1315,74 @@ function currentUserIdOrNull(store) {
|
|
|
1296
1315
|
}
|
|
1297
1316
|
}
|
|
1298
1317
|
|
|
1299
|
-
// src/react/hooks/
|
|
1318
|
+
// src/react/hooks/useComments.ts
|
|
1300
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");
|
|
1301
1386
|
function useAssociationEntitySelectAdapter(args) {
|
|
1302
1387
|
const { token, container, activeId, onActiveChange, createColumns } = args;
|
|
1303
1388
|
const store = useAssociationsStore();
|
|
@@ -1311,10 +1396,10 @@ function useAssociationEntitySelectAdapter(args) {
|
|
|
1311
1396
|
const { titleFor } = useEntityTitles(
|
|
1312
1397
|
rows.map((r) => ({ token, id: r.resourceId, label: r.label }))
|
|
1313
1398
|
);
|
|
1314
|
-
const [titleOverrides, setTitleOverrides] = (0,
|
|
1399
|
+
const [titleOverrides, setTitleOverrides] = (0, import_react9.useState)(
|
|
1315
1400
|
{}
|
|
1316
1401
|
);
|
|
1317
|
-
const [localActiveId, setLocalActiveId] = (0,
|
|
1402
|
+
const [localActiveId, setLocalActiveId] = (0, import_react9.useState)(null);
|
|
1318
1403
|
const items = rows.map((r) => ({
|
|
1319
1404
|
id: r.resourceId,
|
|
1320
1405
|
title: titleOverrides[r.resourceId] ?? titleFor({ token, id: r.resourceId, label: r.label })
|
|
@@ -1371,9 +1456,9 @@ function useAssociationEntitySelectAdapter(args) {
|
|
|
1371
1456
|
}
|
|
1372
1457
|
|
|
1373
1458
|
// src/react/components/PrimaryEntityContext.tsx
|
|
1374
|
-
var
|
|
1459
|
+
var import_react10 = require("react");
|
|
1375
1460
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1376
|
-
var PrimaryEntityCtx = (0,
|
|
1461
|
+
var PrimaryEntityCtx = (0, import_react10.createContext)(null);
|
|
1377
1462
|
function PrimaryEntityProvider({
|
|
1378
1463
|
value,
|
|
1379
1464
|
children
|
|
@@ -1381,11 +1466,11 @@ function PrimaryEntityProvider({
|
|
|
1381
1466
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PrimaryEntityCtx.Provider, { value, children });
|
|
1382
1467
|
}
|
|
1383
1468
|
function usePrimaryEntity() {
|
|
1384
|
-
return (0,
|
|
1469
|
+
return (0, import_react10.useContext)(PrimaryEntityCtx);
|
|
1385
1470
|
}
|
|
1386
1471
|
|
|
1387
1472
|
// src/react/components/AssociationWindow.tsx
|
|
1388
|
-
var
|
|
1473
|
+
var import_react11 = require("react");
|
|
1389
1474
|
var import_design_system = require("@ai-matrx/design-system");
|
|
1390
1475
|
|
|
1391
1476
|
// src/react/icons.tsx
|
|
@@ -1467,6 +1552,27 @@ function ExternalLinkIcon(props) {
|
|
|
1467
1552
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
|
|
1468
1553
|
] });
|
|
1469
1554
|
}
|
|
1555
|
+
function ChevronDownIcon(props) {
|
|
1556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { ...base(props), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m6 9 6 6 6-6" }) });
|
|
1557
|
+
}
|
|
1558
|
+
function ChevronsUpDownIcon(props) {
|
|
1559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...base(props), children: [
|
|
1560
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m7 15 5 5 5-5" }),
|
|
1561
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m7 9 5-5 5 5" })
|
|
1562
|
+
] });
|
|
1563
|
+
}
|
|
1564
|
+
function CornerDownRightIcon(props) {
|
|
1565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...base(props), children: [
|
|
1566
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "15 10 20 15 15 20" }),
|
|
1567
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 4v7a4 4 0 0 0 4 4h12" })
|
|
1568
|
+
] });
|
|
1569
|
+
}
|
|
1570
|
+
function TagIcon(props) {
|
|
1571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...base(props), children: [
|
|
1572
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" }),
|
|
1573
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" })
|
|
1574
|
+
] });
|
|
1575
|
+
}
|
|
1470
1576
|
function DefaultEntityIcon(props) {
|
|
1471
1577
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...base(props), children: [
|
|
1472
1578
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" }),
|
|
@@ -1495,7 +1601,7 @@ function AssociationWindow({
|
|
|
1495
1601
|
subtitle,
|
|
1496
1602
|
children
|
|
1497
1603
|
}) {
|
|
1498
|
-
const instanceId = (0,
|
|
1604
|
+
const instanceId = (0, import_react11.useId)();
|
|
1499
1605
|
const { windowShell } = useAssociationsUiPorts();
|
|
1500
1606
|
if (!open) return null;
|
|
1501
1607
|
const body = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex h-full min-h-0 flex-col gap-2 p-3", children: [
|
|
@@ -1547,7 +1653,7 @@ function AssociationWindow({
|
|
|
1547
1653
|
}
|
|
1548
1654
|
|
|
1549
1655
|
// src/react/components/AssociationPicker.tsx
|
|
1550
|
-
var
|
|
1656
|
+
var import_react12 = require("react");
|
|
1551
1657
|
var import_design_system2 = require("@ai-matrx/design-system");
|
|
1552
1658
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1553
1659
|
function AssociationPicker(props) {
|
|
@@ -1616,8 +1722,8 @@ function EntityCandidateList({
|
|
|
1616
1722
|
}) {
|
|
1617
1723
|
const store = useAssociationsStore();
|
|
1618
1724
|
const notifier = useNotifier();
|
|
1619
|
-
const [search, setSearch] = (0,
|
|
1620
|
-
const [busyId, setBusyId] = (0,
|
|
1725
|
+
const [search, setSearch] = (0, import_react12.useState)("");
|
|
1726
|
+
const [busyId, setBusyId] = (0, import_react12.useState)(null);
|
|
1621
1727
|
const info = store.registry.getEntityInfo(token);
|
|
1622
1728
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
1623
1729
|
const { candidates, loading, error, reload } = useAssociationCandidates({
|
|
@@ -1723,9 +1829,9 @@ function CreateAndAttachFooter({
|
|
|
1723
1829
|
const store = useAssociationsStore();
|
|
1724
1830
|
const notifier = useNotifier();
|
|
1725
1831
|
const info = store.registry.getEntityInfo(token);
|
|
1726
|
-
const [editing, setEditing] = (0,
|
|
1727
|
-
const [name, setName] = (0,
|
|
1728
|
-
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);
|
|
1729
1835
|
const submit = async () => {
|
|
1730
1836
|
const title = name.trim();
|
|
1731
1837
|
if (!title || busy) return;
|
|
@@ -1833,7 +1939,7 @@ function ListMessage({ children }) {
|
|
|
1833
1939
|
}
|
|
1834
1940
|
|
|
1835
1941
|
// src/react/components/UniversalAssociationPicker.tsx
|
|
1836
|
-
var
|
|
1942
|
+
var import_react13 = require("react");
|
|
1837
1943
|
var import_design_system3 = require("@ai-matrx/design-system");
|
|
1838
1944
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1839
1945
|
function attachedKey(token, id) {
|
|
@@ -1845,9 +1951,9 @@ function UniversalAssociationPicker(props) {
|
|
|
1845
1951
|
const { entityDoors, pickerOverrides } = useAssociationsUiPorts();
|
|
1846
1952
|
const notifier = useNotifier();
|
|
1847
1953
|
const tokens = props.tokens ?? store.registry.curatedTokens();
|
|
1848
|
-
const [query, setQuery] = (0,
|
|
1849
|
-
const [browseToken, setBrowseToken] = (0,
|
|
1850
|
-
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);
|
|
1851
1957
|
const { results, loading, isRecents } = useUniversalEntitySearch({
|
|
1852
1958
|
query,
|
|
1853
1959
|
tokens,
|
|
@@ -2018,7 +2124,7 @@ function UniversalAssociationPicker(props) {
|
|
|
2018
2124
|
}
|
|
2019
2125
|
|
|
2020
2126
|
// src/react/components/AttachedItemsSheet.tsx
|
|
2021
|
-
var
|
|
2127
|
+
var import_react14 = require("react");
|
|
2022
2128
|
var import_design_system5 = require("@ai-matrx/design-system");
|
|
2023
2129
|
|
|
2024
2130
|
// src/react/components/entityDoors.tsx
|
|
@@ -2134,11 +2240,11 @@ function AttachedItemsBody({
|
|
|
2134
2240
|
const notifier = useNotifier();
|
|
2135
2241
|
const info = store.registry.getEntityInfo(token);
|
|
2136
2242
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
2137
|
-
const [titles, setTitles] = (0,
|
|
2138
|
-
const [titleError, setTitleError] = (0,
|
|
2139
|
-
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);
|
|
2140
2246
|
const idKey = links.map((l) => l.resourceId).sort().join(",");
|
|
2141
|
-
(0,
|
|
2247
|
+
(0, import_react14.useEffect)(() => {
|
|
2142
2248
|
if (!enabled) return;
|
|
2143
2249
|
const ids = idKey ? idKey.split(",") : [];
|
|
2144
2250
|
if (ids.length === 0) {
|
|
@@ -2251,7 +2357,7 @@ function AttachedItemsBody({
|
|
|
2251
2357
|
}
|
|
2252
2358
|
|
|
2253
2359
|
// src/react/components/AssociationCard.tsx
|
|
2254
|
-
var
|
|
2360
|
+
var import_react15 = require("react");
|
|
2255
2361
|
var import_design_system6 = require("@ai-matrx/design-system");
|
|
2256
2362
|
|
|
2257
2363
|
// src/react/contentRoles.ts
|
|
@@ -2311,11 +2417,11 @@ function AssociationCard({
|
|
|
2311
2417
|
const store = useAssociationsStore();
|
|
2312
2418
|
const fromCtx = usePrimaryEntity();
|
|
2313
2419
|
const container = containerProp ?? fromCtx;
|
|
2314
|
-
const [open, setOpen] = (0,
|
|
2420
|
+
const [open, setOpen] = (0, import_react15.useState)(false);
|
|
2315
2421
|
const info = store.registry.getEntityInfo(token);
|
|
2316
2422
|
const role = getContentRoleMeta(info.contentRole);
|
|
2317
2423
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
2318
|
-
const [listOpen, setListOpen] = (0,
|
|
2424
|
+
const [listOpen, setListOpen] = (0, import_react15.useState)(false);
|
|
2319
2425
|
const { status, countFor, attachedIdsFor, linksFor, attach, detach } = useContainerLinks({
|
|
2320
2426
|
containerType: container?.type ?? "organization",
|
|
2321
2427
|
containerId: container?.id ?? null,
|
|
@@ -2469,7 +2575,7 @@ function AssociationCardGrid({
|
|
|
2469
2575
|
}
|
|
2470
2576
|
|
|
2471
2577
|
// src/react/components/AssociationList.tsx
|
|
2472
|
-
var
|
|
2578
|
+
var import_react16 = require("react");
|
|
2473
2579
|
var import_design_system8 = require("@ai-matrx/design-system");
|
|
2474
2580
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2475
2581
|
function useContainerLinksAdapter(container, tokens) {
|
|
@@ -2509,9 +2615,9 @@ function AssociationList(props) {
|
|
|
2509
2615
|
const adapter = props.adapter ?? defaultAdapter;
|
|
2510
2616
|
const variant = props.variant ?? "full";
|
|
2511
2617
|
const tokenFilter = props.tokens ?? null;
|
|
2512
|
-
const [pickerToken, setPickerToken] = (0,
|
|
2513
|
-
const [showUniversal, setShowUniversal] = (0,
|
|
2514
|
-
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());
|
|
2515
2621
|
const rows = tokenFilter ? adapter.rows.filter((r) => tokenFilter.includes(r.token)) : adapter.rows;
|
|
2516
2622
|
const visibleRows = rows.filter((r) => !removingKeys.has(r.key));
|
|
2517
2623
|
const { titleFor } = useEntityTitles(
|
|
@@ -2823,7 +2929,7 @@ function titleize(token) {
|
|
|
2823
2929
|
}
|
|
2824
2930
|
|
|
2825
2931
|
// src/react/components/AssociationCaptureToolbar.tsx
|
|
2826
|
-
var
|
|
2932
|
+
var import_react17 = require("react");
|
|
2827
2933
|
var import_design_system9 = require("@ai-matrx/design-system");
|
|
2828
2934
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2829
2935
|
function AssociationCaptureToolbar({
|
|
@@ -2843,13 +2949,13 @@ function AssociationCaptureToolbar({
|
|
|
2843
2949
|
const store = useAssociationsStore();
|
|
2844
2950
|
const { capture } = useAssociationsUiPorts();
|
|
2845
2951
|
const notifier = useNotifier();
|
|
2846
|
-
const fileInputRef = (0,
|
|
2847
|
-
const [isUploading, setIsUploading] = (0,
|
|
2848
|
-
const [isDragOver, setIsDragOver] = (0,
|
|
2849
|
-
const [isPicking, setIsPicking] = (0,
|
|
2850
|
-
const [namingDoc, setNamingDoc] = (0,
|
|
2851
|
-
const [docName, setDocName] = (0,
|
|
2852
|
-
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);
|
|
2853
2959
|
const actions = {
|
|
2854
2960
|
upload: (showActions?.upload ?? true) && Boolean(capture?.requestUpload),
|
|
2855
2961
|
addFile: (showActions?.addFile ?? true) && Boolean(capture?.openFilePicker),
|
|
@@ -3155,4 +3261,1129 @@ function CaptureToolbarAction({
|
|
|
3155
3261
|
}
|
|
3156
3262
|
);
|
|
3157
3263
|
}
|
|
3264
|
+
|
|
3265
|
+
// src/react/components/AssociationEntitySelect.tsx
|
|
3266
|
+
var import_react18 = require("react");
|
|
3267
|
+
var import_design_system10 = require("@ai-matrx/design-system");
|
|
3268
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3269
|
+
function AssociationEntitySelect({
|
|
3270
|
+
token,
|
|
3271
|
+
adapter,
|
|
3272
|
+
className,
|
|
3273
|
+
labelClassName,
|
|
3274
|
+
align = "end",
|
|
3275
|
+
showIcon = true,
|
|
3276
|
+
renameActivation = "click",
|
|
3277
|
+
emptyLabel,
|
|
3278
|
+
iconClassName,
|
|
3279
|
+
createSlot
|
|
3280
|
+
}) {
|
|
3281
|
+
const store = useAssociationsStore();
|
|
3282
|
+
const { entityDoors } = useAssociationsUiPorts();
|
|
3283
|
+
const notifier = useNotifier();
|
|
3284
|
+
const info = store.registry.getEntityInfo(token);
|
|
3285
|
+
const InfoIcon = info.Icon ?? DefaultEntityIcon;
|
|
3286
|
+
const DoorControls = entityDoors?.DoorControls;
|
|
3287
|
+
const { items, activeId } = adapter;
|
|
3288
|
+
const active = items.find((i) => i.id === activeId) ?? null;
|
|
3289
|
+
const activeIndex = active ? items.indexOf(active) : -1;
|
|
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);
|
|
3295
|
+
const entityLabel = info.label.toLowerCase();
|
|
3296
|
+
const close = () => {
|
|
3297
|
+
setOpen(false);
|
|
3298
|
+
setQuery("");
|
|
3299
|
+
setCreating(false);
|
|
3300
|
+
setDraftName("");
|
|
3301
|
+
};
|
|
3302
|
+
const create = async (title) => {
|
|
3303
|
+
const name = title.trim();
|
|
3304
|
+
if (!name || busy || !adapter.createAndAttach) return;
|
|
3305
|
+
setBusy(true);
|
|
3306
|
+
const id = await adapter.createAndAttach(name);
|
|
3307
|
+
setBusy(false);
|
|
3308
|
+
if (id) close();
|
|
3309
|
+
else notifier.error(`Couldn't create the ${entityLabel}`);
|
|
3310
|
+
};
|
|
3311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3312
|
+
"div",
|
|
3313
|
+
{
|
|
3314
|
+
className: (0, import_design_system10.cn)(
|
|
3315
|
+
"group/entity-ref flex min-w-0 items-center gap-0.5",
|
|
3316
|
+
className
|
|
3317
|
+
),
|
|
3318
|
+
children: [
|
|
3319
|
+
showIcon ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3320
|
+
InfoIcon,
|
|
3321
|
+
{
|
|
3322
|
+
className: (0, import_design_system10.cn)(
|
|
3323
|
+
"size-3.5 shrink-0",
|
|
3324
|
+
iconClassName ?? "text-muted-foreground"
|
|
3325
|
+
),
|
|
3326
|
+
"aria-hidden": true
|
|
3327
|
+
}
|
|
3328
|
+
) : null,
|
|
3329
|
+
active ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3330
|
+
import_design_system10.EditableLabel,
|
|
3331
|
+
{
|
|
3332
|
+
value: active.title,
|
|
3333
|
+
onCommit: async (next) => {
|
|
3334
|
+
const ok = await adapter.rename(active.id, next);
|
|
3335
|
+
if (!ok) {
|
|
3336
|
+
notifier.error(`Couldn't rename the ${entityLabel}`);
|
|
3337
|
+
throw new Error("rename failed");
|
|
3338
|
+
}
|
|
3339
|
+
},
|
|
3340
|
+
commitMode: "await",
|
|
3341
|
+
activation: renameActivation,
|
|
3342
|
+
ariaLabel: info.label,
|
|
3343
|
+
className: (0, import_design_system10.cn)("max-w-[14rem]", labelClassName),
|
|
3344
|
+
displayClassName: "text-xs font-medium text-foreground",
|
|
3345
|
+
inputClassName: "text-xs font-medium"
|
|
3346
|
+
}
|
|
3347
|
+
) : null,
|
|
3348
|
+
active && DoorControls ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DoorControls, { token, id: active.id, name: active.title }) : null,
|
|
3349
|
+
!active ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3350
|
+
"span",
|
|
3351
|
+
{
|
|
3352
|
+
className: (0, import_design_system10.cn)(
|
|
3353
|
+
"truncate px-1 text-xs font-medium text-muted-foreground",
|
|
3354
|
+
labelClassName
|
|
3355
|
+
),
|
|
3356
|
+
children: adapter.loading ? "\u2026" : emptyLabel ?? info.labelPlural
|
|
3357
|
+
}
|
|
3358
|
+
) : null,
|
|
3359
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3360
|
+
import_design_system10.Popover,
|
|
3361
|
+
{
|
|
3362
|
+
open,
|
|
3363
|
+
onOpenChange: (next) => next ? setOpen(true) : close(),
|
|
3364
|
+
children: [
|
|
3365
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3366
|
+
"button",
|
|
3367
|
+
{
|
|
3368
|
+
type: "button",
|
|
3369
|
+
className: "inline-flex h-6 shrink-0 items-center gap-0.5 rounded-md px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
3370
|
+
title: `Switch or add ${info.labelPlural.toLowerCase()}`,
|
|
3371
|
+
"aria-label": `Switch or add ${info.labelPlural.toLowerCase()}`,
|
|
3372
|
+
children: [
|
|
3373
|
+
items.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "tabular-nums", children: [
|
|
3374
|
+
activeIndex >= 0 ? activeIndex + 1 : "\u2014",
|
|
3375
|
+
"/",
|
|
3376
|
+
items.length
|
|
3377
|
+
] }) : null,
|
|
3378
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChevronDownIcon, { className: "size-3 opacity-60" })
|
|
3379
|
+
]
|
|
3380
|
+
}
|
|
3381
|
+
) }),
|
|
3382
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.PopoverContent, { className: "w-60 p-0", align, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_design_system10.Command, { children: [
|
|
3383
|
+
items.length > 5 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3384
|
+
import_design_system10.CommandInput,
|
|
3385
|
+
{
|
|
3386
|
+
value: query,
|
|
3387
|
+
onValueChange: setQuery,
|
|
3388
|
+
placeholder: `Search ${info.labelPlural.toLowerCase()}\u2026`
|
|
3389
|
+
}
|
|
3390
|
+
) : null,
|
|
3391
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_design_system10.CommandList, { children: [
|
|
3392
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.CommandEmpty, { children: "No match." }),
|
|
3393
|
+
items.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.CommandGroup, { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3394
|
+
import_design_system10.CommandItem,
|
|
3395
|
+
{
|
|
3396
|
+
value: `${item.title} ${item.id}`,
|
|
3397
|
+
onSelect: () => {
|
|
3398
|
+
if (item.id !== activeId)
|
|
3399
|
+
void adapter.setActive(item.id);
|
|
3400
|
+
close();
|
|
3401
|
+
},
|
|
3402
|
+
className: "group/entity-ref group gap-2",
|
|
3403
|
+
children: [
|
|
3404
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3405
|
+
CheckIcon,
|
|
3406
|
+
{
|
|
3407
|
+
className: (0, import_design_system10.cn)(
|
|
3408
|
+
"size-3.5 shrink-0",
|
|
3409
|
+
item.id === activeId ? "opacity-100" : "opacity-0"
|
|
3410
|
+
)
|
|
3411
|
+
}
|
|
3412
|
+
),
|
|
3413
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "min-w-0 flex-1 truncate", children: item.title }),
|
|
3414
|
+
DoorControls ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3415
|
+
"span",
|
|
3416
|
+
{
|
|
3417
|
+
className: "inline-flex shrink-0 items-center",
|
|
3418
|
+
onPointerDown: (e) => {
|
|
3419
|
+
e.preventDefault();
|
|
3420
|
+
e.stopPropagation();
|
|
3421
|
+
},
|
|
3422
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3423
|
+
DoorControls,
|
|
3424
|
+
{
|
|
3425
|
+
token,
|
|
3426
|
+
id: item.id,
|
|
3427
|
+
name: item.title
|
|
3428
|
+
}
|
|
3429
|
+
)
|
|
3430
|
+
}
|
|
3431
|
+
) : null,
|
|
3432
|
+
adapter.detach && item.id !== activeId ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3433
|
+
"button",
|
|
3434
|
+
{
|
|
3435
|
+
type: "button",
|
|
3436
|
+
onPointerDown: (e) => {
|
|
3437
|
+
e.preventDefault();
|
|
3438
|
+
e.stopPropagation();
|
|
3439
|
+
},
|
|
3440
|
+
onClick: (e) => {
|
|
3441
|
+
e.stopPropagation();
|
|
3442
|
+
void adapter.detach?.(item.id);
|
|
3443
|
+
},
|
|
3444
|
+
className: "grid size-5 shrink-0 place-items-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-destructive group-hover:opacity-100",
|
|
3445
|
+
title: `Remove this ${entityLabel} from here (does not delete it)`,
|
|
3446
|
+
"aria-label": `Remove ${item.title}`,
|
|
3447
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(XIcon, { className: "size-3" })
|
|
3448
|
+
}
|
|
3449
|
+
) : null
|
|
3450
|
+
]
|
|
3451
|
+
},
|
|
3452
|
+
item.id
|
|
3453
|
+
)) }) : null
|
|
3454
|
+
] }),
|
|
3455
|
+
createSlot !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "border-t border-border p-1", children: typeof createSlot === "function" ? createSlot(close) : createSlot }) : null,
|
|
3456
|
+
createSlot === void 0 && adapter.createAndAttach ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "border-t border-border p-1", children: creating ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 px-1 py-0.5", children: [
|
|
3457
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3458
|
+
"input",
|
|
3459
|
+
{
|
|
3460
|
+
autoFocus: true,
|
|
3461
|
+
type: "text",
|
|
3462
|
+
value: draftName,
|
|
3463
|
+
disabled: busy,
|
|
3464
|
+
placeholder: `New ${entityLabel} name\u2026`,
|
|
3465
|
+
onChange: (e) => setDraftName(e.target.value),
|
|
3466
|
+
onKeyDown: (e) => {
|
|
3467
|
+
if (e.key === "Enter") {
|
|
3468
|
+
e.preventDefault();
|
|
3469
|
+
void create(draftName);
|
|
3470
|
+
} else if (e.key === "Escape") {
|
|
3471
|
+
e.preventDefault();
|
|
3472
|
+
e.stopPropagation();
|
|
3473
|
+
setCreating(false);
|
|
3474
|
+
setDraftName("");
|
|
3475
|
+
}
|
|
3476
|
+
},
|
|
3477
|
+
className: "h-6 w-full min-w-0 rounded-sm bg-transparent px-1 text-base outline-none focus:bg-background focus:ring-1 focus:ring-ring md:text-xs",
|
|
3478
|
+
"aria-label": `New ${entityLabel} name`
|
|
3479
|
+
}
|
|
3480
|
+
),
|
|
3481
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3482
|
+
"button",
|
|
3483
|
+
{
|
|
3484
|
+
type: "button",
|
|
3485
|
+
disabled: busy || !draftName.trim(),
|
|
3486
|
+
onClick: () => void create(draftName),
|
|
3487
|
+
className: "inline-flex h-6 shrink-0 items-center rounded-md px-1.5 text-[10px] font-medium text-primary transition-colors hover:bg-accent disabled:opacity-50",
|
|
3488
|
+
children: busy ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SpinnerIcon, { className: "size-3 animate-spin" }) : "Create"
|
|
3489
|
+
}
|
|
3490
|
+
)
|
|
3491
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
3492
|
+
"button",
|
|
3493
|
+
{
|
|
3494
|
+
type: "button",
|
|
3495
|
+
disabled: busy,
|
|
3496
|
+
onClick: () => {
|
|
3497
|
+
if (query.trim()) void create(query);
|
|
3498
|
+
else setCreating(true);
|
|
3499
|
+
},
|
|
3500
|
+
className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
3501
|
+
children: [
|
|
3502
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SpinnerIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PlusIcon, { className: "size-3.5" }),
|
|
3503
|
+
query.trim() ? `Create "${query.trim()}"` : `New ${info.label}`
|
|
3504
|
+
]
|
|
3505
|
+
}
|
|
3506
|
+
) }) : null
|
|
3507
|
+
] }) })
|
|
3508
|
+
]
|
|
3509
|
+
}
|
|
3510
|
+
)
|
|
3511
|
+
]
|
|
3512
|
+
}
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// src/react/components/CategorySelect.tsx
|
|
3517
|
+
var import_react19 = require("react");
|
|
3518
|
+
var import_design_system11 = require("@ai-matrx/design-system");
|
|
3519
|
+
|
|
3520
|
+
// src/core/categoryHierarchy.ts
|
|
3521
|
+
function buildCategoryHierarchy(categories) {
|
|
3522
|
+
const byId = new Map(categories.map((category) => [category.id, category]));
|
|
3523
|
+
const hasHierarchy = categories.some(
|
|
3524
|
+
(category) => category.parentId !== null && byId.has(category.parentId)
|
|
3525
|
+
);
|
|
3526
|
+
if (!hasHierarchy) {
|
|
3527
|
+
return {
|
|
3528
|
+
items: categories.map((category) => ({
|
|
3529
|
+
category,
|
|
3530
|
+
depth: 0,
|
|
3531
|
+
parent: null,
|
|
3532
|
+
displayName: category.name
|
|
3533
|
+
})),
|
|
3534
|
+
roots: categories.filter((category) => category.parentId === null),
|
|
3535
|
+
hasHierarchy: false
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
const roots = categories.filter(
|
|
3539
|
+
(category) => category.parentId === null || !byId.has(category.parentId)
|
|
3540
|
+
);
|
|
3541
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
3542
|
+
for (const category of categories) {
|
|
3543
|
+
if (!category.parentId || !byId.has(category.parentId)) continue;
|
|
3544
|
+
const children = childrenByParent.get(category.parentId) ?? [];
|
|
3545
|
+
children.push(category);
|
|
3546
|
+
childrenByParent.set(category.parentId, children);
|
|
3547
|
+
}
|
|
3548
|
+
const items = [];
|
|
3549
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3550
|
+
for (const root of roots) {
|
|
3551
|
+
items.push({
|
|
3552
|
+
category: root,
|
|
3553
|
+
depth: 0,
|
|
3554
|
+
parent: null,
|
|
3555
|
+
displayName: root.name
|
|
3556
|
+
});
|
|
3557
|
+
seen.add(root.id);
|
|
3558
|
+
for (const child of childrenByParent.get(root.id) ?? []) {
|
|
3559
|
+
items.push({
|
|
3560
|
+
category: child,
|
|
3561
|
+
depth: 1,
|
|
3562
|
+
parent: root,
|
|
3563
|
+
displayName: `${root.name} / ${child.name}`
|
|
3564
|
+
});
|
|
3565
|
+
seen.add(child.id);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
for (const category of categories) {
|
|
3569
|
+
if (seen.has(category.id)) continue;
|
|
3570
|
+
items.push({
|
|
3571
|
+
category,
|
|
3572
|
+
depth: 0,
|
|
3573
|
+
parent: null,
|
|
3574
|
+
displayName: category.name
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
return {
|
|
3578
|
+
items,
|
|
3579
|
+
roots: categories.filter((category) => category.parentId === null),
|
|
3580
|
+
hasHierarchy: true
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
// src/react/components/CategorySelect.tsx
|
|
3585
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3586
|
+
var NONE = "__none__";
|
|
3587
|
+
var ROOT_PARENT = "__root__";
|
|
3588
|
+
function plural(noun) {
|
|
3589
|
+
return /[^aeiou]y$/i.test(noun) ? `${noun.slice(0, -1)}ies` : `${noun}s`;
|
|
3590
|
+
}
|
|
3591
|
+
function CategorySelect({
|
|
3592
|
+
dimension,
|
|
3593
|
+
value,
|
|
3594
|
+
onChange,
|
|
3595
|
+
placeholder,
|
|
3596
|
+
allowNone = true,
|
|
3597
|
+
disabled,
|
|
3598
|
+
allowCreate = true,
|
|
3599
|
+
orgId,
|
|
3600
|
+
noun = "category"
|
|
3601
|
+
}) {
|
|
3602
|
+
const store = useAssociationsStore();
|
|
3603
|
+
const notifier = useNotifier();
|
|
3604
|
+
const {
|
|
3605
|
+
categories,
|
|
3606
|
+
status,
|
|
3607
|
+
error,
|
|
3608
|
+
create: createCategory,
|
|
3609
|
+
reload
|
|
3610
|
+
} = useCategories({ dimension });
|
|
3611
|
+
const hierarchy = buildCategoryHierarchy(categories);
|
|
3612
|
+
const [createParentId, setCreateParentId] = (0, import_react19.useState)(ROOT_PARENT);
|
|
3613
|
+
const options = [
|
|
3614
|
+
...allowNone ? [{ value: NONE, label: "None", keywords: "none clear empty" }] : [],
|
|
3615
|
+
...hierarchy.hasHierarchy ? hierarchy.items.map(({ category, depth, parent }) => ({
|
|
3616
|
+
value: category.id,
|
|
3617
|
+
label: category.name,
|
|
3618
|
+
// The type-ahead matches the parent's name too, so someone who knows
|
|
3619
|
+
// the branch but not the leaf still finds it.
|
|
3620
|
+
keywords: parent ? parent.name : "",
|
|
3621
|
+
render: depth === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "font-medium", children: category.name }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "flex items-center gap-1.5 pl-3 text-muted-foreground", children: [
|
|
3622
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CornerDownRightIcon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
3623
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-foreground", children: category.name })
|
|
3624
|
+
] })
|
|
3625
|
+
})) : categories.map((category) => ({
|
|
3626
|
+
value: category.id,
|
|
3627
|
+
label: category.name
|
|
3628
|
+
}))
|
|
3629
|
+
];
|
|
3630
|
+
const selected = value ? hierarchy.items.find((item) => item.category.id === value)?.displayName ?? categories.find((category) => category.id === value)?.name ?? null : null;
|
|
3631
|
+
const create = async (typed) => {
|
|
3632
|
+
const name = typed.trim();
|
|
3633
|
+
if (!name) return null;
|
|
3634
|
+
const existing = categories.find(
|
|
3635
|
+
(category) => category.name.toLowerCase() === name.toLowerCase()
|
|
3636
|
+
);
|
|
3637
|
+
if (existing) return existing.id;
|
|
3638
|
+
let resolvedOrgId = orgId ?? null;
|
|
3639
|
+
if (!resolvedOrgId && store.identity.ensureOrgId) {
|
|
3640
|
+
try {
|
|
3641
|
+
resolvedOrgId = await store.identity.ensureOrgId();
|
|
3642
|
+
} catch (e) {
|
|
3643
|
+
store.errorSink({
|
|
3644
|
+
code: "ensure_org_failed",
|
|
3645
|
+
message: "[CategorySelect] identity.ensureOrgId threw",
|
|
3646
|
+
context: { error: e instanceof Error ? e.message : String(e) }
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
if (!resolvedOrgId) {
|
|
3651
|
+
notifier.error(`Select an organization before creating a ${noun}.`);
|
|
3652
|
+
return null;
|
|
3653
|
+
}
|
|
3654
|
+
const parentId = hierarchy.hasHierarchy && createParentId !== ROOT_PARENT ? createParentId : null;
|
|
3655
|
+
const result = await createCategory({ name, orgId: resolvedOrgId, parentId });
|
|
3656
|
+
if (!result.ok || !result.id) {
|
|
3657
|
+
notifier.error(`Couldn't create that ${noun}`, {
|
|
3658
|
+
...result.error != null ? { description: result.error } : {}
|
|
3659
|
+
});
|
|
3660
|
+
return null;
|
|
3661
|
+
}
|
|
3662
|
+
await reload();
|
|
3663
|
+
setCreateParentId(ROOT_PARENT);
|
|
3664
|
+
const under = parentId ? categories.find((category) => category.id === parentId)?.name : null;
|
|
3665
|
+
notifier.success(
|
|
3666
|
+
`\u201C${name}\u201D is now one of your ${plural(noun)}${under ? ` \u2014 it sits under ${under}, and every screen that offers ${plural(noun)} offers it.` : ` \u2014 every screen that offers ${plural(noun)} offers it from now on.`}`
|
|
3667
|
+
);
|
|
3668
|
+
return result.id;
|
|
3669
|
+
};
|
|
3670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { children: [
|
|
3671
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3672
|
+
import_design_system11.CreatablePicker,
|
|
3673
|
+
{
|
|
3674
|
+
value: value ?? (allowNone ? NONE : null),
|
|
3675
|
+
onSelect: (next) => onChange(next === NONE ? null : next),
|
|
3676
|
+
options,
|
|
3677
|
+
placeholder,
|
|
3678
|
+
searchPlaceholder: allowCreate ? `Search or add a ${noun}\u2026` : `Search ${plural(noun)}\u2026`,
|
|
3679
|
+
noun,
|
|
3680
|
+
ariaLabel: placeholder,
|
|
3681
|
+
className: "text-sm",
|
|
3682
|
+
...disabled !== void 0 ? { disabled } : {},
|
|
3683
|
+
loading: status === "loading",
|
|
3684
|
+
emptyLabel: `No ${noun} matches that.`,
|
|
3685
|
+
...allowCreate ? { onCreate: create } : {},
|
|
3686
|
+
...allowCreate && hierarchy.hasHierarchy ? {
|
|
3687
|
+
createExtra: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_design_system11.Select, { value: createParentId, onValueChange: setCreateParentId, children: [
|
|
3688
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3689
|
+
import_design_system11.SelectTrigger,
|
|
3690
|
+
{
|
|
3691
|
+
className: "h-7 text-xs",
|
|
3692
|
+
"aria-label": `Put the new ${noun} under`,
|
|
3693
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_design_system11.SelectValue, {})
|
|
3694
|
+
}
|
|
3695
|
+
),
|
|
3696
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_design_system11.SelectContent, { children: [
|
|
3697
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_design_system11.SelectItem, { value: ROOT_PARENT, className: "text-xs", children: "Top level" }),
|
|
3698
|
+
hierarchy.items.filter((item) => item.depth === 0).map((item) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
3699
|
+
import_design_system11.SelectItem,
|
|
3700
|
+
{
|
|
3701
|
+
value: item.category.id,
|
|
3702
|
+
className: "text-xs",
|
|
3703
|
+
children: [
|
|
3704
|
+
"Under ",
|
|
3705
|
+
item.category.name
|
|
3706
|
+
]
|
|
3707
|
+
},
|
|
3708
|
+
item.category.id
|
|
3709
|
+
))
|
|
3710
|
+
] })
|
|
3711
|
+
] })
|
|
3712
|
+
} : {}
|
|
3713
|
+
}
|
|
3714
|
+
),
|
|
3715
|
+
selected === null && value !== null ? (
|
|
3716
|
+
// A stored id whose row this reader cannot see is not "nothing chosen":
|
|
3717
|
+
// saying so beats a blank box that looks like an unsaved field.
|
|
3718
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "mt-1 text-xs text-muted-foreground", children: [
|
|
3719
|
+
"This ",
|
|
3720
|
+
noun,
|
|
3721
|
+
" is no longer available to you."
|
|
3722
|
+
] })
|
|
3723
|
+
) : null,
|
|
3724
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mt-1 text-xs text-destructive", children: error }) : null
|
|
3725
|
+
] });
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// src/react/components/CategoryTagPicker.tsx
|
|
3729
|
+
var import_react20 = require("react");
|
|
3730
|
+
var import_design_system12 = require("@ai-matrx/design-system");
|
|
3731
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3732
|
+
var ROOT_PARENT2 = "__root__";
|
|
3733
|
+
function CategoryTagPicker({
|
|
3734
|
+
entityType,
|
|
3735
|
+
entityId,
|
|
3736
|
+
dimension,
|
|
3737
|
+
edgeRole,
|
|
3738
|
+
orgId,
|
|
3739
|
+
addLabel = "Add tag",
|
|
3740
|
+
icon: Icon = TagIcon,
|
|
3741
|
+
allowCreate = true,
|
|
3742
|
+
hierarchical,
|
|
3743
|
+
emptyText = "No categories yet."
|
|
3744
|
+
}) {
|
|
3745
|
+
const store = useAssociationsStore();
|
|
3746
|
+
const notifier = useNotifier();
|
|
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);
|
|
3752
|
+
const {
|
|
3753
|
+
categories,
|
|
3754
|
+
create: createCategory,
|
|
3755
|
+
reload: reloadCategories
|
|
3756
|
+
} = useCategories({ dimension });
|
|
3757
|
+
const { edges, setTargets } = useAssociations({
|
|
3758
|
+
type: entityType,
|
|
3759
|
+
id: entityId
|
|
3760
|
+
});
|
|
3761
|
+
const hierarchy = buildCategoryHierarchy(categories);
|
|
3762
|
+
const showHierarchy = hierarchical ?? hierarchy.hasHierarchy;
|
|
3763
|
+
const hierarchyById = new Map(
|
|
3764
|
+
hierarchy.items.map((item) => [item.category.id, item])
|
|
3765
|
+
);
|
|
3766
|
+
const selectedIds = new Set(
|
|
3767
|
+
edges.filter(
|
|
3768
|
+
(e) => e.direction === "outgoing" && e.otherType === "category" && e.role === edgeRole
|
|
3769
|
+
).map((e) => e.otherId)
|
|
3770
|
+
);
|
|
3771
|
+
const selected = categories.filter((c) => selectedIds.has(c.id));
|
|
3772
|
+
const toggle = async (categoryId) => {
|
|
3773
|
+
if (writing) return;
|
|
3774
|
+
setWriting(true);
|
|
3775
|
+
try {
|
|
3776
|
+
const next = selectedIds.has(categoryId) ? [...selectedIds].filter((id) => id !== categoryId) : [...selectedIds, categoryId];
|
|
3777
|
+
const res = await setTargets({
|
|
3778
|
+
targetType: "category",
|
|
3779
|
+
targetIds: next,
|
|
3780
|
+
...orgId != null ? { orgId } : {},
|
|
3781
|
+
role: edgeRole
|
|
3782
|
+
});
|
|
3783
|
+
if (!res.ok) {
|
|
3784
|
+
store.errorSink({
|
|
3785
|
+
code: "category_tag_set_targets_failed",
|
|
3786
|
+
message: "[CategoryTagPicker] setTargets failed",
|
|
3787
|
+
context: { entityType, entityId, edgeRole, error: res.error }
|
|
3788
|
+
});
|
|
3789
|
+
notifier.error(res.error ?? "Could not update categories");
|
|
3790
|
+
}
|
|
3791
|
+
} finally {
|
|
3792
|
+
setWriting(false);
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
const createAndAttach = async () => {
|
|
3796
|
+
const name = search.trim();
|
|
3797
|
+
if (!name) return;
|
|
3798
|
+
setCreating(true);
|
|
3799
|
+
try {
|
|
3800
|
+
let resolvedOrgId = orgId ?? null;
|
|
3801
|
+
if (!resolvedOrgId && store.identity.ensureOrgId) {
|
|
3802
|
+
try {
|
|
3803
|
+
resolvedOrgId = await store.identity.ensureOrgId();
|
|
3804
|
+
} catch (e) {
|
|
3805
|
+
store.errorSink({
|
|
3806
|
+
code: "ensure_org_failed",
|
|
3807
|
+
message: "[CategoryTagPicker] identity.ensureOrgId threw",
|
|
3808
|
+
context: { error: e instanceof Error ? e.message : String(e) }
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
if (!resolvedOrgId) {
|
|
3813
|
+
store.errorSink({
|
|
3814
|
+
code: "ensure_org_missing",
|
|
3815
|
+
message: "[CategoryTagPicker] no org for the created category \u2014 pass `orgId` or bind `identity.ensureOrgId` on the store config",
|
|
3816
|
+
context: { entityType, entityId, dimension }
|
|
3817
|
+
});
|
|
3818
|
+
notifier.error(
|
|
3819
|
+
"Could not create the category \u2014 no organization is selected"
|
|
3820
|
+
);
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
const parentId = showHierarchy && createParentId !== ROOT_PARENT2 ? createParentId : null;
|
|
3824
|
+
const createRes = await createCategory({
|
|
3825
|
+
name,
|
|
3826
|
+
orgId: resolvedOrgId,
|
|
3827
|
+
parentId
|
|
3828
|
+
});
|
|
3829
|
+
if (!createRes.ok || !createRes.id) {
|
|
3830
|
+
store.errorSink({
|
|
3831
|
+
code: "category_tag_create_failed",
|
|
3832
|
+
message: "[CategoryTagPicker] create failed",
|
|
3833
|
+
context: { dimension, error: createRes.error }
|
|
3834
|
+
});
|
|
3835
|
+
notifier.error(createRes.error ?? "Could not create the category");
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
await reloadCategories();
|
|
3839
|
+
await toggle(createRes.id);
|
|
3840
|
+
setSearch("");
|
|
3841
|
+
setCreateParentId(ROOT_PARENT2);
|
|
3842
|
+
} finally {
|
|
3843
|
+
setCreating(false);
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
const filtered = categories.filter(
|
|
3847
|
+
(c) => c.name.toLowerCase().includes(search.trim().toLowerCase())
|
|
3848
|
+
);
|
|
3849
|
+
const filteredHierarchy = hierarchy.items.filter(
|
|
3850
|
+
(item) => item.displayName.toLowerCase().includes(search.trim().toLowerCase())
|
|
3851
|
+
);
|
|
3852
|
+
const exactMatch = categories.some(
|
|
3853
|
+
(c) => c.name.toLowerCase() === search.trim().toLowerCase()
|
|
3854
|
+
);
|
|
3855
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
3856
|
+
selected.map((c) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3857
|
+
import_design_system12.Badge,
|
|
3858
|
+
{
|
|
3859
|
+
variant: "secondary",
|
|
3860
|
+
className: "gap-1 pr-1 text-xs font-medium",
|
|
3861
|
+
children: [
|
|
3862
|
+
showHierarchy ? hierarchyById.get(c.id)?.displayName ?? c.name : c.name,
|
|
3863
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3864
|
+
"button",
|
|
3865
|
+
{
|
|
3866
|
+
type: "button",
|
|
3867
|
+
onClick: () => void toggle(c.id),
|
|
3868
|
+
className: "ml-0.5 rounded-full p-0.5 hover:bg-muted-foreground/20",
|
|
3869
|
+
"aria-label": `Remove ${c.name}`,
|
|
3870
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(XIcon, { className: "h-2.5 w-2.5" })
|
|
3871
|
+
}
|
|
3872
|
+
)
|
|
3873
|
+
]
|
|
3874
|
+
},
|
|
3875
|
+
c.id
|
|
3876
|
+
)),
|
|
3877
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system12.Popover, { open, onOpenChange: setOpen, children: [
|
|
3878
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3879
|
+
import_design_system12.Button,
|
|
3880
|
+
{
|
|
3881
|
+
variant: "outline",
|
|
3882
|
+
size: "sm",
|
|
3883
|
+
className: "h-7 gap-1 px-2 text-xs",
|
|
3884
|
+
children: [
|
|
3885
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { className: "h-3.5 w-3.5" }),
|
|
3886
|
+
selected.length === 0 ? addLabel : "Edit",
|
|
3887
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChevronsUpDownIcon, { className: "h-3 w-3 opacity-50" })
|
|
3888
|
+
]
|
|
3889
|
+
}
|
|
3890
|
+
) }),
|
|
3891
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.PopoverContent, { className: "w-56 p-0", align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system12.Command, { shouldFilter: false, children: [
|
|
3892
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3893
|
+
import_design_system12.CommandInput,
|
|
3894
|
+
{
|
|
3895
|
+
placeholder: allowCreate ? "Search or create..." : "Search...",
|
|
3896
|
+
value: search,
|
|
3897
|
+
onValueChange: setSearch
|
|
3898
|
+
}
|
|
3899
|
+
),
|
|
3900
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system12.CommandList, { children: [
|
|
3901
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.CommandEmpty, { className: "px-2 py-3 text-xs text-muted-foreground", children: search.trim() ? "No match." : emptyText }),
|
|
3902
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.CommandGroup, { children: showHierarchy ? filteredHierarchy.map(({ category, depth, parent }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3903
|
+
import_design_system12.CommandItem,
|
|
3904
|
+
{
|
|
3905
|
+
value: category.id,
|
|
3906
|
+
keywords: parent ? [parent.name, category.name] : [],
|
|
3907
|
+
onSelect: () => void toggle(category.id),
|
|
3908
|
+
className: (0, import_design_system12.cn)("text-xs", depth === 1 && "pl-6"),
|
|
3909
|
+
children: [
|
|
3910
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3911
|
+
CheckIcon,
|
|
3912
|
+
{
|
|
3913
|
+
className: (0, import_design_system12.cn)(
|
|
3914
|
+
"mr-2 h-3.5 w-3.5",
|
|
3915
|
+
selectedIds.has(category.id) ? "opacity-100" : "opacity-0"
|
|
3916
|
+
)
|
|
3917
|
+
}
|
|
3918
|
+
),
|
|
3919
|
+
depth === 1 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CornerDownRightIcon, { className: "mr-1.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }) : null,
|
|
3920
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: (0, import_design_system12.cn)(depth === 0 && "font-medium"), children: category.name })
|
|
3921
|
+
]
|
|
3922
|
+
},
|
|
3923
|
+
category.id
|
|
3924
|
+
)) : filtered.map((c) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3925
|
+
import_design_system12.CommandItem,
|
|
3926
|
+
{
|
|
3927
|
+
value: c.id,
|
|
3928
|
+
onSelect: () => void toggle(c.id),
|
|
3929
|
+
className: "text-xs",
|
|
3930
|
+
children: [
|
|
3931
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3932
|
+
CheckIcon,
|
|
3933
|
+
{
|
|
3934
|
+
className: (0, import_design_system12.cn)(
|
|
3935
|
+
"mr-2 h-3.5 w-3.5",
|
|
3936
|
+
selectedIds.has(c.id) ? "opacity-100" : "opacity-0"
|
|
3937
|
+
)
|
|
3938
|
+
}
|
|
3939
|
+
),
|
|
3940
|
+
c.name
|
|
3941
|
+
]
|
|
3942
|
+
},
|
|
3943
|
+
c.id
|
|
3944
|
+
)) }),
|
|
3945
|
+
allowCreate && search.trim() && !exactMatch ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system12.CommandGroup, { children: [
|
|
3946
|
+
showHierarchy ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "space-y-1.5 px-2 pb-2", children: [
|
|
3947
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-[11px] font-medium text-muted-foreground", children: "Place in" }),
|
|
3948
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3949
|
+
import_design_system12.Select,
|
|
3950
|
+
{
|
|
3951
|
+
value: createParentId,
|
|
3952
|
+
onValueChange: setCreateParentId,
|
|
3953
|
+
children: [
|
|
3954
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.SelectTrigger, { size: "sm", className: "bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.SelectValue, {}) }),
|
|
3955
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_design_system12.SelectContent, { children: [
|
|
3956
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.SelectItem, { value: ROOT_PARENT2, children: "Top level" }),
|
|
3957
|
+
hierarchy.roots.map((root) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_design_system12.SelectItem, { value: root.id, children: root.name }, root.id))
|
|
3958
|
+
] })
|
|
3959
|
+
]
|
|
3960
|
+
}
|
|
3961
|
+
)
|
|
3962
|
+
] }) : null,
|
|
3963
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3964
|
+
import_design_system12.CommandItem,
|
|
3965
|
+
{
|
|
3966
|
+
value: `__create__${search}`,
|
|
3967
|
+
onSelect: () => void createAndAttach(),
|
|
3968
|
+
disabled: creating,
|
|
3969
|
+
className: "text-xs text-primary",
|
|
3970
|
+
children: [
|
|
3971
|
+
creating ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SpinnerIcon, { className: "mr-2 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { className: "mr-2 h-3.5 w-3.5" }),
|
|
3972
|
+
"Create \u201C",
|
|
3973
|
+
search.trim(),
|
|
3974
|
+
"\u201D"
|
|
3975
|
+
]
|
|
3976
|
+
}
|
|
3977
|
+
)
|
|
3978
|
+
] }) : null
|
|
3979
|
+
] })
|
|
3980
|
+
] }) })
|
|
3981
|
+
] })
|
|
3982
|
+
] });
|
|
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
|
+
}
|
|
3158
4389
|
//# sourceMappingURL=index.cjs.map
|