@conduction/nextcloud-vue 2.147.0 → 2.149.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 (130) hide show
  1. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  2. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +24 -7
  3. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  6. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +12 -1
  7. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
  9. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  11. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +23 -1
  12. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +1 -1
  14. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +34 -1
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +71 -8
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -2
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js +1 -1
  26. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +1 -1
  29. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +64 -8
  30. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +14 -4
  32. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +32 -4
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +10 -2
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +84 -1
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +61 -33
  42. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  44. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +49 -2
  45. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +90 -59
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue2.js +3 -1
  49. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue3.js.map +1 -1
  51. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  52. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +10 -0
  53. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  54. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  56. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +11 -0
  57. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +28 -21
  59. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  61. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +8 -0
  62. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  65. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +1 -1
  66. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  69. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -15
  70. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +123 -115
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  73. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  74. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +4 -0
  75. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  76. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  77. package/dist/esm/components/CnStepper/CnStepper.vue.js +1 -1
  78. package/dist/esm/components/CnStepper/CnStepper.vue2.js.map +1 -1
  79. package/dist/esm/components/CnStepper/CnStepper.vue3.js.map +1 -1
  80. package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +1 -1
  81. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +3 -1
  82. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -1
  83. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +26 -15
  84. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +3 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +3 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +785 -518
  91. package/dist/esm/utils/builtInFormatters.js +53 -1
  92. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  93. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  94. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  95. package/dist/nextcloud-vue.cjs.js +2688 -2154
  96. package/dist/nextcloud-vue.cjs.js.map +2 -2
  97. package/dist/nextcloud-vue.css +785 -518
  98. package/l10n/en.json +3 -1
  99. package/l10n/nl.json +3 -1
  100. package/package.json +1 -1
  101. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +24 -7
  102. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +13 -1
  103. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +29 -2
  104. package/src/components/CnDataTable/CnDataTable.vue +34 -1
  105. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  106. package/src/components/CnDetailPage/CnDetailPage.vue +73 -10
  107. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +6 -1
  108. package/src/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue +1 -0
  109. package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +74 -10
  110. package/src/components/CnFactsWidget/CnFactsWidget.vue +41 -4
  111. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +131 -3
  112. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +103 -5
  113. package/src/components/CnFollowingWidgetHost/CnFollowingWidgetHost.vue +3 -1
  114. package/src/components/CnNotesCard/CnNotesCard.vue +10 -0
  115. package/src/components/CnObjectCard/CnObjectCard.vue +16 -0
  116. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  117. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +8 -0
  118. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +1 -1
  119. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +19 -19
  120. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +4 -0
  121. package/src/components/CnStepper/CnStepper.vue +10 -2
  122. package/src/css/dialog.css +22 -2
  123. package/src/css/look-board-detail.css +52 -3
  124. package/src/css/look-board-index.css +51 -1
  125. package/src/css/look-board.css +48 -5
  126. package/src/css/table.css +5 -2
  127. package/src/dialogs/CnAccessPurposeDialog.vue +27 -12
  128. package/src/schemas/app-manifest-v2.schema.json +4 -2
  129. package/src/utils/builtInFormatters.js +52 -0
  130. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -61,7 +61,8 @@
61
61
  :data-testid="`cn-record-list-sort-${col.key}`"
62
62
  @click="toggleTableSort(col.key)">
63
63
  {{ col.label }}
64
- <span class="cn-record-list__sort-mark" aria-hidden="true">{{ sortMarkOf(col.key) }}</span>
64
+ <ChevronUp v-if="sortStateOf(col.key) === 'ascending'" class="cn-record-list__sort-mark" :size="14" />
65
+ <ChevronDown v-else class="cn-record-list__sort-mark" :size="14" />
65
66
  </button>
66
67
  <template v-else>
67
68
  {{ col.label }}
@@ -575,7 +576,9 @@ import { generateUrl } from '@nextcloud/router'
575
576
  import { NcActionButton, NcActionCheckbox, NcActionLink, NcActions, NcButton, NcLoadingIcon } from '@nextcloud/vue'
576
577
  import { inject, ref } from 'vue'
577
578
  import Check from 'vue-material-design-icons/Check.vue'
579
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
578
580
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
581
+ import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
579
582
  import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
580
583
  import CnIcon from '../CnIcon/CnIcon.vue'
