@conduction/nextcloud-vue 2.65.0 → 2.67.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 (103) 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/CnDashboardPage/CnDashboardPage.vue2.js +99 -2
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +438 -383
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
  13. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
  15. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +78 -21
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +129 -96
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  23. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
  24. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
  25. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
  26. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  28. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +63 -7
  29. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  30. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +2 -1
  31. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +335 -313
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
  37. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  38. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
  39. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  40. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +5 -11
  41. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  42. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  43. package/dist/esm/composables/useDependencyCheck.js +44 -1
  44. package/dist/esm/composables/useDependencyCheck.js.map +1 -1
  45. package/dist/esm/composables/useListView.js +43 -1
  46. package/dist/esm/composables/useListView.js.map +1 -1
  47. package/dist/esm/composables/useSetupStatus.js +26 -3
  48. package/dist/esm/composables/useSetupStatus.js.map +1 -1
  49. package/dist/esm/l10n/en.json.js +7 -1
  50. package/dist/esm/l10n/en.json.js.map +1 -1
  51. package/dist/esm/l10n/nl.json.js +7 -1
  52. package/dist/esm/l10n/nl.json.js.map +1 -1
  53. package/dist/esm/mixins/pageViews.js +400 -0
  54. package/dist/esm/mixins/pageViews.js.map +1 -0
  55. package/dist/esm/nextcloud-vue.css +38 -2
  56. package/dist/esm/store/plugins/liveUpdates.js +19 -9
  57. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  58. package/dist/esm/store/useObjectStore.js +20 -9
  59. package/dist/esm/store/useObjectStore.js.map +1 -1
  60. package/dist/esm/utils/actionsDispatcher.js +11 -2
  61. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  62. package/dist/esm/utils/columnFilters.js +27 -3
  63. package/dist/esm/utils/columnFilters.js.map +1 -1
  64. package/dist/esm/utils/schema.js +40 -2
  65. package/dist/esm/utils/schema.js.map +1 -1
  66. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  67. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  68. package/dist/nextcloud-vue.cjs.js +2377 -1247
  69. package/dist/nextcloud-vue.cjs.js.map +2 -2
  70. package/dist/nextcloud-vue.css +38 -2
  71. package/l10n/en.json +7 -1
  72. package/l10n/nl.json +7 -1
  73. package/package.json +1 -1
  74. package/src/components/CnActionsBar/CnActionsBar.md +2 -1
  75. package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
  76. package/src/components/CnAppNav/CnAppNav.vue +2 -2
  77. package/src/components/CnDashboardPage/CnDashboardPage.vue +165 -8
  78. package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
  79. package/src/components/CnDataTable/CnDataTable.md +1 -1
  80. package/src/components/CnDataTable/CnDataTable.vue +4 -4
  81. package/src/components/CnDetailPage/CnDetailPage.vue +94 -5
  82. package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
  83. package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
  84. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +67 -8
  85. package/src/components/CnIndexPage/CnIndexPage.md +2 -1
  86. package/src/components/CnIndexPage/CnIndexPage.vue +47 -1
  87. package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
  88. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
  89. package/src/components/CnSetupWizard/CnSetupWizard.vue +5 -11
  90. package/src/composables/useDependencyCheck.js +43 -0
  91. package/src/composables/useListView.js +43 -1
  92. package/src/composables/useSetupStatus.js +26 -3
  93. package/src/css/actions-bar.css +20 -2
  94. package/src/css/detail-page.css +5 -0
  95. package/src/mixins/pageViews.js +396 -0
  96. package/src/schemas/app-manifest-v2.schema.json +31 -3
  97. package/src/schemas/app-manifest.schema.json +1 -1
  98. package/src/store/plugins/liveUpdates.js +19 -9
  99. package/src/store/useObjectStore.js +19 -8
  100. package/src/utils/actionsDispatcher.js +11 -2
  101. package/src/utils/columnFilters.js +29 -2
  102. package/src/utils/schema.js +40 -2
  103. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -199,13 +199,12 @@
199
199
 
200
200
  <script>
201
201
  import { getCurrentUser } from '@nextcloud/auth'
202
- import { loadState } from '@nextcloud/initial-state'
203
202
  import { translate as t } from '@nextcloud/l10n'
204
203
  import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'
205
204
  import CnChoiceCards from '../CnChoiceCards/CnChoiceCards.vue'
206
205
  import CnLeafDependencySettings from '../CnLeafDependencySettings/CnLeafDependencySettings.vue'
207
206
  import CnWizardDialog from '../CnWizardDialog/CnWizardDialog.vue'
208
- import { checkDependencies, isDependencyResolved } from '../../composables/useDependencyCheck.js'
207
+ import { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses } from '../../composables/useDependencyCheck.js'
209
208
  import { useSetupStatus } from '../../composables/useSetupStatus.js'
