@conduction/nextcloud-vue 2.66.0 → 2.68.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.
Files changed (95) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +19 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +37 -26
  9. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  10. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js +3 -3
  11. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
  13. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  15. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +87 -18
  16. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  17. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +6 -1
  18. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +89 -49
  20. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  21. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +337 -315
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  23. package/dist/esm/components/CnIndexPage/defaultActions.js +6 -7
  24. package/dist/esm/components/CnIndexPage/defaultActions.js.map +1 -1
  25. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +33 -34
  26. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  27. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js +3 -3
  28. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  29. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +13 -13
  30. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  31. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  32. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +3 -2
  33. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  35. package/dist/esm/composables/useContextMenu.js +4 -2
  36. package/dist/esm/composables/useContextMenu.js.map +1 -1
  37. package/dist/esm/composables/useDependencyCheck.js +44 -1
  38. package/dist/esm/composables/useDependencyCheck.js.map +1 -1
  39. package/dist/esm/composables/useListView.js +43 -1
  40. package/dist/esm/composables/useListView.js.map +1 -1
  41. package/dist/esm/composables/useSetupStatus.js +26 -3
  42. package/dist/esm/composables/useSetupStatus.js.map +1 -1
  43. package/dist/esm/l10n/en.json.js +4 -1
  44. package/dist/esm/l10n/en.json.js.map +1 -1
  45. package/dist/esm/l10n/nl.json.js +4 -1
  46. package/dist/esm/l10n/nl.json.js.map +1 -1
  47. package/dist/esm/nextcloud-vue.css +20 -2
  48. package/dist/esm/store/plugins/liveUpdates.js +19 -9
  49. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  50. package/dist/esm/store/useObjectStore.js +20 -9
  51. package/dist/esm/store/useObjectStore.js.map +1 -1
  52. package/dist/esm/utils/resolveRowActions.js +130 -0
  53. package/dist/esm/utils/resolveRowActions.js.map +1 -0
  54. package/dist/esm/utils/rowActionItem.js +104 -0
  55. package/dist/esm/utils/rowActionItem.js.map +1 -0
  56. package/dist/esm/utils/timelineEvents.js +44 -1
  57. package/dist/esm/utils/timelineEvents.js.map +1 -1
  58. package/dist/esm/utils/validateManifest.js +59 -3
  59. package/dist/esm/utils/validateManifest.js.map +1 -1
  60. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  61. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  62. package/dist/nextcloud-vue.cjs.js +1431 -802
  63. package/dist/nextcloud-vue.cjs.js.map +2 -2
  64. package/dist/nextcloud-vue.css +20 -2
  65. package/l10n/en.json +4 -1
  66. package/l10n/nl.json +4 -1
  67. package/package.json +1 -1
  68. package/scripts/.jsdoc-baselines.json +2 -2
  69. package/src/components/CnActionsBar/CnActionsBar.md +2 -1
  70. package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
  71. package/src/components/CnAppNav/CnAppNav.vue +2 -2
  72. package/src/components/CnContextMenu/CnContextMenu.vue +40 -29
  73. package/src/components/CnDataTable/CnDataTable.md +1 -1
  74. package/src/components/CnDataTable/CnDataTable.vue +4 -4
  75. package/src/components/CnFormDialog/CnFormDialog.vue +92 -18
  76. package/src/components/CnIndexPage/CnIndexPage.md +23 -1
  77. package/src/components/CnIndexPage/CnIndexPage.vue +108 -52
  78. package/src/components/CnIndexPage/defaultActions.js +6 -7
  79. package/src/components/CnRowActions/CnRowActions.vue +36 -37
  80. package/src/components/CnSetupWizard/CnSetupWizard.vue +13 -13
  81. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +3 -2
  82. package/src/composables/useContextMenu.js +4 -2
  83. package/src/composables/useDependencyCheck.js +43 -0
  84. package/src/composables/useListView.js +43 -1
  85. package/src/composables/useSetupStatus.js +26 -3
  86. package/src/css/actions-bar.css +20 -2
  87. package/src/schemas/app-manifest-v2.schema.json +48 -10
  88. package/src/schemas/app-manifest.schema.json +1 -1
  89. package/src/store/plugins/liveUpdates.js +19 -9
  90. package/src/store/useObjectStore.js +19 -8
  91. package/src/utils/resolveRowActions.js +127 -0
  92. package/src/utils/rowActionItem.js +100 -0
  93. package/src/utils/timelineEvents.js +43 -0
  94. package/src/utils/validateManifest.js +60 -3
  95. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -0,0 +1,104 @@
