@conduction/nextcloud-vue 2.136.0 → 2.137.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 (84) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +9 -0
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +4 -3
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +4 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +61 -28
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  10. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +20 -0
  11. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  12. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +33 -25
  13. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +13 -0
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +1 -1
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +36 -1
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +4 -4
  25. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  26. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  27. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +11 -0
  28. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +10 -2
  30. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  32. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  33. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +1 -0
  34. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  35. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  36. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  37. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
  39. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js +14 -4
  40. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
  41. package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
  42. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
  43. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +10 -0
  44. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
  45. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +29 -13
  46. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
  47. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +3 -1
  48. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -1
  49. package/dist/esm/library-style.js +1 -1
  50. package/dist/esm/nextcloud-vue.css +258 -160
  51. package/dist/esm/utils/contentHeaderLink.js +7 -1
  52. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  53. package/dist/esm/utils/savedViewMenu.js +4 -1
  54. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  55. package/dist/esm/utils/savedViewSummary.js +11 -4
  56. package/dist/esm/utils/savedViewSummary.js.map +1 -1
  57. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  58. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  59. package/dist/nextcloud-vue.cjs.js +422 -225
  60. package/dist/nextcloud-vue.cjs.js.map +2 -2
  61. package/dist/nextcloud-vue.css +258 -160
  62. package/package.json +1 -1
  63. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +13 -1
  64. package/src/components/CnAppNav/CnAppNav.vue +28 -3
  65. package/src/components/CnDataTable/CnDataTable.vue +25 -0
  66. package/src/components/CnDetailPage/CnDetailPage.vue +14 -1
  67. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
  68. package/src/components/CnIndexPage/CnIndexPage.vue +40 -5
  69. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -0
  70. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +21 -0
  71. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +7 -0
  72. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +13 -0
  73. package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +14 -4
  74. package/src/components/CnTimelineWidget/CnTimelineManage.vue +23 -0
  75. package/src/components/CnTimelineWidget/dashboardRegistration.js +3 -1
  76. package/src/css/look-board-detail.css +10 -0
  77. package/src/css/look-board-index.css +50 -0
  78. package/src/css/look-board.css +5 -1
  79. package/src/css/table.css +5 -0
  80. package/src/schemas/app-manifest-v2.schema.json +4 -2
  81. package/src/utils/contentHeaderLink.js +7 -1
  82. package/src/utils/savedViewMenu.js +4 -1
  83. package/src/utils/savedViewSummary.js +11 -4
  84. package/src/utils/validateManifestV2.compiled.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.136.0",
3
+ "version": "2.137.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div class="cn-admin-settings-shell" :class="[ownLookClass, sectionColumns ? `cn-admin-settings-shell--columns-${sectionColumns}` : '']">
2
+ <div
3
+ class="cn-admin-settings-shell"
4
+ :class="[ownLookClass, sectionColumns ? `cn-admin-settings-shell--columns-${sectionColumns}` : '']"
5
+ :style="versionTitleSize ? { '--cn-board-version-title-size': `${versionTitleSize}px` } : null">
3
6
  <!-- Names a development, test or acceptance environment; cannot be dismissed. Production shows nothing. -->
4
7
  <CnEnvironmentBanner v-if="environmentName" :environment="environmentName" />
5
8
 
@@ -768,6 +771,15 @@ export default {
768
771
  type: Object,
769
772
  default: () => ({}),
770
773
  },
774
+
775
+ /**
776
+ * The board look's version card title size in px, when the app's board
777
+ * draws it other than 17px (PqBeheer 19, OcInstellingen 18).
778
+ */
779
+ versionTitleSize: {
780
+ type: Number,
781
+ default: 0,
782
+ },
771
783
  },
772
784
 
773
785
  emits: ['update', 'reimported', 'reimport-error', 'tab-change'],
@@ -184,8 +184,9 @@
184
184
  :key="item.id"
185
185
  :name="resolveLabel(item)"
186
186
  :data-testid="`cn-nav-caption-${item.id}`">
187
- <template v-if="item.href" #actions>
187
+ <template v-if="item.href || item.action === 'user-settings'" #actions>
188
188
  <NcActionLink
189
+ v-if="item.href"
189
190
  :href="item.href"
190
191
  :aria-label="captionEditLabel(item)"
191
192
  :data-testid="`cn-nav-caption-edit-${item.id}`">
