@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,100 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * How a row action renders as a menu entry.
6
+ * 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.
7
+ */
8
+
9
+ import { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from './visibleWhen.js'
10
+
11
+ /**
12
+ * Whether an action is a built-in row action (View, Edit, Copy, Delete).
13
+ *
14
+ * @param {object} action The action.
15
+ * @return {boolean} True for a built-in carrying an id.
16
+ */
17
+ function isBuiltin(action) {
18
+ return !!action && action.builtin === true && typeof action.id === 'string' && action.id !== ''
19
+ }
20
+
21
+ /**
22
+ * Whether an action shows for an item.
23
+ * A locally decidable `visibleWhen` that is false hides it; an endpoint or source condition is not decided here.
24
+ * Then `visible` (a boolean or `(item) => boolean`) applies; without it the action shows.
25
+ *
26
+ * @param {object} action The action.
27
+ * @param {object|string|number|null} item The row or right-clicked item.
28
+ * @return {boolean} Whether the entry renders.
29
+ */
30
+ export function isRowActionVisible(action, item) {
31
+ if (action.visibleWhen
32
+ && isLocallyDecidableVisibleWhen(action.visibleWhen)
33
+ && evaluateVisibleWhenLocal(action.visibleWhen, item) === false) {
34
+ return false
35
+ }
36
+ if (action.visible === undefined) {
37
+ return true
38
+ }
39
+ if (typeof action.visible === 'function') {
40
+ return !!action.visible(item)
41
+ }
42
+ return !!action.visible
43
+ }
44
+
45
+ /**
46
+ * Slugify a label for a `data-testid` suffix: lowercase, kebab-case, non-alphanumerics stripped.
47
+ *
48
+ * @param {string} label The label.
49
+ * @return {string} The slug.
50
+ */
51
+ export function slugifyActionLabel(label) {
52
+ // One pass collapses every run of other characters to a single '-', so at
53
+ // most one '-' can sit at either end; trimming it by index keeps this
54
+ // linear. `/^-+|-+$/g` here was a ReDoS on long runs of '-' (CodeQL).
55
+ const slug = String(label || '')
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9]+/g, '-')
58
+ const start = slug.startsWith('-') ? 1 : 0
59
+ const end = slug.length > start && slug.endsWith('-') ? slug.length - 1 : slug.length
60
+ return slug.slice(start, end)
61
+ }
62
+
63
+ /**
64
+ * The render key of an entry: `builtin:<id>` for a built-in, the label for an app action.
65
+ * An app action with `id: "edit"` therefore never shares a key with the built-in Edit.
66
+ *
67
+ * @param {object} action The action.
68
+ * @return {string} The key.
69
+ */
70
+ export function rowActionKey(action) {
71
+ return isBuiltin(action) ? `builtin:${action.id}` : action.label
72
+ }
73
+
74
+ /**
75
+ * 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.
76
+ *
77
+ * @param {object} action The action.
78
+ * @return {string} The testid.
79
+ */
80
+ export function rowActionTestId(action) {
81
+ return `cn-action-item-${isBuiltin(action) ? action.id : slugifyActionLabel(action.label)}`
82
+ }
83
+
84
+ /**
85
+ * 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.
86
+ *
87
+ * @param {object} action The action.
88
+ * @param {object|string|number|null} row The row or right-clicked item.
89
+ * @return {{action: string, row: (object|string|number|null), id?: string, builtin?: true}} The payload.
90
+ */
91
+ export function rowActionPayload(action, row) {
92
+ const payload = { action: action.label, row }
93
+ if (typeof action.id === 'string' && action.id !== '') {
94
+ payload.id = action.id
95
+ }
96
+ if (isBuiltin(action)) {
97
+ payload.builtin = true
98
+ }
99
+ return payload
100
+ }
@@ -97,6 +97,49 @@ export 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
+ export 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
  export function relatedEvents(rows, config, now = new Date()) {
101
144
  const cfg = config || {}
102
145
  const dateField = cfg.dateField || '@self.created'
@@ -6,6 +6,8 @@
6
6
  // names exist.
7
7
  import { BUILT_IN_FILE_COLUMNS } from '../components/CnFilesBrowser/filesBrowserColumns.js'
8
8
  import { LIBRARY_WIDGET_KEYS } from './libraryWidgetKeys.js'
9
+ // The same resolver CnIndexPage orders row actions with, so the placement warnings measure the order that actually renders.
10
+ import { resolveRowActions } from './resolveRowActions.js'
9
11
  // Shared slot→columns resolution so the validator's grid bound matches the
10
12
  // renderer (CnWidgetGrid) exactly. A mismatch would let a manifest pass
11
13
  // validation yet clip at render time.
@@ -120,9 +122,14 @@ const CHART_VALUE_AXIS_BASELINES = ['auto', 'zero', 'fit']
120
122
  * page's own `config.columns` does not declare (the page declares which
121
123
  * columns it has; a scope only chooses among them)