1
+ import { isLocallyDecidableVisibleWhen, evaluateVisibleWhenLocal } from './visibleWhen.js';
2
+
3
+ /**
4
+ * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
5
+ * SPDX-License-Identifier: EUPL-1.2
6
+ *
7
+ * How a row action renders as a menu entry.
8
+ * CnRowActions and CnContextMenu both use these, so a row's actions menu and its right-click menu agree on which entries show and on each entry's key, testid and event payload.
9
+ */
10
+
11
+
12
+ /**
13
+ * Whether an action is a built-in row action (View, Edit, Copy, Delete).
14
+ *
15
+ * @param {object} action The action.
16
+ * @return {boolean} True for a built-in carrying an id.
17
+ */
18
+ function isBuiltin(action) {
19
+ return !!action && action.builtin === true && typeof action.id === 'string' && action.id !== ''
20
+ }
21
+
22
+ /**
23
+ * Whether an action shows for an item.
24
+ * A locally decidable `visibleWhen` that is false hides it; an endpoint or source condition is not decided here.
25
+ * Then `visible` (a boolean or `(item) => boolean`) applies; without it the action shows.
26
+ *
27
+ * @param {object} action The action.
28
+ * @param {object|string|number|null} item The row or right-clicked item.
29
+ * @return {boolean} Whether the entry renders.
30
+ */
31
+ function isRowActionVisible(action, item) {
32
+ if (action.visibleWhen
33
+ && isLocallyDecidableVisibleWhen(action.visibleWhen)
34
+ && evaluateVisibleWhenLocal(action.visibleWhen, item) === false) {
35
+ return false
36
+ }
37
+ if (action.visible === undefined) {
38
+ return true
39
+ }
40
+ if (typeof action.visible === 'function') {
41
+ return !!action.visible(item)
42
+ }
43
+ return !!action.visible
44
+ }
45
+
46
+ /**
47
+ * Slugify a label for a `data-testid` suffix: lowercase, kebab-case, non-alphanumerics stripped.
48
+ *
49
+ * @param {string} label The label.
50
+ * @return {string} The slug.
51
+ */
52
+ function slugifyActionLabel(label) {
53
+ // One pass collapses every run of other characters to a single '-', so at
54
+ // most one '-' can sit at either end; trimming it by index keeps this
55
+ // linear. `/^-+|-+$/g` here was a ReDoS on long runs of '-' (CodeQL).
56
+ const slug = String(label || '')
57
+ .toLowerCase()
58
+ .replace(/[^a-z0-9]+/g, '-');
59
+ const start = slug.startsWith('-') ? 1 : 0;
60
+ const end = slug.length > start && slug.endsWith('-') ? slug.length - 1 : slug.length;
61
+ return slug.slice(start, end)
62
+ }
63
+
64
+ /**
65
+ * The render key of an entry: `builtin:<id>` for a built-in, the label for an app action.
66
+ * An app action with `id: "edit"` therefore never shares a key with the built-in Edit.
67
+ *
68
+ * @param {object} action The action.
69
+ * @return {string} The key.
70
+ */
71
+ function rowActionKey(action) {
72
+ return isBuiltin(action) ? `builtin:${action.id}` : action.label
73
+ }
74
+
75
+ /**
76
+ * The `data-testid` of an entry: `cn-action-item-<id>` for a built-in, in every locale, and the slug of the label for an app action.
77
+ *
78
+ * @param {object} action The action.
79
+ * @return {string} The testid.
80
+ */
81
+ function rowActionTestId(action) {
82
+ return `cn-action-item-${isBuiltin(action) ? action.id : slugifyActionLabel(action.label)}`
83
+ }
84
+
85
+ /**
86
+ * The `action` event payload for an entry: `action` is the label, `id` is the action's id when it has one, and `builtin: true` marks a built-in.
87
+ *
88
+ * @param {object} action The action.
89
+ * @param {object|string|number|null} row The row or right-clicked item.
90
+ * @return {{action: string, row: (object|string|number|null), id?: string, builtin?: true}} The payload.
91
+ */
92
+ function rowActionPayload(action, row) {
93
+ const payload = { action: action.label, row };
94
+ if (typeof action.id === 'string' && action.id !== '') {
95
+ payload.id = action.id;
96
+ }
97
+ if (isBuiltin(action)) {
98
+ payload.builtin = true;
99
+ }
100
+ return payload
101
+ }
102
+
103
+ export { isRowActionVisible, rowActionKey, rowActionPayload, rowActionTestId, slugifyActionLabel };
104
+ //# sourceMappingURL=rowActionItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rowActionItem.js","sources":["../../../src/utils/rowActionItem.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * How a row action renders as a menu entry.\n * CnRowActions and CnContextMenu both use these, so a row's actions menu and its right-click menu agree on which entries show and on each entry's key, testid and event payload.\n */\n\nimport { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from './visibleWhen.js'\n\n/**\n * Whether an action is a built-in row action (View, Edit, Copy, Delete).\n *\n * @param {object} action The action.\n * @return {boolean} True for a built-in carrying an id.\n */\nfunction isBuiltin(action) {\n\treturn !!action && action.builtin === true && typeof action.id === 'string' && action.id !== ''\n}\n\n/**\n * Whether an action shows for an item.\n * A locally decidable `visibleWhen` that is false hides it; an endpoint or source condition is not decided here.\n * Then `visible` (a boolean or `(item) => boolean`) applies; without it the action shows.\n *\n * @param {object} action The action.\n * @param {object|string|number|null} item The row or right-clicked item.\n * @return {boolean} Whether the entry renders.\n */\nexport function isRowActionVisible(action, item) {\n\tif (action.visibleWhen\n\t\t&& isLocallyDecidableVisibleWhen(action.visibleWhen)\n\t\t&& evaluateVisibleWhenLocal(action.visibleWhen, item) === false) {\n\t\treturn false\n\t}\n\tif (action.visible === undefined) {\n\t\treturn true\n\t}\n\tif (typeof action.visible === 'function') {\n\t\treturn !!action.visible(item)\n\t}\n\treturn !!action.visible\n}\n\n/**\n * Slugify a label for a `data-testid` suffix: lowercase, kebab-case, non-alphanumerics stripped.\n *\n * @param {string} label The label.\n * @return {string} The slug.\n */\nexport function slugifyActionLabel(label) {\n\t// One pass collapses every run of other characters to a single '-', so at\n\t// most one '-' can sit at either end; trimming it by index keeps this\n\t// linear. `/^-+|-+$/g` here was a ReDoS on long runs of '-' (CodeQL).\n\tconst slug = String(label || '')\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\tconst start = slug.startsWith('-') ? 1 : 0\n\tconst end = slug.length > start && slug.endsWith('-') ? slug.length - 1 : slug.length\n\treturn slug.slice(start, end)\n}\n\n/**\n * The render key of an entry: `builtin:<id>` for a built-in, the label for an app action.\n * An app action with `id: \"edit\"` therefore never shares a key with the built-in Edit.\n *\n * @param {object} action The action.\n * @return {string} The key.\n */\nexport function rowActionKey(action) {\n\treturn isBuiltin(action) ? `builtin:${action.id}` : action.label\n}\n\n/**\n * The `data-testid` of an entry: `cn-action-item-<id>` for a built-in, in every locale, and the slug of the label for an app action.\n *\n * @param {object} action The action.\n * @return {string} The testid.\n */\nexport function rowActionTestId(action) {\n\treturn `cn-action-item-${isBuiltin(action) ? action.id : slugifyActionLabel(action.label)}`\n}\n\n/**\n * The `action` event payload for an entry: `action` is the label, `id` is the action's id when it has one, and `builtin: true` marks a built-in.\n *\n * @param {object} action The action.\n * @param {object|string|number|null} row The row or right-clicked item.\n * @return {{action: string, row: (object|string|number|null), id?: string, builtin?: true}} The payload.\n */\nexport function rowActionPayload(action, row) {\n\tconst payload = { action: action.label, row }\n\tif (typeof action.id === 'string' && action.id !== '') {\n\t\tpayload.id = action.id\n\t}\n\tif (isBuiltin(action)) {\n\t\tpayload.builtin = true\n\t}\n\treturn payload\n}\n"],"names":[],"mappings":"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"}
@@ -97,6 +97,49 @@ function fieldEvents(object, fields, now = new Date()) {
97
97
  * @param {Date} [now] The reference moment.
98
98
  * @return {Array<object>} The events.
99
99
  */
100
+ /**
101
+ * The text at a path of a list entry, or '' when the path is unset or empty.
102
+ *
103
+ * @param {object} row The list entry.
104
+ * @param {string|undefined} path The path to read.
105
+ * @return {string} The text.
106
+ */
107
+ function textAt(row, path) {
108
+ if (!path) {
109
+ return ''
110
+ }
111
+ const value = readPath(row, path);
112
+ return value === null || value === undefined ? '' : String(value)
113
+ }
114
+
115
+ /**
116
+ * Events from a list held on the object itself, such as a status history:
117
+ * `[{ field, dateField, labelField?, label?, detailField? }]`. Each entry of the
118
+ * list at `field` becomes one event, dated by `dateField`, labelled by its
119
+ * `labelField` value (else `label`), with `detailField` as the line under it.
120
+ *
121
+ * @param {object} object The object.
122
+ * @param {Array<object>} lists The list configs.
123
+ * @param {Date} now The moment that separates past from upcoming.
124
+ * @return {Array<object>} The events.
125
+ * @spec openspec/changes/timeline-audit-trails-url/specs/timeline-widget/spec.md#requirement-a-list-on-the-object-becomes-dated-events
126
+ */
127
+ function listEvents(object, lists, now = new Date()) {
128
+ return (Array.isArray(lists) ? lists : [])
129
+ .filter((l) => l && typeof l.field === 'string' && typeof l.dateField === 'string')
130
+ .flatMap((l) => {
131
+ const rows = readPath(object, l.field);
132
+ return (Array.isArray(rows) ? rows : []).map((row, i) => makeEvent({
133
+ id: `list:${l.field}:${i}`,
134
+ raw: readPath(row, l.dateField),
135
+ label: textAt(row, l.labelField) || l.label || l.field,
136
+ detail: textAt(row, l.detailField),
137
+ source: 'list',
138
+ }, now))
139
+ })
140
+ .filter(Boolean)
141
+ }
142
+
100
143
  function relatedEvents(rows, config, now = new Date()) {
101
144
  const cfg = config || {};
102
145
  const dateField = cfg.dateField || '@self.created';
@@ -170,5 +213,5 @@ function sortEvents(events, order = 'asc') {
170
213
  .map(({ e }) => e)
171
214
  }
172
215
 
173
- export { auditEvents, fieldEvents, parseMoment, readPath, relatedEvents, sortEvents, timelineEntryEvents };
216
+ export { auditEvents, fieldEvents, listEvents, parseMoment, readPath, relatedEvents, sortEvents, timelineEntryEvents };
174
217
  //# sourceMappingURL=timelineEvents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timelineEvents.js","sources":["../../../src/utils/timelineEvents.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Turn an object's dated facts into timeline events.\n *\n * Pure helpers behind `CnTimelineWidget`. An event is\n * `{ id, at, dateOnly, label, detail, source, upcoming }`, where `at` is a\n * `Date`, `dateOnly` says the source value carried no time of day, and\n * `upcoming` says the moment lies after `now`.\n *\n * @module utils/timelineEvents\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md\n */\n\n/**\n * Read a dotted path off an object (`@self.created`, `payment.clearedAt`).\n *\n * @param {object} obj The object.\n * @param {string} path The dotted path.\n * @return {unknown} The value, or undefined.\n */\nexport function readPath(obj, path) {\n\tif (!obj || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\tif (Object.hasOwn(obj, path)) {\n\t\treturn obj[path]\n\t}\n\treturn path.split('.').reduce((acc, part) => ((acc === null || acc === undefined) ? undefined : acc[part]), obj)\n}\n\n/**\n * Parse a stored date value. A bare `YYYY-MM-DD` is a calendar day and is\n * read as local midnight, not UTC, so it never shows as the day before.\n *\n * @param {unknown} raw The stored value.\n * @return {{at: Date, dateOnly: boolean}|null} The moment, or null when it is not a date.\n */\nexport function parseMoment(raw) {\n\tif (raw === undefined || raw === null || raw === '') {\n\t\treturn null\n\t}\n\tif (typeof raw === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(raw)) {\n\t\tconst [y, m, d] = raw.split('-').map(Number)\n\t\treturn { at: new Date(y, m - 1, d), dateOnly: true }\n\t}\n\tconst at = new Date(raw)\n\treturn Number.isNaN(at.getTime()) ? null : { at, dateOnly: false }\n}\n\n/**\n * Build one event, or null when the value is not a date.\n *\n * @param {object} spec `{ id, raw, label, detail, source }`.\n * @param {Date} now The reference moment for `upcoming`.\n * @return {object|null} The event.\n */\nfunction makeEvent(spec, now) {\n\tconst moment = parseMoment(spec.raw)\n\tif (!moment) {\n\t\treturn null\n\t}\n\treturn {\n\t\tid: spec.id,\n\t\tat: moment.at,\n\t\tdateOnly: moment.dateOnly,\n\t\tlabel: spec.label || '',\n\t\tdetail: spec.detail || '',\n\t\tsource: spec.source,\n\t\tupcoming: moment.at.getTime() > now.getTime(),\n\t}\n}\n\n/**\n * Events from the object's own date properties.\n *\n * @param {object} object The object.\n * @param {Array<{field: string, label?: string}>} fields The configured date fields.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} One event per field that holds a date.\n */\nexport function fieldEvents(object, fields, now = new Date()) {\n\treturn (Array.isArray(fields) ? fields : [])\n\t\t.filter((f) => f && typeof f.field === 'string')\n\t\t.map((f) => makeEvent({ id: `field:${f.field}`, raw: readPath(object, f.field), label: f.label || f.field, source: 'field' }, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from related objects: one per row that holds a date in `dateField`.\n *\n * @param {Array<object>} rows The related objects.\n * @param {{label?: string, dateField?: string, titleField?: string, schema?: string}} config The related-source config.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\nexport function relatedEvents(rows, config, now = new Date()) {\n\tconst cfg = config || {}\n\tconst dateField = cfg.dateField || '@self.created'\n\treturn (Array.isArray(rows) ? rows : [])\n\t\t.map((row, i) => {\n\t\t\tconst self = (row && row['@self']) || {}\n\t\t\tconst id = String(self.id || row.id || i)\n\t\t\tconst title = cfg.titleField ? readPath(row, cfg.titleField) : (row && (row.title || row.name)) || self.name\n\t\t\treturn makeEvent({\n\t\t\t\tid: `related:${cfg.schema || ''}:${id}`,\n\t\t\t\traw: readPath(row, dateField),\n\t\t\t\tlabel: cfg.label || '',\n\t\t\t\tdetail: title === undefined || title === null ? '' : String(title),\n\t\t\t\tsource: 'related',\n\t\t\t}, now)\n\t\t})\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from audit-trail entries.\n *\n * @param {Array<object>} entries The audit-trail rows.\n * @param {(action: string, actor: string) => string} describe Builds the label from action and actor.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\nexport function auditEvents(entries, describe, now = new Date()) {\n\treturn (Array.isArray(entries) ? entries : [])\n\t\t.map((e, i) => makeEvent({\n\t\t\tid: `audit:${e && (e.id || e.uuid) ? (e.id || e.uuid) : i}`,\n\t\t\traw: e && (e.created || e.creationDateTime || e.timestamp),\n\t\t\tlabel: describe(String((e && (e.action || e.event)) || ''), String((e && (e.actorDisplayName || e.userName || e.user || e.actor)) || '')),\n\t\t\tsource: 'audit',\n\t\t}, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from OpenRegister's timeline (notes, calls, messages).\n *\n * @param {Array<object>} entries The timeline rows.\n * @param {string} noteLabel The label for an entry without a kind.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\nexport function timelineEntryEvents(entries, noteLabel, now = new Date()) {\n\treturn (Array.isArray(entries) ? entries : [])\n\t\t.map((e, i) => makeEvent({\n\t\t\tid: `timeline:${e && e.id ? e.id : i}`,\n\t\t\traw: e && e.created,\n\t\t\tlabel: (e && (e.kindTitle || e.kind)) || noteLabel,\n\t\t\tdetail: [e && e.author, e && e.message].filter(Boolean).join(': '),\n\t\t\tsource: 'timeline',\n\t\t}, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Sort events by time; ties keep their input order.\n *\n * @param {Array<object>} events The events.\n * @param {'asc'|'desc'} [order] Oldest first (default) or newest first.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortEvents(events, order = 'asc') {\n\tconst dir = order === 'desc' ? -1 : 1\n\treturn (events || [])\n\t\t.map((e, i) => ({ e, i }))\n\t\t.sort((a, b) => ((a.e.at.getTime() - b.e.at.getTime()) * dir) || (a.i - b.i))\n\t\t.map(({ e }) => e)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"timelineEvents.js","sources":["../../../src/utils/timelineEvents.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Turn an object's dated facts into timeline events.\n *\n * Pure helpers behind `CnTimelineWidget`. An event is\n * `{ id, at, dateOnly, label, detail, source, upcoming }`, where `at` is a\n * `Date`, `dateOnly` says the source value carried no time of day, and\n * `upcoming` says the moment lies after `now`.\n *\n * @module utils/timelineEvents\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md\n */\n\n/**\n * Read a dotted path off an object (`@self.created`, `payment.clearedAt`).\n *\n * @param {object} obj The object.\n * @param {string} path The dotted path.\n * @return {unknown} The value, or undefined.\n */\nexport function readPath(obj, path) {\n\tif (!obj || typeof path !== 'string' || path === '') {\n\t\treturn undefined\n\t}\n\tif (Object.hasOwn(obj, path)) {\n\t\treturn obj[path]\n\t}\n\treturn path.split('.').reduce((acc, part) => ((acc === null || acc === undefined) ? undefined : acc[part]), obj)\n}\n\n/**\n * Parse a stored date value. A bare `YYYY-MM-DD` is a calendar day and is\n * read as local midnight, not UTC, so it never shows as the day before.\n *\n * @param {unknown} raw The stored value.\n * @return {{at: Date, dateOnly: boolean}|null} The moment, or null when it is not a date.\n */\nexport function parseMoment(raw) {\n\tif (raw === undefined || raw === null || raw === '') {\n\t\treturn null\n\t}\n\tif (typeof raw === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(raw)) {\n\t\tconst [y, m, d] = raw.split('-').map(Number)\n\t\treturn { at: new Date(y, m - 1, d), dateOnly: true }\n\t}\n\tconst at = new Date(raw)\n\treturn Number.isNaN(at.getTime()) ? null : { at, dateOnly: false }\n}\n\n/**\n * Build one event, or null when the value is not a date.\n *\n * @param {object} spec `{ id, raw, label, detail, source }`.\n * @param {Date} now The reference moment for `upcoming`.\n * @return {object|null} The event.\n */\nfunction makeEvent(spec, now) {\n\tconst moment = parseMoment(spec.raw)\n\tif (!moment) {\n\t\treturn null\n\t}\n\treturn {\n\t\tid: spec.id,\n\t\tat: moment.at,\n\t\tdateOnly: moment.dateOnly,\n\t\tlabel: spec.label || '',\n\t\tdetail: spec.detail || '',\n\t\tsource: spec.source,\n\t\tupcoming: moment.at.getTime() > now.getTime(),\n\t}\n}\n\n/**\n * Events from the object's own date properties.\n *\n * @param {object} object The object.\n * @param {Array<{field: string, label?: string}>} fields The configured date fields.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} One event per field that holds a date.\n */\nexport function fieldEvents(object, fields, now = new Date()) {\n\treturn (Array.isArray(fields) ? fields : [])\n\t\t.filter((f) => f && typeof f.field === 'string')\n\t\t.map((f) => makeEvent({ id: `field:${f.field}`, raw: readPath(object, f.field), label: f.label || f.field, source: 'field' }, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from related objects: one per row that holds a date in `dateField`.\n *\n * @param {Array<object>} rows The related objects.\n * @param {{label?: string, dateField?: string, titleField?: string, schema?: string}} config The related-source config.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\n/**\n * The text at a path of a list entry, or '' when the path is unset or empty.\n *\n * @param {object} row The list entry.\n * @param {string|undefined} path The path to read.\n * @return {string} The text.\n */\nfunction textAt(row, path) {\n\tif (!path) {\n\t\treturn ''\n\t}\n\tconst value = readPath(row, path)\n\treturn value === null || value === undefined ? '' : String(value)\n}\n\n/**\n * Events from a list held on the object itself, such as a status history:\n * `[{ field, dateField, labelField?, label?, detailField? }]`. Each entry of the\n * list at `field` becomes one event, dated by `dateField`, labelled by its\n * `labelField` value (else `label`), with `detailField` as the line under it.\n *\n * @param {object} object The object.\n * @param {Array<object>} lists The list configs.\n * @param {Date} now The moment that separates past from upcoming.\n * @return {Array<object>} The events.\n * @spec openspec/changes/timeline-audit-trails-url/specs/timeline-widget/spec.md#requirement-a-list-on-the-object-becomes-dated-events\n */\nexport function listEvents(object, lists, now = new Date()) {\n\treturn (Array.isArray(lists) ? lists : [])\n\t\t.filter((l) => l && typeof l.field === 'string' && typeof l.dateField === 'string')\n\t\t.flatMap((l) => {\n\t\t\tconst rows = readPath(object, l.field)\n\t\t\treturn (Array.isArray(rows) ? rows : []).map((row, i) => makeEvent({\n\t\t\t\tid: `list:${l.field}:${i}`,\n\t\t\t\traw: readPath(row, l.dateField),\n\t\t\t\tlabel: textAt(row, l.labelField) || l.label || l.field,\n\t\t\t\tdetail: textAt(row, l.detailField),\n\t\t\t\tsource: 'list',\n\t\t\t}, now))\n\t\t})\n\t\t.filter(Boolean)\n}\n\nexport function relatedEvents(rows, config, now = new Date()) {\n\tconst cfg = config || {}\n\tconst dateField = cfg.dateField || '@self.created'\n\treturn (Array.isArray(rows) ? rows : [])\n\t\t.map((row, i) => {\n\t\t\tconst self = (row && row['@self']) || {}\n\t\t\tconst id = String(self.id || row.id || i)\n\t\t\tconst title = cfg.titleField ? readPath(row, cfg.titleField) : (row && (row.title || row.name)) || self.name\n\t\t\treturn makeEvent({\n\t\t\t\tid: `related:${cfg.schema || ''}:${id}`,\n\t\t\t\traw: readPath(row, dateField),\n\t\t\t\tlabel: cfg.label || '',\n\t\t\t\tdetail: title === undefined || title === null ? '' : String(title),\n\t\t\t\tsource: 'related',\n\t\t\t}, now)\n\t\t})\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from audit-trail entries.\n *\n * @param {Array<object>} entries The audit-trail rows.\n * @param {(action: string, actor: string) => string} describe Builds the label from action and actor.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\nexport function auditEvents(entries, describe, now = new Date()) {\n\treturn (Array.isArray(entries) ? entries : [])\n\t\t.map((e, i) => makeEvent({\n\t\t\tid: `audit:${e && (e.id || e.uuid) ? (e.id || e.uuid) : i}`,\n\t\t\traw: e && (e.created || e.creationDateTime || e.timestamp),\n\t\t\tlabel: describe(String((e && (e.action || e.event)) || ''), String((e && (e.actorDisplayName || e.userName || e.user || e.actor)) || '')),\n\t\t\tsource: 'audit',\n\t\t}, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Events from OpenRegister's timeline (notes, calls, messages).\n *\n * @param {Array<object>} entries The timeline rows.\n * @param {string} noteLabel The label for an entry without a kind.\n * @param {Date} [now] The reference moment.\n * @return {Array<object>} The events.\n */\nexport function timelineEntryEvents(entries, noteLabel, now = new Date()) {\n\treturn (Array.isArray(entries) ? entries : [])\n\t\t.map((e, i) => makeEvent({\n\t\t\tid: `timeline:${e && e.id ? e.id : i}`,\n\t\t\traw: e && e.created,\n\t\t\tlabel: (e && (e.kindTitle || e.kind)) || noteLabel,\n\t\t\tdetail: [e && e.author, e && e.message].filter(Boolean).join(': '),\n\t\t\tsource: 'timeline',\n\t\t}, now))\n\t\t.filter(Boolean)\n}\n\n/**\n * Sort events by time; ties keep their input order.\n *\n * @param {Array<object>} events The events.\n * @param {'asc'|'desc'} [order] Oldest first (default) or newest first.\n * @return {Array<object>} A new, sorted array.\n */\nexport function sortEvents(events, order = 'asc') {\n\tconst dir = order === 'desc' ? -1 : 1\n\treturn (events || [])\n\t\t.map((e, i) => ({ e, i }))\n\t\t.sort((a, b) => ((a.e.at.getTime() - b.e.at.getTime()) * dir) || (a.i - b.i))\n\t\t.map(({ e }) => e)\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { BUILT_IN_FILE_COLUMNS } from '../components/CnFilesBrowser/filesBrowserColumns.js';
2
2
  import { LIBRARY_WIDGET_KEYS } from './libraryWidgetKeys.js';
3
+ import { resolveRowActions } from './resolveRowActions.js';
3
4
  import { resolveSlotColumns } from './resolveSlotColumns.js';
4
5
  import _compiledValidateV2 from './validateManifestV2.compiled.js';
5
6
 
@@ -111,9 +112,14 @@ const CHART_VALUE_AXIS_BASELINES = ['auto', 'zero', 'fit'];
111
112
  * page's own `config.columns` does not declare (the page declares which
112
113
  * columns it has; a scope only chooses among them)
113
114
  *
115
+ * It also returns non-fatal `warnings`, which never affect `valid` (index pages with an `entitySource` are skipped):
116
+ * - A `"builtin:<id>"` placeholder whose built-in the manifest's toggles turn off
117
+ * - The built-in Delete enabled but not the last entry of the rendered row action order
118
+ *
114
119
  * @spec openspec/changes/manifest-v2-schema/specs/manifest-v2-schema/spec.md
120
+ * @spec openspec/changes/row-action-builtin-placement/specs/index-page/spec.md
115
121
  * @param {object} manifest The v2 manifest object to validate.
116
- * @return {{ valid: boolean, errors: string[] }}
122
+ * @return {{ valid: boolean, errors: string[], warnings: string[] }}
117
123
  */
118
124
  function validateManifestV2(manifest) {
119
125
  // Clone so Ajv useDefaults mutations don't affect the caller's copy.
@@ -780,7 +786,57 @@ function validateManifestV2(manifest) {
780
786
  });
781
787
  }