@@ -194,6 +195,16 @@
194
195
  </template>
195
196
  {{ captionEditLabel(item) }}
196
197
  </NcActionLink>
198
+ <NcActionButton
199
+ v-else
200
+ :aria-label="captionEditLabel(item)"
201
+ :data-testid="`cn-nav-caption-edit-${item.id}`"
202
+ @click="cnOpenUserSettings()">
203
+ <template #icon>
204
+ <Pencil :size="20" />
205
+ </template>
206
+ {{ captionEditLabel(item) }}
207
+ </NcActionButton>
197
208
  </template>
198
209
  </NcAppNavigationCaption>
199
210
  <!-- `type: "hint"`: a muted line, no link, as the empty "My
@@ -447,8 +458,9 @@
447
458
  :key="item.id"
448
459
  :name="resolveLabel(item)"
449
460
  :data-testid="`cn-nav-caption-${item.id}`">
450
- <template v-if="item.href" #actions>
461
+ <template v-if="item.href || item.action === 'user-settings'" #actions>
451
462
  <NcActionLink
463
+ v-if="item.href"
452
464
  :href="item.href"
453
465
  :aria-label="captionEditLabel(item)"
454
466
  :data-testid="`cn-nav-caption-edit-${item.id}`">
@@ -457,6 +469,16 @@
457
469
  </template>
458
470
  {{ captionEditLabel(item) }}
459
471
  </NcActionLink>
472
+ <NcActionButton
473
+ v-else
474
+ :aria-label="captionEditLabel(item)"
475
+ :data-testid="`cn-nav-caption-edit-${item.id}`"
476
+ @click="cnOpenUserSettings()">
477
+ <template #icon>
478
+ <Pencil :size="20" />
479
+ </template>
480
+ {{ captionEditLabel(item) }}
481
+ </NcActionButton>
460
482
  </template>
461
483
  </NcAppNavigationCaption>
462
484
  <NcAppNavigationItem
@@ -500,7 +522,7 @@
500
522
  <script>
501
523
  import { translate as t } from '@nextcloud/l10n'
502
524
  import { generateUrl } from '@nextcloud/router'
