@mulmoclaude/collection-plugin 0.7.6 → 0.8.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/dist/style.css +14 -0
- package/dist/vue/components/CollectionBacklinksView.vue.d.ts +9 -0
- package/dist/vue/components/CollectionBacklinksView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionRecordPanel.vue.d.ts.map +1 -1
- package/dist/vue/components/CollectionView.vue.d.ts.map +1 -1
- package/dist/vue/useCollectionRendering.d.ts +2 -1
- package/dist/vue/useCollectionRendering.d.ts.map +1 -1
- package/dist/vue/useCollectionRendering.helpers.d.ts +11 -0
- package/dist/vue/useCollectionRendering.helpers.d.ts.map +1 -1
- package/dist/vue/useCollectionRendering.renderers.d.ts +11 -1
- package/dist/vue/useCollectionRendering.renderers.d.ts.map +1 -1
- package/dist/vue/useLinkedCollectionCaches.d.ts +5 -1
- package/dist/vue/useLinkedCollectionCaches.d.ts.map +1 -1
- package/dist/vue.cjs +187 -51
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +188 -52
- package/dist/vue.js.map +1 -1
- package/package.json +3 -3
package/dist/vue.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MINUTES_PER_DAY, TOOL_DEFINITION, actionVisible, assignLanes, boolSortValue, bucketRecords, buildMonthGrid, buildUpdatedRecord, coerceInlineValue, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumSortValue, errorMessage, executePresentCollection, fieldVisible, firstMissingRequiredField, isSortableField, itemIdOf, itemLabelOf, labelFieldFor, nextSortDirection, numericSortValue, resolveEnumColor, rowFromItem, shortHexId, sortItems, stringSortValue, ymdKey } from "@mulmoclaude/core/collection";
|
|
1
|
+
import { MINUTES_PER_DAY, TOOL_DEFINITION, actionVisible, assignLanes, backlinkRows, boolSortValue, bucketRecords, buildMonthGrid, buildUpdatedRecord, coerceInlineValue, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, embedTargetId, emptyRow, enumSortValue, errorMessage, executePresentCollection, fieldVisible, firstMissingRequiredField, isSortableField, itemIdOf, itemLabelOf, labelFieldFor, nextSortDirection, numericSortValue, resolveEnumColor, rowFromItem, shortHexId, sortItems, stringSortValue, ymdKey } from "@mulmoclaude/core/collection";
|
|
2
2
|
import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, effectScope, mergeModels, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useModel, vModelCheckbox, vModelDynamic, vModelSelect, vModelText, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
|
|
3
3
|
import { createI18n } from "vue-i18n";
|
|
4
4
|
import draggable from "vuedraggable";
|
|
@@ -1210,18 +1210,18 @@ var CollectionRecordModal_default = /* @__PURE__ */ defineComponent({
|
|
|
1210
1210
|
});
|
|
1211
1211
|
//#endregion
|
|
1212
1212
|
//#region src/vue/components/CollectionCalendarView.vue?vue&type=script&setup=true&lang.ts
|
|
1213
|
-
var _hoisted_1$
|
|
1213
|
+
var _hoisted_1$15 = {
|
|
1214
1214
|
class: "flex flex-col gap-3",
|
|
1215
1215
|
"data-testid": "collection-calendar"
|
|
1216
1216
|
};
|
|
1217
|
-
var _hoisted_2$
|
|
1218
|
-
var _hoisted_3$
|
|
1219
|
-
var _hoisted_4$
|
|
1220
|
-
var _hoisted_5$
|
|
1217
|
+
var _hoisted_2$14 = { class: "flex items-center gap-2" };
|
|
1218
|
+
var _hoisted_3$14 = ["aria-label"];
|
|
1219
|
+
var _hoisted_4$14 = ["aria-label"];
|
|
1220
|
+
var _hoisted_5$13 = {
|
|
1221
1221
|
class: "text-sm font-bold text-slate-800 flex-1",
|
|
1222
1222
|
"data-testid": "collection-calendar-month"
|
|
1223
1223
|
};
|
|
1224
|
-
var _hoisted_6$
|
|
1224
|
+
var _hoisted_6$12 = { class: "grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none" };
|
|
1225
1225
|
var _hoisted_7$10 = { class: "grid grid-cols-7 gap-1" };
|
|
1226
1226
|
var _hoisted_8$10 = [
|
|
1227
1227
|
"aria-label",
|
|
@@ -1351,23 +1351,23 @@ var CollectionCalendarView_default = /* @__PURE__ */ defineComponent({
|
|
|
1351
1351
|
viewMonth.value = now.getMonth() + 1;
|
|
1352
1352
|
}
|
|
1353
1353
|
return (_ctx, _cache) => {
|
|
1354
|
-
return openBlock(), createElementBlock("div", _hoisted_1$
|
|
1355
|
-
createElementVNode("div", _hoisted_2$
|
|
1354
|
+
return openBlock(), createElementBlock("div", _hoisted_1$15, [
|
|
1355
|
+
createElementVNode("div", _hoisted_2$14, [
|
|
1356
1356
|
createElementVNode("button", {
|
|
1357
1357
|
type: "button",
|
|
1358
1358
|
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
1359
1359
|
"aria-label": unref(t)("collectionsView.calendarPrevMonth"),
|
|
1360
1360
|
"data-testid": "collection-calendar-prev",
|
|
1361
1361
|
onClick: _cache[0] || (_cache[0] = ($event) => stepMonth(-1))
|
|
1362
|
-
}, [..._cache[2] || (_cache[2] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_left", -1)])], 8, _hoisted_3$
|
|
1362
|
+
}, [..._cache[2] || (_cache[2] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_left", -1)])], 8, _hoisted_3$14),
|
|
1363
1363
|
createElementVNode("button", {
|
|
1364
1364
|
type: "button",
|
|
1365
1365
|
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
1366
1366
|
"aria-label": unref(t)("collectionsView.calendarNextMonth"),
|
|
1367
1367
|
"data-testid": "collection-calendar-next",
|
|
1368
1368
|
onClick: _cache[1] || (_cache[1] = ($event) => stepMonth(1))
|
|
1369
|
-
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_right", -1)])], 8, _hoisted_4$
|
|
1370
|
-
createElementVNode("h3", _hoisted_5$
|
|
1369
|
+
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "chevron_right", -1)])], 8, _hoisted_4$14),
|
|
1370
|
+
createElementVNode("h3", _hoisted_5$13, toDisplayString(monthLabel.value), 1),
|
|
1371
1371
|
createElementVNode("button", {
|
|
1372
1372
|
type: "button",
|
|
1373
1373
|
class: "h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors",
|
|
@@ -1375,7 +1375,7 @@ var CollectionCalendarView_default = /* @__PURE__ */ defineComponent({
|
|
|
1375
1375
|
onClick: goToday
|
|
1376
1376
|
}, toDisplayString(unref(t)("collectionsView.calendarToday")), 1)
|
|
1377
1377
|
]),
|
|
1378
|
-
createElementVNode("div", _hoisted_6$
|
|
1378
|
+
createElementVNode("div", _hoisted_6$12, [(openBlock(true), createElementBlock(Fragment, null, renderList(weekdayLabels.value, (label, idx) => {
|
|
1379
1379
|
return openBlock(), createElementBlock("div", {
|
|
1380
1380
|
key: idx,
|
|
1381
1381
|
class: "px-1 py-1 text-center"
|
|
@@ -1416,18 +1416,18 @@ var CollectionCalendarView_default = /* @__PURE__ */ defineComponent({
|
|
|
1416
1416
|
});
|
|
1417
1417
|
//#endregion
|
|
1418
1418
|
//#region src/vue/components/CollectionDayView.vue?vue&type=script&setup=true&lang.ts
|
|
1419
|
-
var _hoisted_1$
|
|
1420
|
-
var _hoisted_2$
|
|
1419
|
+
var _hoisted_1$14 = { class: "flex items-center gap-2 border-b border-slate-200 px-4 py-3" };
|
|
1420
|
+
var _hoisted_2$13 = {
|
|
1421
1421
|
class: "flex-1 text-sm font-bold text-slate-800",
|
|
1422
1422
|
"data-testid": "collection-day-view-title"
|
|
1423
1423
|
};
|
|
1424
|
-
var _hoisted_3$
|
|
1425
|
-
var _hoisted_4$
|
|
1426
|
-
var _hoisted_5$
|
|
1424
|
+
var _hoisted_3$13 = ["aria-label"];
|
|
1425
|
+
var _hoisted_4$13 = ["aria-label"];
|
|
1426
|
+
var _hoisted_5$12 = {
|
|
1427
1427
|
key: 0,
|
|
1428
1428
|
class: "px-4 py-10 text-center text-sm text-slate-400"
|
|
1429
1429
|
};
|
|
1430
|
-
var _hoisted_6$
|
|
1430
|
+
var _hoisted_6$11 = { class: "absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400" };
|
|
1431
1431
|
var _hoisted_7$9 = {
|
|
1432
1432
|
class: "absolute inset-y-0 right-0",
|
|
1433
1433
|
style: { "left": "2.75rem" }
|
|
@@ -1513,6 +1513,7 @@ var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
|
1513
1513
|
"datetime",
|
|
1514
1514
|
"table",
|
|
1515
1515
|
"embed",
|
|
1516
|
+
"backlinks",
|
|
1516
1517
|
"image",
|
|
1517
1518
|
"markdown"
|
|
1518
1519
|
]);
|
|
@@ -1608,8 +1609,8 @@ var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
|
1608
1609
|
role: "dialog",
|
|
1609
1610
|
"aria-modal": "true"
|
|
1610
1611
|
}, [createElementVNode("div", { class: normalizeClass(["flex min-h-0 flex-col", __props.showDetail ? "w-80 shrink-0 border-r border-slate-200" : "w-full"]) }, [
|
|
1611
|
-
createElementVNode("div", _hoisted_1$
|
|
1612
|
-
createElementVNode("h3", _hoisted_2$
|
|
1612
|
+
createElementVNode("div", _hoisted_1$14, [
|
|
1613
|
+
createElementVNode("h3", _hoisted_2$13, toDisplayString(dayLabel.value), 1),
|
|
1613
1614
|
__props.canCreate ? (openBlock(), createElementBlock("button", {
|
|
1614
1615
|
key: 0,
|
|
1615
1616
|
type: "button",
|
|
@@ -1617,16 +1618,16 @@ var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
|
1617
1618
|
"aria-label": unref(t)("collectionsView.calendarCreateOn", { date: dayKey.value }),
|
|
1618
1619
|
"data-testid": "collection-day-view-create",
|
|
1619
1620
|
onClick: onCreate
|
|
1620
|
-
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "add", -1)])], 8, _hoisted_3$
|
|
1621
|
+
}, [..._cache[3] || (_cache[3] = [createElementVNode("span", { class: "material-icons text-lg" }, "add", -1)])], 8, _hoisted_3$13)) : createCommentVNode("", true),
|
|
1621
1622
|
createElementVNode("button", {
|
|
1622
1623
|
type: "button",
|
|
1623
1624
|
class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
|
|
1624
1625
|
"aria-label": unref(t)("collectionsView.dayViewClose"),
|
|
1625
1626
|
"data-testid": "collection-day-view-close",
|
|
1626
1627
|
onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
|
|
1627
|
-
}, [..._cache[4] || (_cache[4] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$
|
|
1628
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$13)
|
|
1628
1629
|
]),
|
|
1629
|
-
timedEntries.value.length === 0 && allDayEntries.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_5$
|
|
1630
|
+
timedEntries.value.length === 0 && allDayEntries.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_5$12, toDisplayString(unref(t)("collectionsView.dayViewEmpty")), 1)) : (openBlock(), createElementBlock("div", {
|
|
1630
1631
|
key: 1,
|
|
1631
1632
|
ref_key: "scrollEl",
|
|
1632
1633
|
ref: scrollEl,
|
|
@@ -1640,7 +1641,7 @@ var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
|
1640
1641
|
key: hour,
|
|
1641
1642
|
class: "absolute left-0 right-0 border-t border-slate-100",
|
|
1642
1643
|
style: normalizeStyle({ top: `${(hour - 1) * HOUR_PX}px` })
|
|
1643
|
-
}, [createElementVNode("span", _hoisted_6$
|
|
1644
|
+
}, [createElementVNode("span", _hoisted_6$11, toDisplayString(hourLabel(hour - 1)), 1)], 4);
|
|
1644
1645
|
}), 64)), createElementVNode("div", _hoisted_7$9, [(openBlock(true), createElementBlock(Fragment, null, renderList(timedEntries.value, (entry) => {
|
|
1645
1646
|
return openBlock(), createElementBlock("button", {
|
|
1646
1647
|
key: entry.id,
|
|
@@ -1675,15 +1676,15 @@ var CollectionDayView_default = /* @__PURE__ */ defineComponent({
|
|
|
1675
1676
|
});
|
|
1676
1677
|
//#endregion
|
|
1677
1678
|
//#region src/vue/components/CollectionKanbanView.vue?vue&type=script&setup=true&lang.ts
|
|
1678
|
-
var _hoisted_1$
|
|
1679
|
+
var _hoisted_1$13 = {
|
|
1679
1680
|
class: "h-full overflow-x-auto overflow-y-hidden",
|
|
1680
1681
|
"data-testid": "collection-kanban"
|
|
1681
1682
|
};
|
|
1682
|
-
var _hoisted_2$
|
|
1683
|
-
var _hoisted_3$
|
|
1684
|
-
var _hoisted_4$
|
|
1685
|
-
var _hoisted_5$
|
|
1686
|
-
var _hoisted_6$
|
|
1683
|
+
var _hoisted_2$12 = { class: "flex gap-3 h-full p-1 min-w-max" };
|
|
1684
|
+
var _hoisted_3$12 = ["data-testid"];
|
|
1685
|
+
var _hoisted_4$12 = { class: "flex items-center justify-between px-3 py-2 border-b border-slate-200" };
|
|
1686
|
+
var _hoisted_5$11 = { class: "flex items-center gap-2 min-w-0" };
|
|
1687
|
+
var _hoisted_6$10 = ["title"];
|
|
1687
1688
|
var _hoisted_7$8 = { class: "text-[11px] text-slate-400 shrink-0" };
|
|
1688
1689
|
var _hoisted_8$8 = [
|
|
1689
1690
|
"data-testid",
|
|
@@ -1802,15 +1803,15 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
|
|
|
1802
1803
|
emit("move", itemId(item), next);
|
|
1803
1804
|
}
|
|
1804
1805
|
return (_ctx, _cache) => {
|
|
1805
|
-
return openBlock(), createElementBlock("div", _hoisted_1$
|
|
1806
|
+
return openBlock(), createElementBlock("div", _hoisted_1$13, [createElementVNode("div", _hoisted_2$12, [(openBlock(true), createElementBlock(Fragment, null, renderList(columns.value, (column) => {
|
|
1806
1807
|
return openBlock(), createElementBlock("div", {
|
|
1807
1808
|
key: column.value,
|
|
1808
1809
|
"data-testid": `collection-kanban-column-${column.value || "uncategorized"}`,
|
|
1809
1810
|
class: "w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg"
|
|
1810
|
-
}, [createElementVNode("div", _hoisted_4$
|
|
1811
|
+
}, [createElementVNode("div", _hoisted_4$12, [createElementVNode("div", _hoisted_5$11, [createElementVNode("span", { class: normalizeClass(["w-2 h-2 rounded-full shrink-0", unref(resolveEnumColor)(__props.schema, __props.groupField, column.value).dot]) }, null, 2), createElementVNode("span", {
|
|
1811
1812
|
class: "font-semibold text-xs text-slate-600 truncate",
|
|
1812
1813
|
title: column.label
|
|
1813
|
-
}, toDisplayString(column.label), 9, _hoisted_6$
|
|
1814
|
+
}, toDisplayString(column.label), 9, _hoisted_6$10)]), createElementVNode("span", _hoisted_7$8, toDisplayString(itemsByColumn(column.value).length), 1)]), createVNode(unref(draggable), {
|
|
1814
1815
|
"model-value": itemsByColumn(column.value),
|
|
1815
1816
|
"item-key": __props.schema.primaryKey,
|
|
1816
1817
|
group: "collection-kanban-cards",
|
|
@@ -1841,7 +1842,7 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
|
|
|
1841
1842
|
"model-value",
|
|
1842
1843
|
"item-key",
|
|
1843
1844
|
"onChange"
|
|
1844
|
-
])], 8, _hoisted_3$
|
|
1845
|
+
])], 8, _hoisted_3$12);
|
|
1845
1846
|
}), 128))])]);
|
|
1846
1847
|
};
|
|
1847
1848
|
}
|
|
@@ -1870,6 +1871,61 @@ function activatePathLink(event, path, stop = false) {
|
|
|
1870
1871
|
nav(path);
|
|
1871
1872
|
}
|
|
1872
1873
|
//#endregion
|
|
1874
|
+
//#region src/vue/components/CollectionBacklinksView.vue?vue&type=script&setup=true&lang.ts
|
|
1875
|
+
var _hoisted_1$12 = ["data-testid"];
|
|
1876
|
+
var _hoisted_2$11 = { class: "w-full text-[11px] text-slate-600 bg-white" };
|
|
1877
|
+
var _hoisted_3$11 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
|
|
1878
|
+
var _hoisted_4$11 = { class: "divide-y divide-slate-100" };
|
|
1879
|
+
var _hoisted_5$10 = [
|
|
1880
|
+
"data-testid",
|
|
1881
|
+
"onClick",
|
|
1882
|
+
"onKeydown"
|
|
1883
|
+
];
|
|
1884
|
+
var _hoisted_6$9 = ["data-testid"];
|
|
1885
|
+
//#endregion
|
|
1886
|
+
//#region src/vue/components/CollectionBacklinksView.vue
|
|
1887
|
+
var CollectionBacklinksView_default = /* @__PURE__ */ defineComponent({
|
|
1888
|
+
__name: "CollectionBacklinksView",
|
|
1889
|
+
props: {
|
|
1890
|
+
view: {},
|
|
1891
|
+
fieldKey: {}
|
|
1892
|
+
},
|
|
1893
|
+
setup(__props) {
|
|
1894
|
+
const { t } = useCollectionI18n();
|
|
1895
|
+
return (_ctx, _cache) => {
|
|
1896
|
+
return __props.view.rows.length > 0 ? (openBlock(), createElementBlock("div", {
|
|
1897
|
+
key: 0,
|
|
1898
|
+
class: "border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1",
|
|
1899
|
+
"data-testid": `collections-backlinks-${__props.fieldKey}`
|
|
1900
|
+
}, [createElementVNode("table", _hoisted_2$11, [createElementVNode("thead", _hoisted_3$11, [createElementVNode("tr", null, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.view.columns, (column) => {
|
|
1901
|
+
return openBlock(), createElementBlock("th", {
|
|
1902
|
+
key: column.key,
|
|
1903
|
+
class: "text-left px-4 py-2 font-bold"
|
|
1904
|
+
}, toDisplayString(column.label), 1);
|
|
1905
|
+
}), 128))])]), createElementVNode("tbody", _hoisted_4$11, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.view.rows, (row) => {
|
|
1906
|
+
return openBlock(), createElementBlock("tr", {
|
|
1907
|
+
key: row.id,
|
|
1908
|
+
class: "group hover:bg-indigo-50/30 cursor-pointer transition-colors",
|
|
1909
|
+
role: "link",
|
|
1910
|
+
tabindex: "0",
|
|
1911
|
+
"data-testid": `collections-backlinks-${__props.fieldKey}-${row.id}`,
|
|
1912
|
+
onClick: ($event) => unref(activateRefLink)($event, __props.view.fromSlug, row.id),
|
|
1913
|
+
onKeydown: [withKeys(($event) => unref(activateRefLink)($event, __props.view.fromSlug, row.id), ["enter"]), withKeys(($event) => unref(activateRefLink)($event, __props.view.fromSlug, row.id), ["space"])]
|
|
1914
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(row.cells, (cell, cellIdx) => {
|
|
1915
|
+
return openBlock(), createElementBlock("td", {
|
|
1916
|
+
key: __props.view.columns[cellIdx]?.key ?? cellIdx,
|
|
1917
|
+
class: "px-4 py-2 align-middle font-medium"
|
|
1918
|
+
}, [createElementVNode("span", { class: normalizeClass(cellIdx === 0 ? "text-indigo-600 group-hover:text-indigo-800 font-bold" : "") }, toDisplayString(cell), 3)]);
|
|
1919
|
+
}), 128))], 40, _hoisted_5$10);
|
|
1920
|
+
}), 128))])])], 8, _hoisted_1$12)) : (openBlock(), createElementBlock("span", {
|
|
1921
|
+
key: 1,
|
|
1922
|
+
class: "text-slate-400 italic",
|
|
1923
|
+
"data-testid": `collections-backlinks-${__props.fieldKey}`
|
|
1924
|
+
}, toDisplayString(unref(t)("collectionsView.noRows")), 9, _hoisted_6$9));
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
//#endregion
|
|
1873
1929
|
//#region src/vue/components/CollectionEmbedView.vue?vue&type=script&setup=true&lang.ts
|
|
1874
1930
|
var _hoisted_1$11 = [
|
|
1875
1931
|
"href",
|
|
@@ -2170,7 +2226,7 @@ var _hoisted_71$1 = [
|
|
|
2170
2226
|
"onClick"
|
|
2171
2227
|
];
|
|
2172
2228
|
var _hoisted_72$1 = {
|
|
2173
|
-
key:
|
|
2229
|
+
key: 14,
|
|
2174
2230
|
class: "text-slate-800 font-semibold"
|
|
2175
2231
|
};
|
|
2176
2232
|
var _hoisted_73$1 = {
|
|
@@ -2243,6 +2299,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
|
|
|
2243
2299
|
* safe in the template. */
|
|
2244
2300
|
const detailRecord = computed(() => editing.value ? props.liveDerived ?? props.liveRecord ?? {} : props.viewing ?? {});
|
|
2245
2301
|
const embedViews = computed(() => props.render.embedViewsFor(detailRecord.value));
|
|
2302
|
+
const backlinksViews = computed(() => props.render.backlinksViewsFor(detailRecord.value));
|
|
2246
2303
|
const embedOwnerByKey = computed(() => {
|
|
2247
2304
|
const map = /* @__PURE__ */ new Map();
|
|
2248
2305
|
for (const field of Object.values(props.collection.schema.fields)) if (field.type === "embed" && field.idField) map.set(field.idField, field);
|
|
@@ -2259,11 +2316,12 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
|
|
|
2259
2316
|
return editing.value.mode === "create" ? "collections-create" : "collections-edit";
|
|
2260
2317
|
});
|
|
2261
2318
|
/** Whether a field gets an editable control in edit mode. Toggle (a
|
|
2262
|
-
* projection of an enum that has its own input), derived (computed),
|
|
2263
|
-
* embed (a foreign record)
|
|
2264
|
-
*
|
|
2319
|
+
* projection of an enum that has its own input), derived (computed),
|
|
2320
|
+
* embed (a foreign record), and backlinks (reverse refs owned by OTHER
|
|
2321
|
+
* records) stay read-only in both modes, so the cell geometry never
|
|
2322
|
+
* changes on the view↔edit toggle. */
|
|
2265
2323
|
function isEditableType(type) {
|
|
2266
|
-
return type !== "toggle" && type !== "derived" && type !== "embed";
|
|
2324
|
+
return type !== "toggle" && type !== "derived" && type !== "embed" && type !== "backlinks";
|
|
2267
2325
|
}
|
|
2268
2326
|
/** Wide field types span the full grid width in BOTH modes — keeping
|
|
2269
2327
|
* `image` full-width here (not just when viewing) is what stops a field
|
|
@@ -2273,6 +2331,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
|
|
|
2273
2331
|
"table",
|
|
2274
2332
|
"markdown",
|
|
2275
2333
|
"embed",
|
|
2334
|
+
"backlinks",
|
|
2276
2335
|
"image"
|
|
2277
2336
|
].includes(field.type) ? "col-span-full" : "col-span-1";
|
|
2278
2337
|
}
|
|
@@ -2575,28 +2634,32 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
|
|
|
2575
2634
|
key: 8,
|
|
2576
2635
|
view: embedViews.value[key],
|
|
2577
2636
|
"field-key": String(key)
|
|
2578
|
-
}, null, 8, ["view", "field-key"])) : field.type === "
|
|
2637
|
+
}, null, 8, ["view", "field-key"])) : field.type === "backlinks" && backlinksViews.value[key] ? (openBlock(), createBlock(CollectionBacklinksView_default, {
|
|
2579
2638
|
key: 9,
|
|
2639
|
+
view: backlinksViews.value[key],
|
|
2640
|
+
"field-key": String(key)
|
|
2641
|
+
}, null, 8, ["view", "field-key"])) : field.type === "image" && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? (openBlock(), createElementBlock("img", {
|
|
2642
|
+
key: 10,
|
|
2580
2643
|
src: unref(resolveImageSrc)(String(detailRecord.value[key])),
|
|
2581
2644
|
alt: field.label,
|
|
2582
2645
|
class: "max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50",
|
|
2583
2646
|
"data-testid": `collections-detail-image-${key}`
|
|
2584
2647
|
}, null, 8, _hoisted_68$1)) : field.type !== "file" && __props.render.isExternalUrl(detailRecord.value[key]) ? (openBlock(), createElementBlock("a", {
|
|
2585
|
-
key:
|
|
2648
|
+
key: 11,
|
|
2586
2649
|
href: String(detailRecord.value[key]),
|
|
2587
2650
|
target: "_blank",
|
|
2588
2651
|
rel: "noopener noreferrer",
|
|
2589
2652
|
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
2590
2653
|
"data-testid": `collections-detail-url-${key}`
|
|
2591
2654
|
}, toDisplayString(String(detailRecord.value[key])), 9, _hoisted_69$1)) : field.type === "file" && __props.render.artifactUrl(detailRecord.value[key]) ? (openBlock(), createElementBlock("a", {
|
|
2592
|
-
key:
|
|
2655
|
+
key: 12,
|
|
2593
2656
|
href: __props.render.artifactUrl(detailRecord.value[key]) ?? void 0,
|
|
2594
2657
|
target: "_blank",
|
|
2595
2658
|
rel: "noopener noreferrer",
|
|
2596
2659
|
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
2597
2660
|
"data-testid": `collections-detail-file-${key}`
|
|
2598
2661
|
}, toDisplayString(String(detailRecord.value[key])), 9, _hoisted_70$1)) : field.type === "file" && __props.render.fileRoutePath(detailRecord.value[key]) ? (openBlock(), createElementBlock("a", {
|
|
2599
|
-
key:
|
|
2662
|
+
key: 13,
|
|
2600
2663
|
href: __props.render.fileRoutePath(detailRecord.value[key]) ?? void 0,
|
|
2601
2664
|
class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
|
|
2602
2665
|
"data-testid": `collections-detail-file-${key}`,
|
|
@@ -3116,6 +3179,14 @@ function uniqueEmbedTargets(schema) {
|
|
|
3116
3179
|
for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
|
|
3117
3180
|
return [...targets];
|
|
3118
3181
|
}
|
|
3182
|
+
/** Slugs of every SOURCE collection a `backlinks` field reverses over.
|
|
3183
|
+
* Top-level only, like `embed` (the schema rejects `backlinks` inside a
|
|
3184
|
+
* table's `of`). Mirrors the server's `uniqueBacklinkSources`. */
|
|
3185
|
+
function uniqueBacklinkSources(schema) {
|
|
3186
|
+
const sources = /* @__PURE__ */ new Set();
|
|
3187
|
+
for (const field of Object.values(schema.fields)) if (field.type === "backlinks" && field.from.length > 0) sources.add(field.from);
|
|
3188
|
+
return [...sources];
|
|
3189
|
+
}
|
|
3119
3190
|
function buildRefDisplayMap(detail) {
|
|
3120
3191
|
const { fields, primaryKey } = detail.collection.schema;
|
|
3121
3192
|
const displayField = displayFieldFor(fields, primaryKey);
|
|
@@ -3128,15 +3199,23 @@ function buildRefDisplayMap(detail) {
|
|
|
3128
3199
|
}
|
|
3129
3200
|
return map;
|
|
3130
3201
|
}
|
|
3131
|
-
|
|
3132
|
-
|
|
3202
|
+
/** Index DERIVED records by primary key — the client mirror of the
|
|
3203
|
+
* server's `loadTarget` indexing: string non-empty ids only, one record
|
|
3204
|
+
* per id (a duplicate keeps the LAST record in its FIRST position —
|
|
3205
|
+
* plain-object key semantics). Shared by the ref-record cache and the
|
|
3206
|
+
* backlinks view so every client consumer agrees with server enrichment
|
|
3207
|
+
* on which source records exist. */
|
|
3208
|
+
function derivedRecordsById(schema, items) {
|
|
3133
3209
|
const map = {};
|
|
3134
|
-
for (const item of
|
|
3210
|
+
for (const item of items) {
|
|
3135
3211
|
const slugRaw = item[schema.primaryKey];
|
|
3136
3212
|
if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});
|
|
3137
3213
|
}
|
|
3138
3214
|
return map;
|
|
3139
3215
|
}
|
|
3216
|
+
function buildRefRecordMap(detail) {
|
|
3217
|
+
return derivedRecordsById(detail.collection.schema, detail.items);
|
|
3218
|
+
}
|
|
3140
3219
|
function sortedRefOptions(map) {
|
|
3141
3220
|
return Object.entries(map).map(([slug, display]) => ({
|
|
3142
3221
|
slug,
|
|
@@ -3161,10 +3240,14 @@ function buildEmbedOptions(schema, items) {
|
|
|
3161
3240
|
//#endregion
|
|
3162
3241
|
//#region src/vue/useLinkedCollectionCaches.ts
|
|
3163
3242
|
/** The de-duplicated ref + embed target slugs a schema links to. `allTargets`
|
|
3164
|
-
* is the union (each target fetched once even when both ref'd and embedded).
|
|
3243
|
+
* is the union (each target fetched once even when both ref'd and embedded).
|
|
3244
|
+
* `backlinks` SOURCE collections ride in `embedTargets`: a backlink needs
|
|
3245
|
+
* exactly what an embed target caches (the source's schema + items in
|
|
3246
|
+
* `embedCache`), so reverse sources reuse that cache rather than adding a
|
|
3247
|
+
* parallel one. */
|
|
3165
3248
|
function linkedTargets(schema) {
|
|
3166
3249
|
const refTargets = new Set(uniqueRefTargets(schema));
|
|
3167
|
-
const embedTargets = new Set(uniqueEmbedTargets(schema));
|
|
3250
|
+
const embedTargets = /* @__PURE__ */ new Set([...uniqueEmbedTargets(schema), ...uniqueBacklinkSources(schema)]);
|
|
3168
3251
|
return {
|
|
3169
3252
|
refTargets,
|
|
3170
3253
|
embedTargets,
|
|
@@ -3309,6 +3392,58 @@ function buildEmbedViews(schema, embedCache, record, locale) {
|
|
|
3309
3392
|
}
|
|
3310
3393
|
return out;
|
|
3311
3394
|
}
|
|
3395
|
+
/** One backlinks table CELL: money formatted with its currency, anything
|
|
3396
|
+
* else through the same `formatCell` list tables use — so a markdown
|
|
3397
|
+
* source column (a worklog `notes`) shows the one-line 80-char preview,
|
|
3398
|
+
* not the whole text. Unknown `display` key ⇒ plain text (fail-soft). */
|
|
3399
|
+
function formatBacklinkCell(sourceField, value, row, locale) {
|
|
3400
|
+
if (!sourceField) return detailText(value);
|
|
3401
|
+
if (sourceField.type === "money") return formatMoney(value, resolveCurrency(sourceField, row), locale);
|
|
3402
|
+
return formatCell(value, sourceField.type);
|
|
3403
|
+
}
|
|
3404
|
+
/** Build the read-only backlinks view-models for one record: for each
|
|
3405
|
+
* `backlinks` field, the rows of `from` whose `via` points at the open
|
|
3406
|
+
* record (matched via the SHARED `backlinkRows`, on source records
|
|
3407
|
+
* derived exactly like the server's enrichment — so a `display`/`filter`
|
|
3408
|
+
* on a derived column such as an invoice `total` agrees on both sides).
|
|
3409
|
+
* Source data comes out of `embedCache` (reverse sources ride the embed
|
|
3410
|
+
* fan-out — see `linkedTargets`). Fail-soft: an unloadable source or an
|
|
3411
|
+
* unknown `display` key degrades to `found: false` / a raw-key column,
|
|
3412
|
+
* never a throw. */
|
|
3413
|
+
function buildBacklinksViews(schema, embedCache, record, locale) {
|
|
3414
|
+
const out = {};
|
|
3415
|
+
if (!schema) return out;
|
|
3416
|
+
for (const [key, field] of Object.entries(schema.fields)) {
|
|
3417
|
+
if (field.type !== "backlinks") continue;
|
|
3418
|
+
const columns = field.display.map((col) => ({
|
|
3419
|
+
key: col,
|
|
3420
|
+
label: col
|
|
3421
|
+
}));
|
|
3422
|
+
const data = embedCache[field.from];
|
|
3423
|
+
if (!data) {
|
|
3424
|
+
out[key] = {
|
|
3425
|
+
found: false,
|
|
3426
|
+
columns,
|
|
3427
|
+
rows: [],
|
|
3428
|
+
fromSlug: field.from
|
|
3429
|
+
};
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
for (const column of columns) column.label = data.schema.fields[column.key]?.label ?? column.key;
|
|
3433
|
+
const selfId = String(record?.[schema.primaryKey] ?? "");
|
|
3434
|
+
const sourceById = derivedRecordsById(data.schema, data.items);
|
|
3435
|
+
out[key] = {
|
|
3436
|
+
found: true,
|
|
3437
|
+
columns,
|
|
3438
|
+
rows: backlinkRows(field, selfId, Object.values(sourceById)).map((row) => ({
|
|
3439
|
+
id: String(row[data.schema.primaryKey] ?? ""),
|
|
3440
|
+
cells: columns.map((column) => formatBacklinkCell(data.schema.fields[column.key], row[column.key], row, locale))
|
|
3441
|
+
})),
|
|
3442
|
+
fromSlug: field.from
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
return out;
|
|
3446
|
+
}
|
|
3312
3447
|
function renderSubCell(subField, value, record, refCache, locale) {
|
|
3313
3448
|
if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale);
|
|
3314
3449
|
if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return lookupRefDisplay(refCache, subField.to, value);
|
|
@@ -3349,6 +3484,7 @@ function useCollectionRendering(collection, locale) {
|
|
|
3349
3484
|
refOptions: (targetSlug) => refOptionsFor(refCache.value, targetSlug),
|
|
3350
3485
|
embedOptions: (targetSlug) => embedOptionsFor(embedCache.value, targetSlug),
|
|
3351
3486
|
embedViewsFor: (record) => buildEmbedViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
|
|
3487
|
+
backlinksViewsFor: (record) => buildBacklinksViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
|
|
3352
3488
|
currencySymbol: (currency) => currencySymbolForLocale(currency, locale.value),
|
|
3353
3489
|
artifactUrl: (value) => collectionUi().fileAssetUrl(value),
|
|
3354
3490
|
fileRoutePath: (value) => collectionUi().fileRoutePath(value),
|
|
@@ -4125,7 +4261,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
|
|
|
4125
4261
|
* list table only (a whole embedded record doesn't fit a table cell,
|
|
4126
4262
|
* and it'd be identical in every row). The detail modal and the edit
|
|
4127
4263
|
* form iterate the full `schema.fields` so embeds render there too. */
|
|
4128
|
-
const listColumnFields = computed(() => collection.value ? Object.entries(collection.value.schema.fields).filter(([key, field]) => field.type !== "embed" && field.type !== "image" && key !== collection.value?.schema.primaryKey) : []);
|
|
4264
|
+
const listColumnFields = computed(() => collection.value ? Object.entries(collection.value.schema.fields).filter(([key, field]) => field.type !== "embed" && field.type !== "backlinks" && field.type !== "image" && key !== collection.value?.schema.primaryKey) : []);
|
|
4129
4265
|
/** True when the current collection declares `schema.singleton` —
|
|
4130
4266
|
* exactly one record, its primary key fixed to the declared value. */
|
|
4131
4267
|
const isSingleton = computed(() => Boolean(collection.value?.schema.singleton));
|
|
@@ -4314,7 +4450,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
|
|
|
4314
4450
|
boolOriginallyPresent[key] = false;
|
|
4315
4451
|
boolTouched[key] = false;
|
|
4316
4452
|
} else if (field.type === "table") table[key] = [];
|
|
4317
|
-
else if (field.type !== "derived" && field.type !== "embed" && field.type !== "toggle") text[key] = "";
|
|
4453
|
+
else if (field.type !== "derived" && field.type !== "embed" && field.type !== "backlinks" && field.type !== "toggle") text[key] = "";
|
|
4318
4454
|
const { singleton, primaryKey } = collection.value.schema;
|
|
4319
4455
|
if (singleton) text[primaryKey] = singleton;
|
|
4320
4456
|
else if (primaryKey in text) text[primaryKey] = generateUniqueItemId(primaryKey);
|
|
@@ -4346,7 +4482,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
|
|
|
4346
4482
|
} else if (field.type === "table" && field.of) {
|
|
4347
4483
|
const sub = field.of;
|
|
4348
4484
|
table[key] = (Array.isArray(raw) ? raw : []).filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)).map((row) => rowFromItem(row, sub));
|
|
4349
|
-
} else if (field.type !== "derived" && field.type !== "embed" && field.type !== "toggle") text[key] = raw === void 0 || raw === null ? "" : String(raw);
|
|
4485
|
+
} else if (field.type !== "derived" && field.type !== "embed" && field.type !== "backlinks" && field.type !== "toggle") text[key] = raw === void 0 || raw === null ? "" : String(raw);
|
|
4350
4486
|
}
|
|
4351
4487
|
const primaryRaw = item[collection.value.schema.primaryKey];
|
|
4352
4488
|
const originalId = typeof primaryRaw === "string" ? primaryRaw : String(primaryRaw ?? "");
|