122
124
  *
125
+ * It also returns non-fatal `warnings`, which never affect `valid` (index pages with an `entitySource` are skipped):
126
+ * - A `"builtin:<id>"` placeholder whose built-in the manifest's toggles turn off
127
+ * - The built-in Delete enabled but not the last entry of the rendered row action order
128
+ *
123
129
  * @spec openspec/changes/manifest-v2-schema/specs/manifest-v2-schema/spec.md
130
+ * @spec openspec/changes/row-action-builtin-placement/specs/index-page/spec.md
124
131
  * @param {object} manifest The v2 manifest object to validate.
125
- * @return {{ valid: boolean, errors: string[] }}
132
+ * @return {{ valid: boolean, errors: string[], warnings: string[] }}
126
133
  */
127
134
  export function validateManifestV2(manifest) {
128
135
  // Clone so Ajv useDefaults mutations don't affect the caller's copy.
@@ -789,7 +796,57 @@ export function validateManifestV2(manifest) {
789
796
  })
790
797
  }
791
798
 
792
- return { valid: errors.length === 0, errors }
799
+ // --- Post-schema warnings (never affect `valid`) ---
800
+ const warnings = []
801
+
802
+ // 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.
803
+ // An entitySource page is skipped: its built-in defaults depend on the source at runtime.
804
+ if (Array.isArray(clone.pages)) {
805
+ clone.pages.forEach((page, pIndex) => {
806
+ const config = page && page.type === 'index' && isPlainObject(page.config) ? page.config : null
807
+ if (!config || !Array.isArray(config.actions) || typeof config.entitySource === 'string') {
808
+ return
809
+ }
810
+ const resolved = resolveRowActions(config.actions, indexPageBuiltinStubs(config))
811
+ for (const warning of resolved.warnings) {
812
+ if (warning.code === 'disabled-placeholder') {
813
+ warnings.push(`pages[${pIndex}]/config/actions: ${warning.message}`)
814
+ }
815
+ }
816
+ if (resolved.deleteNotLast) {
817
+ 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`)
818
+ }
819
+ })
820
+ }
821
+
822
+ return { valid: errors.length === 0, errors, warnings }
823
+ }
824
+
825
+ /**
826
+ * The built-in row actions an index page enables, as `{ id, builtin, label }` stubs in default order.
827
+ * 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.
828
+ * A non-boolean value counts as the default.
829
+ *
830
+ * @param {object} config The page's config.
831
+ * @return {Array<{id: string, builtin: true, label: string}>} The enabled built-ins.
832
+ */
833
+ function indexPageBuiltinStubs(config) {
834
+ const toggles = isPlainObject(config.actionToggles) ? config.actionToggles : {}
835
+ const flag = (key, readOnlyOff) => {
836
+ if (typeof config[key] === 'boolean') {
837
+ return config[key]
838
+ }
839
+ if (typeof toggles[key] === 'boolean') {
840
+ return toggles[key]
841
+ }
842
+ return !(readOnlyOff && config.readOnly === true)
843
+ }
844
+ return [
845
+ ['view', flag('showViewAction', false)],
846
+ ['edit', flag('showEditAction', true)],
847
+ ['copy', flag('showCopyAction', true)],
848
+ ['delete', flag('showDeleteAction', true)],
849
+ ].filter(([, on]) => on).map(([id]) => ({ id, builtin: true, label: id }))
793
850
  }
794
851
 
795
852
  /**
@@ -853,7 +910,7 @@ function isSentinel(value) {
853
910
  * `pageTypes` registry is known up-front. When omitted, any
854
911
  * non-empty string is accepted; the runtime renderer logs a warning
855
912
  * for unknown types.
856
- * @return {{ valid: boolean, errors: string[] }}
913
+ * @return {{ valid: boolean, errors: string[], warnings?: string[] }} A v2 manifest also gets `warnings`, which never affect `valid`.
857
914
  */
858
915
  export function validateManifest(manifest, options = {}) {
859
916
  // Dispatch to v2 validator when the manifest declares a v2 $schema.