503
- import { NcActionLink, NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
525
+ import { NcActionButton, NcActionLink, NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
504
526
  import { toRaw } from 'vue'
505
527
  import BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'
506
528
  import Cog from 'vue-material-design-icons/Cog.vue'
@@ -595,6 +617,7 @@ export default {
595
617
  name: 'CnAppNav',
596
618
 
597
619
  components: {
620
+ NcActionButton,
598
621
  NcActionLink,
599
622
  Pencil,
600
623
  NcAppNavigation,
@@ -1974,6 +1997,8 @@ export default {
1974
1997
  * `icon`, `count`, `children`, and `pinned`. A caption's `href` is
1975
1998
  * drawn as a pencil link beside the heading: the place where what
1976
1999
  * stands under it is changed (a user's own choice of entries, say).
2000
+ * `action: "user-settings"` draws the same pencil as a button that
2001
+ * opens the user settings dialog.
1977
2002
  *
1978
2003
  * @param {{ type?: string }} item Menu entry descriptor.
1979
2004
  * @return {boolean}
@@ -346,7 +346,12 @@
346
346
  falls back to formatValue(). Columns with `aggregate` get a
347
347
  count of related objects (see cellValue/loadAggregates). The
348
348
  #column-{key} slot still wins. -->
349
+ <span
350
+ v-if="isTextZero(row, col)"
351
+ class="cn-table-cell__zero"
352
+ data-testid="cn-cell-zero">{{ cellValue(row, col) }}</span>
349
353
  <CnCellRenderer
354
+ v-else
350
355
  :value="cellValue(row, col)"
351
356
  :property="columnProperty(col)"
352
357
  :formatter="col.formatter || null"
@@ -2199,6 +2204,26 @@ export default {
2199
2204
  return this.getCellValue(row, this.columnValueField(col))
2200
2205
  },
2201
2206
 
2207
+ /**
2208
+ * Whether an aggregate cell shows its `zeroLabel` as muted text instead
2209
+ * of through the column's widget (`aggregate.zeroAs: "text"`): a count
2210
+ * of zero, or a max with nothing to read.
2211
+ *
2212
+ * @param {object} row The row data.
2213
+ * @param {object} col The column definition.
2214
+ * @return {boolean}
2215
+ * @spec openspec/changes/aggregate-zero-as-text/specs/index-page/spec.md#requirement-a-count-column-can-show-its-zero-label-as-text
2216
+ */
2217
+ isTextZero(row, col) {
2218
+ const aggregate = col && col.aggregate
2219
+ if (!aggregate || aggregate.zeroAs !== 'text' || typeof aggregate.zeroLabel !== 'string' || aggregate.zeroLabel === '') {
2220
+ return false
2221
+ }
2222
+ const cached = this.aggregateValues[String(row[this.rowKey])]
2223
+ const v = cached ? cached[col.key] : undefined
2224
+ return v === 0 || v === '0' || v === null
2225
+ },
2226
+
2202
2227
  /**
2203
2228
  * The row field a column's cell shows: `col.valueField` when set, else
2204
2229
  * `col.key`. The key stays what the header sorts and filters on, so a
@@ -253,7 +253,7 @@
253
253
  card, next to the checklist that explains it, and not here
254
254
  as well: one primary button per page. -->
255
255
  <NcButton
256
- v-if="effectivePrimaryLabel && !showsNextStepCard"
256
+ v-if="effectivePrimaryLabel && !showsNextStepCard && stagePrimaryOnTab"
257
257
  :id="primaryActionAnchorId"
258
258
  ref="primaryActionButton"
259
259
  variant="primary"
@@ -3832,6 +3832,19 @@ export default {
3832
3832
  .slice(0, MAX_QUICK_ACTIONS)
3833
3833
  },
3834
3834
 
3835
+ /**
3836
+ * Whether the stage's primary button belongs beside the open body tab:
3837
+ * an inline stage action with `tabs`/`notTabs` follows the tab like a
3838
+ * quick action (DqZaakAssistent: no "Next step" beside AI oversight).
3839
+ * It stays in the Actions menu either way.
3840
+ *
3841
+ * @return {boolean} true when the button may show.
3842
+ * @spec openspec/changes/stage-primary-follows-tab/specs/detail-page/spec.md#requirement-a-stage-action-can-follow-the-open-tab
3843
+ */
3844
+ stagePrimaryOnTab() {
3845
+ return !this.stagePrimaryEntry || onTab(this.stagePrimary, this.activeBodyTab)
3846
+ },
3847
+
3835
3848
  /**
3836
3849
  * The stage's primary action in dispatchable form: the menu entry
3837
3850
  * CnActionButtons built for it, with its `run()`. Null while the
@@ -99,6 +99,7 @@
99
99
  :hideEmpty="content.hideEmpty === true || hideEmpty"
100
100
  :columns="content.columns || 3"
101
101
  :collapsedFields="content.collapsedFields ?? null"
102
+ :footnote="content.footnote || ''"
102
103
  :editable="content.editable !== false" />
103
104
 
104
105
  <!-- `type: 'related'` — resolves this object's relations and links into
@@ -926,7 +926,7 @@
926
926
  <template v-if="hasRowActions" #row-actions="{ row }">
927
927
  <slot name="row-actions" :row="row">
928
928
  <CnRowActions
929
- :actions="rowActionsFor(row)"
929
+ :actions="rowMenuActionsFor(row)"
930
930
  :row="row"
931
931
  :rowLabel="rowTitleFor(row)"
932
932
  @action="onRowAction" />
@@ -1173,7 +1173,7 @@
1173
1173
  <template v-if="hasRowActions || $slots['row-actions']" #row-actions="{ object }">
1174
1174
  <slot name="row-actions" :row="object">
1175
1175
  <CnRowActions
1176
- :actions="rowActionsFor(object)"
1176
+ :actions="rowMenuActionsFor(object)"
1177
1177
  :row="object"
1178
1178
  :rowLabel="rowTitleFor(object)"
1179
1179
  @action="onRowAction" />
@@ -1225,7 +1225,7 @@
1225
1225
  <template v-if="hasRowActions" #card-actions="{ object }">
1226
1226
  <slot name="row-actions" :row="object">
1227
1227
  <CnRowActions
1228
- :actions="rowActionsFor(object)"
1228
+ :actions="rowMenuActionsFor(object)"
1229
1229
  :row="object"
1230
1230
  :rowLabel="rowTitleFor(object)"
1231
1231
  :triggerLabel="isBoardLook ? t('nextcloud-vue', 'More actions for {name}', { name: rowTitleFor(object) }) : ''"
@@ -1237,7 +1237,7 @@
1237
1237
  <!-- Right-click context menu (positioned at cursor via CSS). Same per-row list as the row actions menu, so the two never drift. -->
1238
1238
  <CnContextMenu
1239
1239
  v-model:open="contextMenuOpen"
1240
- :actions="rowActionsFor(contextMenuShownRow)"
1240
+ :actions="rowMenuActionsFor(contextMenuShownRow)"
1241
1241
  :targetItem="contextMenuShownRow"
1242
1242
  @action="onRowAction"
1243
1243
  @close="closeContextMenu" />
@@ -8017,7 +8017,26 @@ export default {
8017
8017
  },
8018
8018
 
8019
8019
  hasRowActions() {
8020
- return this.$slots['row-actions'] || this.mergedActions.length > 0
8020
+ const refs = this.columnActionRefs
8021
+ return this.$slots['row-actions'] || this.mergedActions.some((action) => action && !refs.has(String(action.id || action.label)) && !refs.has(String(action.label)))
8022
+ },
8023
+
8024
+ /**
8025
+ * The row actions an `actions` column already shows as buttons, by the
8026
+ * ids (or labels) its `widgetProps.actions` names. The row menu leaves
8027
+ * them out, so a row does not offer the same action twice (DqPostvak).
8028
+ *
8029
+ * @return {Set<string>}
8030
+ * @spec openspec/changes/column-actions-leave-the-row-menu/specs/column-actions-leave-the-row-menu/spec.md
8031
+ */
8032
+ columnActionRefs() {
8033
+ const refs = new Set()
8034
+ for (const col of Array.isArray(this.columns) ? this.columns : []) {
8035
+ if (col && typeof col === 'object' && col.widget === 'actions' && Array.isArray(col.widgetProps?.actions)) {
8036
+ col.widgetProps.actions.forEach((ref) => refs.add(String(ref)))
8037
+ }
8038
+ }
8039
+ return refs
8021
8040
  },
8022
8041
 
8023
8042
  /**
@@ -11864,6 +11883,22 @@ export default {
11864
11883
  )
11865
11884
  },
11866
11885
 
11886
+ /**
11887
+ * The row menu's actions: the row's actions less those an `actions`
11888
+ * column already shows as buttons.
11889
+ *
11890
+ * @param {object} row The row.
11891
+ * @return {Array<object>}
11892
+ * @spec openspec/changes/column-actions-leave-the-row-menu/specs/column-actions-leave-the-row-menu/spec.md
11893
+ */
11894
+ rowMenuActionsFor(row) {
11895
+ const refs = this.columnActionRefs
11896
+ if (refs.size === 0) {
11897
+ return this.rowActionsFor(row)
11898
+ }
11899
+ return this.rowActionsFor(row).filter((action) => !refs.has(String(action.id || action.label)) && !refs.has(String(action.label)))
11900
+ },
11901
+
11867
11902
  /**
11868
11903
  * The row's own name, for the row menu button's accessible name
11869
11904
  * ("Actions for <title>"): the `nameFormatter`, else the
@@ -141,3 +141,4 @@ export default {
141
141
  | `borderless` | Boolean | `false` | Drop the card border and background, for a host that draws its own (a tab panel). |
142
142
  | `flush` | Boolean | `false` | Drop the content padding, for a host that supplies its own inset. |
143
143
  | `chromeless` | Boolean | `false` | Draw no card at all: what a tab panel wants, in one prop instead of three. Implies `show-title="false"`, `borderless` and `flush`, and drops the header divider too. The Save button keeps its header. Forwarded to `CnWidgetWrapper`. |
144
+ | `footnote` | String | `''` | A muted line under the fields, through the host translate (DqZaakContact "Ontvangstbevestiging"). |
@@ -396,6 +396,10 @@
396
396
  </NcButton>
397
397
  </div>
398
398
 
399
+ <p v-if="footnote" class="cn-object-data-widget__footnote" data-testid="cn-object-data-widget-footnote">
400
+ {{ cnTranslate(footnote) }}
401
+ </p>
402
+
399
403
  <!-- Read-only @self metadata, surfaced on demand from the
400
404
  Metadata action item rather than as a permanent page widget. -->
401
405
  <CnObjectMetadataModal
@@ -837,6 +841,17 @@ export default {
837
841
  default: () => t('nextcloud-vue', 'No data available'),
838
842
  },
839
843
 
844
+ /**
845
+ * A muted line under the fields, through the host translate
846
+ * (DqZaakContact "Ontvangstbevestiging").
847
+ *
848
+ * @spec openspec/changes/data-widget-footnote/specs/data-widget-footnote/spec.md#requirement-a-data-card-can-end-with-a-footnote
849
+ */
850
+ footnote: {
851
+ type: String,
852
+ default: '',
853
+ },
854
+
840
855
  /** Label for the Metadata item in the overflow Actions menu. */
841
856
  metadataLabel: {
842
857
  type: String,
@@ -2535,6 +2550,12 @@ export default {
2535
2550
  outline: none;
2536
2551
  }
2537
2552
 
2553
+ .cn-object-data-widget__footnote {
2554
+ margin: calc(var(--default-grid-baseline, 4px) * 3) 0 0;
2555
+ color: var(--color-text-maxcontrast);
2556
+ font-size: 13px;
2557
+ }
2558
+
2538
2559
  .cn-object-data-widget__empty {
2539
2560
  color: var(--color-text-maxcontrast);
2540
2561
  font-style: italic;
@@ -72,6 +72,7 @@
72
72
  :options="schemaSelectOptions(groupIdx)"
73
73
  :placeholder="labels.selectSchema"
74
74
  :inputLabel="type.label"
75
+ :clearable="false"
75
76
  label="label"
76
77
  trackBy="value"
77
78
  @update:modelValue="handleSchemaChange(groupIdx, type, $event)" />
@@ -1371,6 +1372,12 @@ export default {
1371
1372
  max-width: 50%;
1372
1373
  }
1373
1374
 
1375
+ /* DqBeheerinstellingen: the register select fills its half of the card. */
1376
+ .cn-register-mapping__selects-register :deep(.v-select) {
1377
+ width: 100%;
1378
+ max-width: none;
1379
+ }
1380
+
1374
1381
  .cn-register-mapping__select-grid {
1375
1382
  display: grid;
1376
1383
  grid-template-columns: repeat(var(--cn-register-mapping-columns, 3), minmax(0, 1fr));
@@ -603,13 +603,26 @@ export default {
603
603
  margin: 0;
604
604
  }
605
605
 
606
+ /* The server draws every dt as a 130px right-aligned inline block and pads
607
+ dt and dd 12px; a fact is a label over its value. */
608
+ .cn-retention__fact dt,
609
+ .cn-retention__fact dd {
610
+ display: block;
611
+ width: auto;
612
+ white-space: normal;
613
+ text-align: start;
614
+ }
615
+
606
616
  .cn-retention__fact dt {
617
+ margin: 0;
618
+ padding: 0;
607
619
  color: var(--color-text-maxcontrast);
608
620
  font-size: 13px;
609
621
  }
610
622
 
611
623
  .cn-retention__fact dd {
612
624
  margin: 0;
625
+ padding: 0;
613
626
  font-weight: 600;
614
627
  }
615
628
 
@@ -93,13 +93,23 @@ function loadSchema(register, schema) {
93
93
  /**
94
94
  * The names of referenced records, each asked once.
95
95
  *
96
- * @param {{register: string, schema: string, labelField: string, ids: Array<string>}} ref The reference filter.
96
+ * @param {{register: string, schema: string, labelField: string, matchField?: string, ids: Array<string>}} ref The reference filter; with `matchField` the ids are that field's values.
97
97
  * @return {Promise<{[id: string]: string}>}
98
98
  */
99
99
  async function loadLabels(ref) {
100
- const prefix = `${ref.register}|${ref.schema}|${ref.labelField}|`
100
+ const prefix = `${ref.register}|${ref.schema}|${ref.labelField}|${ref.matchField || ''}|`
101
101
  const missing = ref.ids.filter((id) => !labelCache.has(prefix + id))
102
- if (missing.length > 0) {
102
+ // A value that identifies a record by another field: one read per value.
103
+ if (missing.length > 0 && ref.matchField) {
104
+ for (const value of missing) {
105
+ labelCache.set(prefix + value, getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { [ref.matchField]: value, _limit: 1 })
106
+ .then((data) => {
107
+ const row = Array.isArray(data && data.results) ? data.results[0] : null
108
+ return row ? pickRefLabel(row, ref.labelField) : null
109
+ })
110
+ .catch(() => null))
111
+ }
112
+ } else if (missing.length > 0) {
103
113
  const request = getJson(`/apps/openregister/api/objects/${encodeURIComponent(ref.register)}/${encodeURIComponent(ref.schema)}`, { _ids: missing.join(','), _limit: missing.length })
104
114
  .then((data) => {
105
115
  const byId = {}
@@ -372,7 +382,7 @@ export default {
372
382
  const fields = fieldsFromSchema(schema, { includeReadOnly: true })
373
383
  const refs = this.candidates
374
384
  .filter((c) => c.page === page.id)
375
- .flatMap((c) => viewFilterReferences(c.state, fields))
385
+ .flatMap((c) => viewFilterReferences(c.state, fields, page.filterLabels))
376
386
  for (const ref of refs) {
377
387
  const labels = await loadLabels({ ...ref, register: ref.register || page.register })
378
388
  if (Object.keys(labels).length === 0) {
@@ -169,6 +169,19 @@
169
169
  </div>
170
170
  </template>
171
171
 
172
+ <div v-if="composerOnly && canManage && composerEnabled"
173
+ class="cn-timeline-manage__header"
174
+ data-testid="cn-timeline-composer-heading">
175
+ <div class="cn-timeline-manage__heading">
176
+ <h3 class="cn-timeline-manage__title">
177
+ {{ composerTitleText }}
178
+ </h3>
179
+ <p class="cn-timeline-manage__subtitle">
180
+ {{ composerSubtitleText }}
181
+ </p>
182
+ </div>
183
+ </div>
184
+
172
185
  <NcNoteCard v-if="composerOnly && error" type="error" data-testid="cn-timeline-error">
173
186
  {{ error }}
174
187
  </NcNoteCard>
@@ -332,6 +345,16 @@ export default {
332
345
  return this.content.subtitle ? this.translate(this.content.subtitle) : t('nextcloud-vue', 'What happened, who did it and when, newest first')
333
346
  },
334
347
 
348
+ /** @return {string} The title of the note form's own card (`composer: "only"`): `content.title`, else Add note. */
349
+ composerTitleText() {
350
+ return this.content.title ? this.translate(this.content.title) : t('nextcloud-vue', 'Add note')
351
+ },
352
+
353
+ /** @return {string} The line under that title: `content.subtitle`, else where the note goes. */
354
+ composerSubtitleText() {
355
+ return this.content.subtitle ? this.translate(this.content.subtitle) : t('nextcloud-vue', 'Goes on the timeline above, internal unless you make it visible')
356
+ },
357
+
335
358
  publicLabel() {
336
359
  return this.content.publicLabel ? this.translate(this.content.publicLabel) : t('nextcloud-vue', 'Visible to the requester')
337
360
  },
@@ -6,7 +6,8 @@
6
6
  * dashboardWidgetRegistry. Like `audit-trail` it is a DETAIL-PAGE surface:
7
7
  * the object comes from the page's object context. It has no config form
8
8
  * yet, so it is placed from the manifest and never offered in the
9
- * Add-widget picker.
9
+ * Add-widget picker. It draws its own title row and asks for the card, so a
10
+ * tab stack or the side column stands it in an untitled card.
10
11
  *
11
12
  * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order
12
13
  */
@@ -29,4 +30,5 @@ registerDashboardWidget('timeline', {
29
30
  icon: 'TimelineClockOutline',
30
31
  surfaces: ['detail-page'],
31
32
  ownsTitle: true,
33
+ chrome: 'card',
32
34
  })
@@ -1213,6 +1213,16 @@
1213
1213
  font-weight: 500;
1214
1214
  }
1215
1215
 
1216
+ /* "Bewaren voor een procedure" and "Overdracht naar het e-Depot" are 15px/600
1217
+ section titles over their rows, not the inner heading's 1.4em. */
1218
+ .cn-look-board .cn-retention .cn-retention__section > .cn-inner-heading,
1219
+ .cn-look-board .cn-retention .cn-retention__outcome > .cn-inner-heading {
1220
+ margin: 4px 0 0;
1221
+ font-size: 15px;
1222
+ font-weight: 600;
1223
+ line-height: 1.2;
1224
+ }
1225
+
1216
1226
  /* ---- The task table's row button (CnTasksWidget table, DqZaakTaken) ------ */
1217
1227
  /* "Afronden" is a 34px secondary with 13px text 14px in. NcButton pads a
1218
1228
  text-only label 12px plus 4px inside; the doubled class outranks its
@@ -1368,6 +1368,7 @@
1368
1368
  margin-inline-start: auto;
1369
1369
  color: var(--color-text-maxcontrast);
1370
1370
  font-size: 13px;
1371
+ white-space: nowrap;
1371
1372
  }
1372
1373
 
1373
1374
  .cn-look-board .cn-object-card--board .cn-object-card__record .cn-object-card__title {
@@ -1380,12 +1381,19 @@
1380
1381
  }
1381
1382
 
1382
1383
  .cn-look-board .cn-object-card--board .cn-object-card__record-meta {
1384
+ --nldesign-component-paragraph-font-size: 14px;
1385
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
1383
1386
  margin: 0;
1384
1387
  color: var(--color-text-maxcontrast);
1385
1388
  font-size: 14px;
1386
1389
  }
1387
1390
 
1391
+ /* The meta line, note and description are paragraphs: each hands the theme,
1392
+ which sets paragraphs with `!important`, its own size and ink. */
1388
1393
  .cn-look-board .cn-object-card--board .cn-object-card__record-note {
1394
+ --nldesign-component-paragraph-font-size: 13px;
1395
+ --nldesign-component-paragraph-font-weight: 600;
1396
+ --nldesign-component-paragraph-color: var(--cn-board-text-soft);
1389
1397
  margin: 0;
1390
1398
  color: var(--cn-board-text-soft);
1391
1399
  font-size: 13px;
@@ -1393,10 +1401,12 @@
1393
1401
  }
1394
1402
 
1395
1403
  .cn-look-board .cn-object-card--board .cn-object-card__record-note--purple {
1404
+ --nldesign-component-paragraph-color: var(--cn-status-purple-text);
1396
1405
  color: var(--cn-status-purple-text);
1397
1406
  }
1398
1407
 
1399
1408
  .cn-look-board .cn-object-card--board .cn-object-card__record-note--teal {
1409
+ --nldesign-component-paragraph-color: var(--cn-status-teal-text);
1400
1410
  color: var(--cn-status-teal-text);
1401
1411
  }
1402
1412
 
@@ -1433,6 +1443,9 @@
1433
1443
  }
1434
1444
 
1435
1445
  .cn-look-board .cn-object-card--board .cn-object-card__record-description {
1446
+ --nldesign-component-paragraph-font-size: 14px;
1447
+ --nldesign-component-paragraph-line-height: 1.4;
1448
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
1436
1449
  margin: 0;
1437
1450
  color: var(--color-text-maxcontrast);
1438
1451
  font-size: 14px;
@@ -1869,6 +1882,22 @@
1869
1882
  font-weight: 700;
1870
1883
  }
1871
1884
 
1885
+ /* A pill in a trailing cell stays inside the cell's width and ends in an
1886
+ ellipsis, instead of running over the next cell (DqTeamwachtrij). */
1887
+ .cn-look-board .cn-object-row__trail .cn-cell-renderer:not(.cn-look-nextcloud *) {
1888
+ display: block;
1889
+ max-width: 100%;
1890
+ }
1891
+
1892
+ .cn-look-board .cn-object-row__trail .cn-status-badge:not(.cn-look-nextcloud *) {
1893
+ display: inline-block;
1894
+ box-sizing: border-box;
1895
+ max-width: 100%;
1896
+ overflow: hidden;
1897
+ text-overflow: ellipsis;
1898
+ vertical-align: middle;
1899
+ }
1900
+
1872
1901
  /* ---- The map view's missing-address card (DqZakenKaart) ---------------- */
1873
1902
  /* The board draws "3 zaken zonder adres" as a 15px title at its normal line
1874
1903
  height with "Toon ze in de tabel" under it as a link flush with the title,
@@ -2030,3 +2059,24 @@
2030
2059
  .cn-look-board .cn-saved-views-panel:not(.cn-look-nextcloud *) .cn-saved-views-panel__row:is(.cn-saved-views-panel__row--selected, :has(.action-item--open)) .cn-saved-views-panel__name-text {
2031
2060
  color: var(--color-primary-element-light-text);
2032
2061
  }
2062
+
2063
+ /* ---- The side-panel grid's row spacing --------------------------------- */
2064
+ /* The side-panel page is a grid of seven rows; a row-gap opens between every
2065
+ pair, empty or not, so a page without below-header, stat row or chip row
2066
+ drew 60px of nothing under its header (DqZoekresultaten). The grid has no
2067
+ row gap here; each part that is present carries the section gap under it.
2068
+ @spec openspec/changes/side-panel-grid-no-empty-row-gaps/specs/side-panel-grid-no-empty-row-gaps/spec.md */
2069
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) {
2070
+ row-gap: 0;
2071
+ }
2072
+
2073
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > :first-child,
2074
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-index-page__below-header,
2075
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-index-page__stat-row,
2076
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-actions-bar {
2077
+ margin-bottom: var(--cn-board-section-gap);
2078
+ }
2079
+
2080
+ .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-index-page__quick-filter-row {
2081
+ margin-bottom: calc(var(--cn-board-section-gap) + 12px);
2082
+ }
@@ -2886,8 +2886,12 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2886
2886
  font-size: 14px;
2887
2887
  }
2888
2888
 
2889
+ /* The version card's title follows its board: 17px on DqInstellingenAlgemeen
2890
+ and LrBeheerinstellingen; an app whose board draws another size passes the
2891
+ shell's `versionTitleSize`.
2892
+ @spec openspec/changes/board-version-title-size/specs/board-settings-card-controls/spec.md */
2889
2893
  .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(.cn-version-info--compact) > .settings-section__name {
2890
- font-size: 18px;
2894
+ font-size: var(--cn-board-version-title-size, 17px);
2891
2895
  }
2892
2896
 
2893
2897
  /* ---- A card titled as a side card (`titleTone: "side"`) ------------------ */
package/src/css/table.css CHANGED
@@ -203,6 +203,11 @@
203
203
  /* The overline of a cell (`columns[].overline` / `overlineText`): muted and
204
204
  smaller, on its own line above the value. Same theme hooks as the
205
205
  secondary line. */
206
+ /* A count column's zero label as text (`aggregate.zeroAs: "text"`): "Geen" in the muted ink. */
207
+ .cn-data-table .cn-table-cell__zero {
208
+ color: var(--color-text-maxcontrast);
209
+ }
210
+
206
211
  .cn-data-table .cn-table-cell__overline {
207
212
  display: block;
208
213
  color: var(--cn-table-secondary-color, var(--color-text-maxcontrast));
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.154.0",
6
+ "version": "2.155.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -3609,7 +3609,9 @@
3609
3609
  "else": {
3610
3610
  "$ref": "#/$defs/actionFields",
3611
3611
  "properties": {
3612
- "children": { "items": { "$ref": "#/$defs/headerAction" } }
3612
+ "children": { "items": { "$ref": "#/$defs/headerAction" } },
3613
+ "tabs": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "As a `primaryActionByStage` entry: the body tabs beside which its header button shows (2.155.0)." },
3614
+ "notTabs": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "As a `primaryActionByStage` entry: the body tabs beside which its header button stands aside; it stays in the Actions menu (2.155.0)." }
3613
3615
  },
3614
3616
  "unevaluatedProperties": false
3615
3617
  }
@@ -52,6 +52,8 @@ export function contentHeaderLink(content) {
52
52
  /**
53
53
  * The header link a widget DEFINITION declares: `headerLink` on the
54
54
  * definition itself, else what its content asks for (see contentHeaderLink).
55
+ * A view-all the widget draws under its own content
56
+ * (`viewAllPlacement: "footer"`) is not a header link.
55
57
  *
56
58
  * @param {object|null|undefined} widget A widget definition.
57
59
  * @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
@@ -64,7 +66,11 @@ export function widgetHeaderLink(widget) {
64
66
  if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
65
67
  return declared
66
68
  }
67
- return contentHeaderLink(widget.content)
69
+ const content = widget.content
70
+ if (content && content.viewAllPlacement === 'footer' && !content.headerLink) {
71
+ return null
72
+ }
73
+ return contentHeaderLink(content)
68
74
  }
69
75
 
70
76
  /**