782
788
 
783
- return { valid: errors.length === 0, errors }
789
+ // --- Post-schema warnings (never affect `valid`) ---
790
+ const warnings = [];
791
+
792
+ // W1. Row action placement on an index page: a placeholder whose built-in the manifest turns off, and the built-in Delete placed before other row actions.
793
+ // An entitySource page is skipped: its built-in defaults depend on the source at runtime.
794
+ if (Array.isArray(clone.pages)) {
795
+ clone.pages.forEach((page, pIndex) => {
796
+ const config = page && page.type === 'index' && isPlainObject(page.config) ? page.config : null;
797
+ if (!config || !Array.isArray(config.actions) || typeof config.entitySource === 'string') {
798
+ return
799
+ }
800
+ const resolved = resolveRowActions(config.actions, indexPageBuiltinStubs(config));
801
+ for (const warning of resolved.warnings) {
802
+ if (warning.code === 'disabled-placeholder') {
803
+ warnings.push(`pages[${pIndex}]/config/actions: ${warning.message}`);
804
+ }
805
+ }
806
+ if (resolved.deleteNotLast) {
807
+ warnings.push(`pages[${pIndex}]/config/actions: "builtin:delete" is not the last row action in the rendered order (enabled built-ins the array does not place are appended after it); Delete is normally last`);
808
+ }
809
+ });
810
+ }
811
+
812
+ return { valid: errors.length === 0, errors, warnings }
813
+ }
814
+
815
+ /**
816
+ * The built-in row actions an index page enables, as `{ id, builtin, label }` stubs in default order.
817
+ * They are read from its config the way CnPageRenderer derives the show*Action props: an explicit `config.show*Action` wins over `config.actionToggles`, and `config.readOnly: true` turns Edit, Copy and Delete off by default.
818
+ * A non-boolean value counts as the default.
819
+ *
820
+ * @param {object} config The page's config.
821
+ * @return {Array<{id: string, builtin: true, label: string}>} The enabled built-ins.
822
+ */
823
+ function indexPageBuiltinStubs(config) {
824
+ const toggles = isPlainObject(config.actionToggles) ? config.actionToggles : {};
825
+ const flag = (key, readOnlyOff) => {
826
+ if (typeof config[key] === 'boolean') {
827
+ return config[key]
828
+ }
829
+ if (typeof toggles[key] === 'boolean') {
830
+ return toggles[key]
831
+ }
832
+ return !(readOnlyOff && config.readOnly === true)
833
+ };
834
+ return [
835
+ ['view', flag('showViewAction', false)],
836
+ ['edit', flag('showEditAction', true)],
837
+ ['copy', flag('showCopyAction', true)],
838
+ ['delete', flag('showDeleteAction', true)],
839
+ ].filter(([, on]) => on).map(([id]) => ({ id, builtin: true, label: id }))
784
840
  }
785
841
 
786
842
  /**
@@ -844,7 +900,7 @@ function isSentinel(value) {
844
900
  * `pageTypes` registry is known up-front. When omitted, any
845
901
  * non-empty string is accepted; the runtime renderer logs a warning
846
902
  * for unknown types.
847
- * @return {{ valid: boolean, errors: string[] }}
903
+ * @return {{ valid: boolean, errors: string[], warnings?: string[] }} A v2 manifest also gets `warnings`, which never affect `valid`.
848
904
  */
849
905
  function validateManifest(manifest, options = {}) {
850
906
  // Dispatch to v2 validator when the manifest declares a v2 $schema.