581
584
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
@@ -672,7 +675,7 @@ function unwrap(value) {
672
675
  export default {
673
676
  name: 'CnRecordListWidget',
674
677
 
675
- components: { Check, ChevronRight, CnIcon, CnRecordNoteRows, CnStatusBadge, FilterOutline, NcActionButton, NcActionCheckbox, NcActionLink, NcActions, NcButton, NcLoadingIcon },
678
+ components: { Check, ChevronDown, ChevronRight, ChevronUp, CnIcon, CnRecordNoteRows, CnStatusBadge, FilterOutline, NcActionButton, NcActionCheckbox, NcActionLink, NcActions, NcButton, NcLoadingIcon },
676
679
 
677
680
  inject: {
678
681
  /** Detail-page object context (`{ objectId, object, register, schema }`). */
@@ -2269,20 +2272,6 @@ export default {
2269
2272
  return this.tableSort.key === key && this.tableSort.dir ? this.tableSort.dir : 'none'
2270
2273
  },
2271
2274
 
2272
- /**
2273
- * The arrow after a sortable column's label.
2274
- *
2275
- * @param {string} key The column key.
2276
- * @return {string} An up or down arrow, or '' while the column is not the sort.
2277
- */
2278
- sortMarkOf(key) {
2279
- const state = this.sortStateOf(key)
2280
- if (state === 'ascending') {
2281
- return '▲'
2282
- }
2283
- return state === 'descending' ? '▼' : ''
2284
- },
2285
-
2286
2275
  /**
2287
2276
  * Sort the `table` by a column: ascending first, then descending.
2288
2277
  *
@@ -3312,7 +3301,7 @@ export default {
3312
3301
  }
3313
3302
 
3314
3303
  .cn-record-list__sort-mark {
3315
- font-size: 10px;
3304
+ color: var(--color-text-maxcontrast);
3316
3305
  }
3317
3306
 
3318
3307
  .cn-record-list__head {
@@ -3321,8 +3310,14 @@ export default {
3321
3310
  align-items: center;
3322
3311
  }
3323
3312
 
3324
- .cn-record-list__head-filter {
3313
+ /* The funnel is a 22px control, as the boards draw it beside a head, not
3314
+ NcActions' 34px trigger that made the head row 54px. */
3315
+ .cn-record-list__head-filter :deep(.button-vue) {
3325
3316
  --button-size: 22px;
3317
+ width: 22px;
3318
+ min-width: 22px;
3319
+ height: 22px;
3320
+ min-height: 22px;
3326
3321
  }
3327
3322
 
3328
3323
  .cn-record-list__table .cn-record-list__cell--end {
@@ -3413,11 +3408,16 @@ export default {
3413
3408
  font-weight: 600;
3414
3409
  height: 34px;
3415
3410
  justify-content: center;
3416
- margin: 0;
3417
3411
  padding: 0 14px;
3418
3412
  text-decoration: none;
3419
3413
  }
3420
3414
 
3415
+ /* server.css gives every button `margin: 3px 3px 3px 0` at (0,2,1); the
3416
+ doubled class outranks it. */
3417
+ .cn-record-list__action.cn-record-list__action {
3418
+ margin: 0;
3419
+ }
3420
+
3421
3421
  .cn-record-list__action:hover,
3422
3422
  .cn-record-list__action:focus-visible {
3423
3423
  background: var(--color-primary-element-light-hover, var(--color-primary-element-light));
@@ -1094,6 +1094,10 @@ export default {
1094
1094
  const results = data.results || data
1095
1095
  this.registers = Array.isArray(results) ? results : []
1096
1096
  this.registersLoaded = true
1097
+ // A configuration that arrived while the registers were loading
1098
+ // skipped its schema read (the watcher waits for the registers),
1099
+ // so read the mapped registers' schemas now.
1100
+ this.mappedRegisterIds().forEach((id) => this.loadSchemasForRegister(id))
1097
1101
  } catch (error) {
1098
1102
  this.registersError = error.message || t('nextcloud-vue', 'The registers could not be loaded: {reason}', { reason: t('nextcloud-vue', 'no connection') })
1099
1103
  } finally {
@@ -154,6 +154,14 @@ export default {
154
154
  font: inherit;
155
155
  }
156
156
 
157
+ /* Nextcloud's server.css gives every plain button a 3px margin and the
158
+ clickable-area height (`button:not(.button-vue, …):not(…)`, 0,2,1), which
159
+ made a done step 40px tall around its 28px circle. Two classes outrank it. */
160
+ .cn-stepper button.cn-stepper__step {
161
+ margin: 0;
162
+ min-height: 0;
163
+ }
164
+
157
165
  button.cn-stepper__step {
158
166
  cursor: pointer;
159
167
  }
@@ -230,14 +238,14 @@ button.cn-stepper__step {
230
238
  }
231
239
 
232
240
  /* The board look: one row, 28px circles, the label beside the circle. */
233
- /* The board leaves 20px between the stepper and the step's body (DqInstellen). */
241
+ /* The board leaves 16px between the stepper and the step's body (DqInstellen). */
234
242
  .cn-stepper--board {
235
243
  flex-wrap: wrap;
236
244
  align-items: center;
237
245
  justify-content: flex-start;
238
246
  gap: 8px;
239
247
  padding: 0;
240
- margin: 0 0 20px;
248
+ margin: 0 0 16px;
241
249
  }
242
250
 
243
251
  .cn-stepper--board .cn-stepper__item {
@@ -265,8 +265,11 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
265
265
  margin-bottom: 4px;
266
266
  }
267
267
 
268
- /* Footer: 16px 24px, hairline on top, right aligned, 40px buttons, 10px gap. */
269
- .cn-dialog-board .dialog__actions {
268
+ /* Footer: 16px 24px, hairline on top, right aligned, 40px buttons, 10px gap,
269
+ flush with the dialog's bottom. NcDialog's own footer rule (6px 0 12px
270
+ margin, 12px end padding) ties a two-class selector and wins when its
271
+ stylesheet loads later, so `.dialog` is added to outrank it. */
272
+ .cn-dialog-board .dialog .dialog__actions {
270
273
  display: flex;
271
274
  flex-wrap: wrap;
272
275
  align-items: center;
@@ -592,3 +595,20 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
592
595
  max-height: calc(100% - var(--cn-dialog-top, 88px) - 24px);
593
596
  }
594
597
  }
598
+
599
+ /* The chosen option's fields start 12px under its description, the label on
600
+ the normal line, as DqVolgendeStap draws "Opschorten": the row's 12px
601
+ bottom padding plus the cell's 8px gap and a 21px label line put them
602
+ 11.6px lower (r31).
603
+ @spec openspec/changes/board-choice-card-fields-under-the-text/specs/dialog/spec.md */
604
+ .cn-dialog-board .cn-choice-cards .cn-choice-cards__cell--with-details {
605
+ gap: 12px;
606
+ }
607
+
608
+ .cn-dialog-board .cn-choice-cards .cn-choice-cards__cell--with-details .cn-choice-cards__row {
609
+ padding-bottom: 0;
610
+ }
611
+
612
+ .cn-dialog-board .cn-transition-input-fields .cn-transition-input-fields__label {
613
+ line-height: normal;
614
+ }
@@ -751,6 +751,14 @@
751
751
  padding: calc(12px - var(--cn-board-first-line-leading)) var(--cn-board-card-inline-padding) 20px;
752
752
  }
753
753
 
754
+ /* A card that opens on a record-list table starts 12px under its title: the
755
+ table's head row sets its own padding and line height, so no first line
756
+ carries the half-leading the rule above takes off (r31 DqZaaktype
757
+ "Berichten aan de aanvrager" sat 2.2px high). */
758
+ .cn-look-board .cn-detail-page .cn-widget-wrapper__content:has(> .cn-record-list > .cn-record-list__table:first-child):not(.cn-look-nextcloud *) {
759
+ padding-top: 12px;
760
+ }
761
+
754
762
  /* A history card with stacked rows (DqTaak "Historie") opens on the first
755
763
  row's hairline 6px under the title, the row's own 12px padding above its
756
764
  text, as drawn: 19px from the title to the first action.
@@ -841,6 +849,15 @@
841
849
  font-weight: 500;
842
850
  }
843
851
 
852
+ /* The files table's checkbox column is the board's 42px (12px either side
853
+ of the 18px box): DqZaakDocumenten and DqDocumentenBeheer both draw it so.
854
+ The component's 36px pushed the file icon and name 6px left (r31: name 96
855
+ vs 100; probed with the rule: icon 54 as drawn, name 102).
856
+ @spec openspec/changes/board-files-select-column-42/specs/board-files-select-column-42/spec.md#requirement-the-files-tables-checkbox-column-is-42px */
857
+ .cn-look-board .cn-files-browser__col-select:not(.cn-look-nextcloud *) {
858
+ width: 42px;
859
+ }
860
+
844
861
  /* A labelled select in a page card is the board's field, as in a board
845
862
  dialog (dialog.css): its label above the control at 14px/600 on the normal
846
863
  line (the theme's label weight token set to 600 here), the control 40px,
@@ -1584,10 +1601,10 @@
1584
1601
 
1585
1602
  /* The buttons above the groups are the card's 34px controls ("Bekijken als
1586
1603
  flow", "Taak toevoegen"), not the page's 40px ones. */
1587
- /* The buttons sit 14px under the subtitle and 14px above the groups, the
1588
- card's gap (the content's own 12px top padding plus 2px). */
1604
+ /* The buttons sit 14px above the groups, the card's gap; above them the
1605
+ content's own top padding is the space under the subtitle. */
1589
1606
  .cn-look-board .cn-grouped-cards__actions.cn-grouped-cards__actions:not(.cn-look-nextcloud *) {
1590
- margin-block: 2px 14px;
1607
+ margin-block: 0 14px;
1591
1608
  }
1592
1609
 
1593
1610
  .cn-look-board .cn-grouped-cards__actions:not(.cn-look-nextcloud *) .button-vue {
@@ -2379,3 +2396,35 @@
2379
2396
  .cn-look-board .cn-timeline-manage__draft-label:not(.cn-look-nextcloud *) {
2380
2397
  line-height: 1.2;
2381
2398
  }
2399
+
2400
+ /* A facts card's link with an icon (Werkinstructie) sets the icon beside its
2401
+ text, 10px apart, the text on the normal line starting 10px under the
2402
+ title, as the Dq boards draw it. The anchor was a block, so the icon stood
2403
+ on its own line above the text (r31).
2404
+ @spec openspec/changes/board-facts-link-icon-beside-text/specs/facts-widget/spec.md */
2405
+ .cn-look-board .cn-facts-widget .cn-facts-widget__link:has(> .material-design-icon):not(.cn-look-nextcloud *) {
2406
+ display: flex;
2407
+ align-items: center;
2408
+ gap: 10px;
2409
+ margin-top: var(--cn-board-first-line-leading);
2410
+ line-height: normal;
2411
+ }
2412
+
2413
+ /* A table in a detail page card names each row in the link colour at 14px,
2414
+ as DqZaakGerelateerd draws "Deelzaken" and "Objecten" (r31: 15px in the
2415
+ body ink). The row opens the record, so its name reads as the link.
2416
+ @spec openspec/changes/board-detail-table-title-cells/specs/board-tables/spec.md */
2417
+ .cn-look-board .cn-detail-page .cn-object-list-widget td.cn-table-col--title:not(.cn-look-nextcloud *),
2418
+ .cn-look-board .cn-detail-page .cn-object-list-widget td.cn-table-col--title .cn-cell-renderer:not(.cn-look-nextcloud *) {
2419
+ font-size: 14px;
2420
+ line-height: 1.2;
2421
+ color: var(--color-primary-element);
2422
+ }
2423
+
2424
+ /* A data card keeps its 14px under a subtitle too (DqZaakBetrokkenen
2425
+ "Zetels"): the subtitle rule's 12px won over the data card's 14px, so the
2426
+ labels sat 2.2px high (r31).
2427
+ @spec openspec/changes/board-data-card-under-subtitle/specs/board-card/spec.md */
2428
+ .cn-look-board .cn-detail-page .cn-widget-wrapper:not(.cn-detail-page__side *):has(> .cn-widget-wrapper__header .cn-widget-wrapper__subtitle) > .cn-widget-wrapper__content:has(> .cn-object-data-widget__grid-wrap) {
2429
+ padding-top: 14px;
2430
+ }
@@ -1079,8 +1079,9 @@
1079
1079
  @spec openspec/changes/board-avatar-chip/specs/data-table/spec.md#requirement-a-board-avatar-cell-draws-the-initials-chip */
1080
1080
  .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar-initials--neutral {
1081
1081
  background: var(--color-background-dark);
1082
- color: var(--color-main-text);
1082
+ color: var(--cn-board-text-soft);
1083
1083
  font-size: 13px;
1084
+ line-height: normal;
1084
1085
  }
1085
1086
 
1086
1087
  .cn-look-board .cn-table-container--board .cn-data-table .cn-cell-renderer__avatar:has(.cn-cell-renderer__avatar-initials--neutral) {
@@ -1703,9 +1704,19 @@
1703
1704
  --app-sidebar-width: 360px;
1704
1705
  }
1705
1706
 
1707
+ /* The subtitle 2px under the title at a normal line height, and the tab's
1708
+ content 20px in from the panel edge (the tab's 10px and 10px here), as
1709
+ DqFilterBeheren draws the side panel.
1710
+ @spec openspec/changes/board-side-panel-rhythm/specs/board-side-panel-rhythm/spec.md */
1706
1711
  .cn-look-board:has(.cn-index-page) > .app-sidebar .app-sidebar-header__subname {
1712
+ margin-top: 2px;
1707
1713
  color: var(--color-text-maxcontrast);
1708
1714
  font-size: 14px;
1715
+ line-height: normal;
1716
+ }
1717
+
1718
+ .cn-look-board:has(.cn-index-page) > .app-sidebar .cn-index-sidebar__section {
1719
+ padding: 10px;
1709
1720
  }
1710
1721
 
1711
1722
  .cn-look-board:has(.cn-index-page) > .app-sidebar .app-sidebar__close {
@@ -1924,9 +1935,33 @@
1924
1935
  @spec openspec/changes/board-index-sidebar-title-inset/specs/board-index-sidebar-title-inset/spec.md#requirement-the-index-side-panel-title-is-inset-as-the-board-draws-it */
1925
1936
  .cn-look-board .cn-index-sidebar-panel .app-sidebar-header.app-sidebar-header .app-sidebar-header__info .app-sidebar-header__desc.app-sidebar-header__desc {
1926
1937
  padding-block-start: 15px;
1938
+ padding-block-end: 0;
1927
1939
  padding-inline-start: 20px;
1928
1940
  }
1929
1941
 
1942
+ /* The line under the title is a paragraph the theme sets with `!important`
1943
+ (15px, its own leading): 14px muted at its normal leading, 2px under the
1944
+ title, so the tab row starts 14px under it and the first field 20px under
1945
+ the tabs (DqZakenSorteren, DqFilterBeheren: title 20, line 46, tabs 77,
1946
+ search field 137).
1947
+ @spec openspec/changes/board-side-panel-header-rhythm/specs/board-side-panel-header-rhythm/spec.md#requirement-the-side-panel-header-and-first-field-sit-as-the-boards-draw-them */
1948
+ .cn-look-board .cn-index-sidebar-panel .app-sidebar-header__subname {
1949
+ --nldesign-component-paragraph-font-size: 14px;
1950
+ --nldesign-component-paragraph-line-height: normal;
1951
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
1952
+ font-size: 14px !important;
1953
+ line-height: normal !important;
1954
+ margin-block-start: -3px !important;
1955
+ }
1956
+
1957
+ .cn-look-board .cn-index-sidebar-panel .cn-index-sidebar__tab-content > .cn-index-sidebar__section:first-child {
1958
+ padding-top: 8px;
1959
+ }
1960
+
1961
+ .cn-look-board .cn-index-sidebar-panel .cn-index-sidebar__tab-content > .cn-index-sidebar__section:first-child > .input-field {
1962
+ margin-top: 0;
1963
+ }
1964
+
1930
1965
  .cn-look-board .cn-index-sidebar-panel .app-sidebar-tabs__nav[role="tablist"] {
1931
1966
  justify-content: flex-start;
1932
1967
  gap: 4px;
@@ -2119,6 +2154,7 @@
2119
2154
  .cn-look-board .cn-saved-views-panel:not(.cn-look-nextcloud *) .cn-saved-views-panel__name-text {
2120
2155
  font-size: 14px;
2121
2156
  font-weight: 600;
2157
+ line-height: normal;
2122
2158
  }
2123
2159
 
2124
2160
  /* A view's count: a grey pill at the end of its row (DqFilterBeheren). */
@@ -2363,6 +2399,20 @@
2363
2399
  padding-inline-start: 12px;
2364
2400
  }
2365
2401
 
2402
+ /* The boards draw the leading icon inside the title cell, so the title's
2403
+ heading starts over the icon (DqOrganisaties "Naam" at 76, the icon's x),
2404
+ as an avatar shares its column's heading (DqContacten). The blank icon
2405
+ heading stays for the column count; the title heading reaches back over it.
2406
+ @spec openspec/changes/board-head-over-row-icon/specs/data-table/spec.md#requirement-a-board-title-heading-starts-over-the-row-icon */
2407
+ .cn-look-board .cn-table-container--board .cn-data-table th.cn-table-col--icon + th:not(.cn-look-nextcloud *) {
2408
+ padding-inline-start: 0;
2409
+ overflow: visible;
2410
+ }
2411
+
2412
+ .cn-look-board .cn-table-container--board .cn-data-table th.cn-table-col--icon + th:not(.cn-look-nextcloud *) > :first-child {
2413
+ margin-inline-start: -32px;
2414
+ }
2415
+
2366
2416
  /* ---- The table beside the open side panel (DqZakenSorteren, DqFilterBeheren) */
2367
2417
  /* The table takes its column's width: its configured column widths make way
2368
2418
  for the content, titles and names wrap (a long word hyphenated, an
@@ -942,14 +942,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
942
942
  }
943
943
 
944
944
  /* A saved view's manage menu (DqFilterBeheren, DqFilterInMenu "Beheer
945
- <view>"): 264px wide, rows of at least 40px, 6px 10px in, 8px rounded,
945
+ <view>"): 278px wide outside (the board's 264px of content, 6px in and a
946
+ 1px line), rows of at least 40px, 6px 10px in, 8px rounded,
946
947
  the icon 12px before a 14px label at 600 with its explanation under it in
947
948
  12px muted text at 400; the separator in the soft line, and Delete in the
948
949
  error red. Its rows carry the panel's `cn-saved-views-` test ids.
949
950
  @spec openspec/changes/board-saved-view-manage-menu/specs/board-saved-view-manage-menu/spec.md#requirement-a-saved-views-manage-menu-is-the-boards-menu */
950
951
  body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-item__popper:has(li[data-testid^="cn-saved-views-"]) .v-popper__wrapper .v-popper__inner {
951
952
  box-sizing: border-box;
952
- width: 264px;
953
+ width: 278px;
953
954
  max-width: calc(100vw - 32px);
954
955
  }
955
956
 
@@ -1520,8 +1521,8 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1520
1521
  text-align: start;
1521
1522
  }
1522
1523
 
1523
- /* Body rows are 42px as on the boards (10px, a 21px line, 10px, the rule).
1524
- A row header (dossiq's prerequisites) reads as a strong cell, not as a
1524
+ /* Body cells read 14px at 1.2, as the boards' settings tables do; a row's
1525
+ height comes from its content (a pill or a 34px button). A row header (dossiq's prerequisites) reads as a strong cell, not as a
1525
1526
  head: the theme paints every th, so its tokens are handed the cell's. */
1526
1527
  .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) td,
1527
1528
  .cn-look-board .settings-section table:not(.cn-data-table):not(.cn-look-nextcloud *) tbody th {
@@ -1529,7 +1530,7 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1529
1530
  border-bottom: 1px solid var(--color-background-dark);
1530
1531
  color: var(--color-main-text);
1531
1532
  font-size: 14px;
1532
- line-height: 1.5;
1533
+ line-height: 1.2;
1533
1534
  vertical-align: middle;
1534
1535
  }
1535
1536
 
@@ -1660,6 +1661,38 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
1660
1661
  min-width: 0;
1661
1662
  }
1662
1663
 
1664
+ /* A settings card's first line sits where the boards draw it: an
1665
+ "Uitleg tonen" link, a switch or a note adds no height beyond its text, so
1666
+ the first text is 16px under the subtitle (DqBeheerinstellingen
1667
+ "Zaak-e-mail" and "AI-ondersteunde verwerking", DqBeheerGeavanceerd "KCC"
1668
+ and "Storeregister": 7px and 29px lower live). The controls keep their full
1669
+ hit area; the negative margin only takes it out of the line.
1670
+ @spec openspec/changes/board-settings-first-line/specs/settings-board-look/spec.md */
1671
+ .cn-look-board .cn-settings-fields__hints-toggle:not(.cn-look-nextcloud *) {
1672
+ margin-block: -8px;
1673
+ }
1674
+
1675
+ .cn-look-board .cn-settings-field--switch:not(.cn-look-nextcloud *) > .checkbox-radio-switch {
1676
+ margin-block: -8px;
1677
+ }
1678
+
1679
+ .cn-look-board .cn-settings-field--status:not(.cn-look-nextcloud *) > .notecard {
1680
+ margin: 0;
1681
+ }
1682
+
1683
+ /* A card's longer explanation reads as the boards' second paragraph: 14px
1684
+ muted at 1.5, the fields 14px under it (DqBeheerGeavanceerd "Urgentie in de
1685
+ werkvoorraad": 15px and 30px live). The theme sizes every paragraph through
1686
+ its tokens. */
1687
+ .cn-look-board .cn-settings-section__description-text:not(.cn-look-nextcloud *) {
1688
+ --nldesign-component-paragraph-font-size: 14px;
1689
+ --nldesign-component-paragraph-line-height: 1.5;
1690
+ --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
1691
+ margin: 0;
1692
+ font-size: 14px;
1693
+ line-height: 1.5;
1694
+ }
1695
+
1663
1696
  @container (max-width: 400px) {
1664
1697
  .cn-look-board .settings-section:not(.cn-look-nextcloud *) > .cn-settings-section__actions,
1665
1698
  .cn-look-board .settings-section:not(.cn-look-nextcloud *):has(> .settings-section__desc) > .cn-settings-section__actions {
@@ -2614,6 +2647,12 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
2614
2647
  font-weight: 500;
2615
2648
  }
2616
2649
 
2650
+ /* A compact card keeps its 15px/700 title on a dashboard page too (DqInspectie). */
2651
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--compact .cn-record-list__card .cn-record-list__title {
2652
+ font-weight: 700;
2653
+ line-height: 1.2;
2654
+ }
2655
+
2617
2656
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--compact .cn-record-list__main {
2618
2657
  gap: 2px;
2619
2658
  }
@@ -3220,6 +3259,8 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3220
3259
  Nextcloud's input height stretched it to 13x36. Its weight outranks the
3221
3260
  18px other native radios take.
3222
3261
  @spec openspec/changes/board-setup-choice-radio/specs/board-setup-choice-radio/spec.md#requirement-a-setup-steps-choice-radio-is-20px */
3262
+ /* The board's native radio keeps the browser's 3px end margin before the
3263
+ 12px gap, so the option title starts 56px in (DqInstellen), not 53. */
3223
3264
  .cn-look-board .cn-setup-step .cn-choice-cards input.cn-choice-cards__row-input:not(.cn-look-nextcloud *) {
3224
3265
  box-sizing: border-box;
3225
3266
  width: 20px;
@@ -3227,6 +3268,7 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
3227
3268
  height: 20px;
3228
3269
  min-height: 20px;
3229
3270
  accent-color: var(--color-primary-element);
3271
+ margin-inline-end: 3px;
3230
3272
  }
3231
3273
 
3232
3274
  /* The chosen setup choice is edged by a real 2px border, as DqInstellen
@@ -3469,4 +3511,5 @@ html :where(.cn-look-board) :is(h2, h3, h5, h6):where(.cn-inner-heading) {
3469
3511
  .cn-look-board .cn-saved-view-share__mode:not(.cn-look-nextcloud *) {
3470
3512
  border-radius: 8px;
3471
3513
  background: var(--cn-board-sunken-ground);
3514
+ line-height: normal;
3472
3515
  }
package/src/css/table.css CHANGED
@@ -616,10 +616,13 @@
616
616
  cursor: grab;
617
617
  }
618
618
 
619
- /* The handle before a reorderable row's name: a muted 16px grip 10px before it. */
619
+ /* The handle before a reorderable row's name: a muted 16px grip 10px before
620
+ it. It floats, so a name that wraps in a narrow cell wraps beside the grip
621
+ instead of dropping under it. */
620
622
  .cn-table-row__handle {
621
623
  display: inline-flex;
622
- vertical-align: middle;
624
+ float: inline-start;
625
+ margin-block-start: 1px;
623
626
  margin-inline-end: 10px;
624
627
  color: var(--color-text-maxcontrast);
625
628
  cursor: grab;
@@ -17,17 +17,23 @@
17
17
  <legend class="cn-access-purpose__legend">
18
18
  {{ t('nextcloud-vue', 'Purpose') }}
19
19
  </legend>
20
- <NcCheckboxRadioSwitch
21
- v-for="option in choices"
22
- :key="option.value"
23
- v-model="purpose"
24
- type="radio"
25
- name="cn-access-purpose"
26
- :value="option.value"
27
- :disabled="loading"
28
- :data-testid="`cn-access-purpose-option-${option.value}`">
29
- {{ option.label }}
30
- </NcCheckboxRadioSwitch>
20
+ <template v-for="option in choices" :key="option.value">
21
+ <NcCheckboxRadioSwitch
22
+ v-model="purpose"
23
+ type="radio"
24
+ name="cn-access-purpose"
25
+ :value="option.value"
26
+ :disabled="loading"
27
+ :data-testid="`cn-access-purpose-option-${option.value}`">
28
+ {{ option.label }}
29
+ </NcCheckboxRadioSwitch>
30
+ <!-- What choosing this purpose does besides recording it ("you become this case's handler"). -->
31
+ <p v-if="option.hint"
32
+ class="cn-access-purpose__hint"
33
+ :data-testid="`cn-access-purpose-hint-${option.value}`">
34
+ {{ option.hint }}
35
+ </p>
36
+ </template>
31
37
  </fieldset>
32
38
  <NcTextArea
33
39
  v-if="purpose === OTHER"
@@ -94,8 +100,10 @@ export default {
94
100
 
95
101
  /**
96
102
  * The purposes offered, already translated. "Other" is added at the end.
103
+ * A `hint` is a line under the option that says what choosing it does
104
+ * besides recording it.
97
105
  *
98
- * @type {Array<{value: string, label: string}>}
106
+ * @type {Array<{value: string, label: string, hint?: string}>}
99
107
  */
100
108
  options: {
101
109
  type: Array,
@@ -176,6 +184,13 @@ export default {
176
184
  border: 0;
177
185
  }
178
186
 
187
+ .cn-access-purpose__hint {
188
+ margin: 0 0 6px;
189
+ padding-inline-start: 36px;
190
+ color: var(--color-text-maxcontrast);
191
+ font-size: 13px;
192
+ }
193
+
179
194
  .cn-access-purpose__legend {
180
195
  margin-bottom: 4px;
181
196
  font-weight: 600;
@@ -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.156.0",
6
+ "version": "2.157.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -5811,11 +5811,13 @@
5811
5811
  "additionalProperties": false,
5812
5812
  "properties": {
5813
5813
  "value": { "type": "string", "minLength": 1 },
5814
- "label": { "type": "string", "minLength": 1, "description": "i18n key." }
5814
+ "label": { "type": "string", "minLength": 1, "description": "i18n key." },
5815
+ "hint": { "type": "string", "description": "i18n key of a line under the option: what choosing it does besides recording it." }
5815
5816
  }
5816
5817
  }
5817
5818
  },
5818
5819
  "url": { "type": "string", "minLength": 1, "description": "App endpoint that records the purpose; {objectId} is filled in." },
5820
+ "checkUrl": { "type": "string", "minLength": 1, "description": "App endpoint asked first (GET, {objectId} filled in): an answer { required: false } opens the record without the dialog, because the app recorded the purpose itself." },
5819
5821
  "title": { "type": "string", "description": "i18n key of the dialog's title." },
5820
5822
  "description": { "type": "string", "description": "i18n key of the sentence under the title." }
5821
5823
  }
@@ -524,6 +524,57 @@ export function formatIsoDuration(value) {
524
524
  return words[period.unit] ? words[period.unit]() : String(value)
525
525
  }
526
526
 
527
+ /** The ISO designator of each date unit a term is written in. */
528
+ const UNIT_DESIGNATOR = { years: 'Y', months: 'M', weeks: 'W', days: 'D' }
529
+
530
+ /**
531
+ * A one-unit ISO-8601 period, a day count that is whole weeks read as weeks
532
+ * (a term of `P28D` is "4 weeks"); null when it is not a one-unit period.
533
+ *
534
+ * @param {unknown} value The stored period.
535
+ * @return {{amount: number, unit: string}|null}
536
+ */
537
+ function termPeriod(value) {
538
+ if (value === null || value === undefined || value === '') {
539
+ return null
540
+ }
541
+ const period = parseDuration(String(value))
542
+ if (period === null || !UNIT_DESIGNATOR[period.unit]) {
543
+ return null
544
+ }
545
+ if (period.unit === 'days' && period.amount > 0 && period.amount % 7 === 0) {
546
+ return { amount: period.amount / 7, unit: 'weeks' }
547
+ }
548
+ return period
549
+ }
550
+
551
+ /**
552
+ * A term in words, with the extension it may get (`format: 'duration'`):
553
+ * `P28D` reads "4 weeks", and with an extension of `P14D` "4 weeks,
554
+ * extendable by 2" (the unit is said once when both share it). A value that
555
+ * is not a one-unit period is returned as written, '' for null or empty.
556
+ *
557
+ * @spec openspec/changes/data-widget-duration-format/specs/data-widget/spec.md#requirement-a-term-reads-in-words-with-its-extension
558
+ * @param {unknown} value The stored term.
559
+ * @param {unknown} [extension] The stored extension period, if any.
560
+ * @return {string}
561
+ */
562
+ export function formatTermDuration(value, extension = null) {
563
+ const term = termPeriod(value)
564
+ if (term === null) {
565
+ return value === null || value === undefined ? '' : String(value)
566
+ }
567
+ const words = formatIsoDuration(`P${term.amount}${UNIT_DESIGNATOR[term.unit]}`)
568
+ const extra = termPeriod(extension)
569
+ if (extra === null || extra.amount === 0) {
570
+ return words
571
+ }
572
+ const extraWords = extra.unit === term.unit
573
+ ? String(extra.amount)
574
+ : formatIsoDuration(`P${extra.amount}${UNIT_DESIGNATOR[extra.unit]}`)
575
+ return t('nextcloud-vue', '{term}, extendable by {extension}', { term: words, extension: extraWords })
576
+ }
577
+
527
578
  /**
528
579
  * A Nextcloud user id as its display name (`userName` registry key): the id
529
580
  * while the name is looked up, and for a user nobody answers for.
@@ -553,6 +604,7 @@ export const BUILT_IN_FORMATTERS = {
553
604
  connectionSettingsLabel: formatConnectionSettingsLabel,
554
605
  masked: formatMasked,
555
606
  isoDuration: formatIsoDuration,
607
+ termDuration: formatTermDuration,
556
608
  valueMap: formatValueMap,
557
609
  userName: formatUserName,
558
610
  }