210
209
  import { fieldsFromSchema } from '../../utils/schema.js'
211
210
 
@@ -474,16 +473,15 @@ export default {
474
473
  *
475
474
  * @return {Array<{ step: object, missing: Array<string> }>}
476
475
  * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
476
+ * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
477
477
  */
478
478
  skippedSteps() {
479
479
  return this.declaredSteps
480
480
  .filter((s) => Array.isArray(s.requires) && s.requires.length > 0)
481
481
  .map((step) => {
482
482
  const known = Object.fromEntries(this.dependencyRows.map((d) => [d.id, d]))
483
- const missing = checkDependencies(step.requires, this.serverAppStatuses)
484
- .map((row) => known[row.id] || row)
485
- .filter((row) => !isDependencyResolved(row))
486
- .map((row) => row.name || row.id)
483
+ const missing = missingRequiredApps(step.requires, this.serverAppStatuses)
484
+ .map((row) => (known[row.id] || row).name || row.id)
487
485
  return { step, missing }
488
486
  })
489
487
  .filter((entry) => entry.missing.length > 0)
@@ -523,11 +521,7 @@ export default {
523
521
  * @return {object}
524
522
  */
525
523
  serverAppStatuses() {
526
- try {
527
- return loadState(this.appId, 'dependency_statuses', {}) || {}
528
- } catch {
529
- return {}
530
- }
524
+ return readServerAppStatuses(this.appId)
531
525
  },
532
526
 
533
527
  /**
@@ -3,6 +3,7 @@
3
3
  * SPDX-License-Identifier: EUPL-1.2
4
4
  */
5
5
 
6
+ import { loadState } from '@nextcloud/initial-state'
6
7
  import { useAppStatus } from './useAppStatus.js'
7
8
 
8
9
  /**
@@ -59,3 +60,45 @@ export function checkDependencies(entries, serverStatuses = {}) {
59
60
  export function isDependencyResolved(row) {
60
61
  return !!row && row.installed === true && row.enabled === true
61
62
  }
63
+
64
+ /**
65
+ * Read the `dependency_statuses` initial state an app may inject from PHP.
66
+ *
67
+ * Keyed by app id: `{ installed, enabled }`. Returns `{}` when the app injects
68
+ * none or the state cannot be read, so callers fall back to `useAppStatus`.
69
+ *
70
+ * @param {string} appId The app whose initial state to read.
71
+ * @return {object} Server-reported statuses keyed by app id.
72
+ *
73
+ * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
74
+ */
75
+ export function readServerAppStatuses(appId) {
76
+ try {
77
+ const statuses = loadState(appId, 'dependency_statuses', {})
78
+ return (statuses && typeof statuses === 'object') ? statuses : {}
79
+ } catch {
80
+ return {}
81
+ }
82
+ }
83
+
84
+ /**
85
+ * The apps in a setup step's `requires` list that are not installed and
86
+ * enabled.
87
+ *
88
+ * This is the one resolution every setup surface uses: `CnSetupWizard` skips a
89
+ * step when it is non-empty, and `useSetupStatus` then counts that step as not
90
+ * applicable rather than unmet. Keeping both on one function is what stops the
91
+ * wizard and the status from disagreeing about the same step.
92
+ *
93
+ * @param {Array<string>} requires The step's `requires` app ids.
94
+ * @param {object} [serverStatuses] Server-reported statuses keyed by app id.
95
+ * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} The missing apps.
96
+ *
97
+ * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
98
+ */
99
+ export function missingRequiredApps(requires, serverStatuses = {}) {
100
+ if (!Array.isArray(requires) || requires.length === 0) {
101
+ return []
102
+ }
103
+ return checkDependencies(requires, serverStatuses).filter((row) => !isDependencyResolved(row))
104
+ }
@@ -94,6 +94,13 @@ export function useListView(objectTypeOrOptions, options) {
94
94
  // field (a folder pane grouping by it, say) reads them here rather than
95
95
  // deriving them from `objects`, which is only the current page.
96
96
  const facets = computed(() => objectStore.facets[objectType] || {})
97
+ // Outcome of the latest `refresh()`: the store's error for it, or null once
98
+ // it succeeded or a later fetch stored new rows. The store keeps the previous
99
+ // rows on a failed fetch, so this is how a consumer tells stale rows from
100
+ // current ones.
101
+ const error = ref(null)
102
+ let refreshSeq = 0
103
+ let pendingRefreshes = 0
97
104
 
98
105
  let searchTimeout = null
99
106
 
@@ -178,14 +185,48 @@ export function useListView(objectTypeOrOptions, options) {
178
185
 
179
186
  /**
180
187
  * Fetch the collection using current state params and update sidebar facet data.
188
+ * Only the most recently started refresh sets `error`, so an older response
189
+ * settling late cannot override a newer one's outcome.
181
190
  *
182
191
  * @param {number} [page] Page to fetch
183
192
  * @return {Promise<void>}
184
193
  */
185
194
  async function refresh(page = 1) {
186
- await objectStore.fetchCollection(objectType, buildParams(page))
195
+ const seq = ++refreshSeq
196
+ // This call's own result. `errors[objectType]` is shared by every call
197
+ // for the type and is not cleared on success, so it cannot say which
198
+ // request failed; it is only the fallback for a store that ignores
199
+ // `options.outcome`.
200
+ const outcome = {}
201
+ pendingRefreshes++
202
+ try {
203
+ await objectStore.fetchCollection(objectType, buildParams(page), { outcome })
204
+ } catch (e) {
205
+ if (seq === refreshSeq) {
206
+ error.value = e
207
+ }
208
+ throw e
209
+ } finally {
210
+ pendingRefreshes--
211
+ }
212
+ if (seq === refreshSeq) {
213
+ error.value = ('error' in outcome) ? outcome.error : (objectStore.errors?.[objectType] || null)
214
+ }
187
215
  }
188
216
 
217
+ // A fetch outside `refresh()` (e.g. the live-updates refetch) that stores
218
+ // new rows recovers the list from an earlier failure. Rows written while
219
+ // a refresh is pending are left to that refresh, which decides `error`.
220
+ watch(
221
+ () => objectStore.collections[objectType],
222
+ () => {
223
+ if (pendingRefreshes === 0) {
224
+ error.value = null
225
+ }
226
+ },
227
+ { flush: 'sync' },
228
+ )
229
+
189
230
  // ── Event handlers ───────────────────────────────────────────────────
190
231
 
191
232
  /**
@@ -323,6 +364,7 @@ export function useListView(objectTypeOrOptions, options) {
323
364
  loading,
324
365
  pagination,
325
366
  facets,
367
+ error,
326
368
  // Local state
327
369
  searchTerm,
328
370
  sortKey,
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { computed, ref } from 'vue'
7
+ import { missingRequiredApps, readServerAppStatuses } from './useDependencyCheck.js'
7
8
 
8
9
  /**
9
10
  * Per-`appId` cache of setup-status results. The Vue refs are stored so all
@@ -27,6 +28,14 @@ const cache = new Map()
27
28
  * every non-admin user of an app with a `setup` block met a wizard they had no
28
29
  * permission to complete instead of the app itself.
29
30
  *
31
+ * A step that declares `requires: [appId, ...]` is **not applicable** while any
32
+ * of those apps is not installed and enabled: it is neither met nor unmet, so
33
+ * it never lands in `requiredUnmet` or `optionalUnmet`. `CnSetupWizard` skips
34
+ * such a step, and counting it as unmet kept the wizard reopening (optional
35
+ * step) or gating the app (required step) for a step it would never show. App
36
+ * status is resolved exactly as the wizard does it: the `dependency_statuses`
37
+ * initial state first, then `useAppStatus`.
38
+ *
30
39
  * @param {string} appId Nextcloud app id (e.g. `"procest"`).
31
40
  * @param {object} manifest The app manifest (reads `manifest.setup`).
32
41
  * @return {{
@@ -35,6 +44,7 @@ const cache = new Map()
35
44
  * requiredUnmet: import('vue').ComputedRef<Array<object>>,
36
45
  * optionalUnmet: import('vue').ComputedRef<Array<object>>,
37
46
  * optionalUnmetReported: import('vue').ComputedRef<Array<object>>,
47
+ * notApplicable: import('vue').ComputedRef<Array<object>>,
38
48
  * completed: import('vue').ComputedRef<boolean>,
39
49
  * enabled: boolean,
40
50
  * loading: import('vue').Ref<boolean>,
@@ -45,6 +55,7 @@ const cache = new Map()
45
55
  *
46
56
  * @example
47
57
  * const { requiredUnmet, completed, loading } = useSetupStatus('procest', manifest)
58
+ * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable
48
59
  */
49
60
  export function useSetupStatus(appId, manifest) {
50
61
  const setup = (manifest && manifest.setup) || {}
@@ -68,6 +79,9 @@ export function useSetupStatus(appId, manifest) {
68
79
  cache.set(appId, entry)
69
80
  }
70
81
  const { status, loading, error, forbidden } = entry
82
+ // Same source the wizard's dependency gate reads. Initial state is fixed
83
+ // for the page lifetime, so one read per call is enough.
84
+ const serverAppStatuses = readServerAppStatuses(appId)
71
85
 
72
86
  const steps = computed(() => stepDefs.map((s) => {
73
87
  const bag = status.value.steps || {}
@@ -81,7 +95,12 @@ export function useSetupStatus(appId, manifest) {
81
95
  // that act on an unmet step — CnAppRoot auto-opening the non-gating
82
96
  // wizard — must act only on steps the server actually reported, or an
83
97
  // unknowable step parks the wizard over the app for ever.
84
- return { ...s, done: st.done === true, reported, detail: st.detail }
98
+ //
99
+ // `applicable` is false when the step `requires` an app that is not
100
+ // installed and enabled. The wizard skips such a step, so it can never
101
+ // be done here; it is not applicable rather than unmet.
102
+ const missingApps = missingRequiredApps(s.requires, serverAppStatuses).map((row) => row.id)
103
+ return { ...s, done: st.done === true, reported, detail: st.detail, applicable: missingApps.length === 0, missingApps }
85
104
  }))
86
105
  // Presentational step types carry no work, so the server never reports a
87
106
  // `done` flag for them. Counting them as "unmet" made `optionalUnmet`
@@ -89,7 +108,9 @@ export function useSetupStatus(appId, manifest) {
89
108
  // which kept CnAppRoot's non-gating setup wizard auto-opening over the
90
109
  // app on every fresh browser profile no matter how complete setup was.
91
110
  // Only actionable steps can be unmet.
92
- const isActionable = (s) => s.type !== 'info' && s.type !== 'summary'
111
+ // A step whose required apps are absent is skipped by the wizard, so it
112
+ // is neither met nor unmet (see `applicable` above).
113
+ const isActionable = (s) => s.type !== 'info' && s.type !== 'summary' && s.applicable !== false
93
114
  // `forbidden` empties BOTH lists, and that is what actually suppresses the
94
115
  // wizard: CnAppRoot gates on `requiredUnmet.length > 0` (blocking) and on
95
116
  // `requiredUnmet.length === 0 && optionalUnmet.length > 0` (auto-open) —
@@ -110,6 +131,8 @@ export function useSetupStatus(appId, manifest) {
110
131
  // suppressing it is what left dossiq's `seed` step (and with it the app's
111
132
  // only demo-data affordance) permanently unreachable.
112
133
  const optionalUnmetReported = computed(() => optionalUnmet.value.filter((s) => s.reported === true))
134
+ // Steps the wizard skips because an app in their `requires` is absent.
135
+ const notApplicable = computed(() => steps.value.filter((s) => s.applicable === false))
113
136
  const completed = computed(() => {
114
137
  if (!enabled) {
115
138
  return true
@@ -170,7 +193,7 @@ export function useSetupStatus(appId, manifest) {
170
193
  refresh()
171
194
  }
172
195
 
173
- return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, completed, enabled, loading, error, forbidden, refresh }
196
+ return { steps, status, requiredUnmet, optionalUnmet, optionalUnmetReported, notApplicable, completed, enabled, loading, error, forbidden, refresh }
174
197
  }
175
198
 
176
199
  /**
@@ -35,6 +35,23 @@
35
35
  color: var(--color-text-maxcontrast);
36
36
  }
37
37
 
38
+ /* Beside the inline search (`showCountWithSearch`): the search field shrinks
39
+ first and the counter stays on one line. */
40
+ .cn-actions-bar__count--beside-search {
41
+ flex-shrink: 0;
42
+ white-space: nowrap;
43
+ }
44
+
45
+ /* Taken out of the flex row so it adds no gap, but kept in the accessibility
46
+ tree so the live region exists before a total appears. */
47
+ .cn-actions-bar__count--empty {
48
+ position: absolute;
49
+ width: 1px;
50
+ height: 1px;
51
+ overflow: hidden;
52
+ clip-path: inset(50%);
53
+ }
54
+
38
55
  /* Inline search field */
39
56
  .cn-actions-bar__search {
40
57
  position: relative;
@@ -294,8 +311,9 @@
294
311
  display: contents;
295
312
  }
296
313
 
297
- /* #after-search filter buttons + the view toggle ride beside the
298
- search field (DOM order — search first via its own order). */
314
+ /* #after-search filter buttons, the beside-search counter and the view
315
+ toggle ride beside the search field (DOM order — search first via its
316
+ own order). */
299
317
  .cn-actions-bar__info > * {
300
318
  order: 1;
301
319
  }
@@ -481,3 +481,8 @@
481
481
  font-size: 0.9em;
482
482
  white-space: nowrap;
483
483
  }
484
+
485
+ /* A page view the switch shows with nothing to draw (view-switch-containers). */
486
+ .cn-detail-page .cn-page-view-region--empty {
487
+ padding: 40px 20px;
488
+ }