@conduction/nextcloud-vue 2.84.0 → 2.85.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 (166) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  7. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  8. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  9. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
  13. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
  15. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -0
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  25. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  26. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  29. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  30. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  37. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  39. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  42. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  43. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  44. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
  45. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  47. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  48. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  49. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  50. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  51. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  52. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  53. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  54. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  56. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  57. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
  58. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  60. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  62. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
  63. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  64. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  66. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  67. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  70. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  71. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  72. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  73. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  74. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  75. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  76. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  78. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  79. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  80. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  81. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  82. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  83. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  84. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  85. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  86. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  87. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  88. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  89. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  90. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  91. package/dist/esm/composables/useListView.js +21 -0
  92. package/dist/esm/composables/useListView.js.map +1 -1
  93. package/dist/esm/index.js +1 -0
  94. package/dist/esm/index.js.map +1 -1
  95. package/dist/esm/l10n/en.json.js +23 -1
  96. package/dist/esm/l10n/en.json.js.map +1 -1
  97. package/dist/esm/l10n/nl.json.js +41 -1
  98. package/dist/esm/l10n/nl.json.js.map +1 -1
  99. package/dist/esm/library-style.js +1 -1
  100. package/dist/esm/nextcloud-vue.css +742 -397
  101. package/dist/esm/store/useObjectStore.js +16 -2
  102. package/dist/esm/store/useObjectStore.js.map +1 -1
  103. package/dist/esm/utils/boardColumns.js +8 -1
  104. package/dist/esm/utils/boardColumns.js.map +1 -1
  105. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  106. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  107. package/dist/esm/utils/buildManifest.js +4 -1
  108. package/dist/esm/utils/buildManifest.js.map +1 -1
  109. package/dist/esm/utils/gridStack.js +40 -1
  110. package/dist/esm/utils/gridStack.js.map +1 -1
  111. package/dist/esm/utils/humaniseKey.js +41 -0
  112. package/dist/esm/utils/humaniseKey.js.map +1 -0
  113. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  114. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/esm/utils/widgetDispatch.js +8 -2
  118. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  119. package/dist/nextcloud-vue.cjs.js +15075 -12590
  120. package/dist/nextcloud-vue.cjs.js.map +2 -2
  121. package/dist/nextcloud-vue.css +742 -397
  122. package/l10n/en.json +23 -1
  123. package/l10n/nl.json +41 -1
  124. package/package.json +1 -1
  125. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  126. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  127. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
  128. package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
  129. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  130. package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
  131. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  132. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  133. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  134. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  135. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
  136. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
  137. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  138. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  139. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  140. package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
  141. package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
  142. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  143. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  144. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  145. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  146. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  147. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  148. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  149. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  150. package/src/composables/useListView.js +21 -0
  151. package/src/css/board.css +65 -4
  152. package/src/css/dialog.css +4 -2
  153. package/src/css/index-page.css +49 -0
  154. package/src/css/kpi-card.css +13 -0
  155. package/src/css/look-board.css +96 -7
  156. package/src/index.js +2 -0
  157. package/src/schemas/app-manifest-v2.schema.json +115 -5
  158. package/src/store/useObjectStore.js +16 -2
  159. package/src/utils/boardColumns.js +8 -1
  160. package/src/utils/boardReferenceColumns.js +274 -0
  161. package/src/utils/buildManifest.js +4 -1
  162. package/src/utils/gridStack.js +39 -0
  163. package/src/utils/humaniseKey.js +38 -0
  164. package/src/utils/notificationRuleLabel.js +5 -23
  165. package/src/utils/validateManifestV2.compiled.js +3 -3
  166. package/src/utils/widgetDispatch.js +8 -2
@@ -4,7 +4,7 @@
4
4
  :description="description"
5
5
  :docUrl="docUrl"
6
6
  :loading="registersLoading"
7
- loadingMessage="Loading registers..."
7
+ :loadingMessage="t('nextcloud-vue', 'Loading registers…')"
8
8
  :error="!!registersError"
9
9
  :errorMessage="registersError || ''"
10
10
  :onRetry="loadRegisters">
@@ -19,7 +19,7 @@
19
19
  <NcLoadingIcon v-if="saving" :size="20" />
20
20
  <ContentSave v-else :size="20" />
21
21
  </template>
22
- {{ saving ? 'Saving...' : saveButtonText }}
22
+ {{ saving ? t('nextcloud-vue', 'Saving…') : saveButtonText }}
23
23
  </NcButton>
24
24
  <NcButton
25
25
  v-if="showReimportButton"
@@ -30,7 +30,7 @@
30
30
  <NcLoadingIcon v-if="reimporting" :size="20" />
31
31
  <Refresh v-else :size="20" />
32
32
  </template>
33
- {{ reimporting ? 'Importing...' : reimportButtonText }}
33
+ {{ reimporting ? t('nextcloud-vue', 'Importing…') : reimportButtonText }}
34
34
  </NcButton>
35
35
  <slot name="actions" />
36
36
  </template>
@@ -40,9 +40,39 @@
40
40
  <div
41
41
  v-for="(group, groupIdx) in groups"
42
42
  :key="groupIdx"
43
- class="cn-register-mapping__group">
44
- <!-- Group header -->
45
- <slot name="group-header"
43
+ class="cn-register-mapping__group"
44
+ :class="{ 'cn-register-mapping__group--collapsed': !isGroupOpen(groupIdx) }"
45
+ data-testid="cn-register-mapping-group">
46
+ <!-- Group header. With collapsible groups a heading holds the button
47
+ that opens and closes the group (the WAI-ARIA accordion); the
48
+ count stays visible while it is closed. -->
49
+ <h4 v-if="groupsCollapsible" class="cn-register-mapping__group-heading">
50
+ <button
51
+ type="button"
52
+ class="cn-register-mapping__group-toggle"
53
+ :aria-expanded="String(isGroupOpen(groupIdx))"
54
+ :aria-controls="groupBodyId(groupIdx)"
55
+ data-testid="cn-register-mapping-group-toggle"
56
+ @click="toggleGroup(groupIdx)">
57
+ <span class="cn-register-mapping__group-chevron" aria-hidden="true">
58
+ <ChevronUp v-if="isGroupOpen(groupIdx)" :size="20" />
59
+ <ChevronDown v-else :size="20" />
60
+ </span>
61
+ <slot name="group-header"
62
+ :group="group"
63
+ :configuredCount="configuredCount(groupIdx)"
64
+ :totalCount="group.types.length">
65
+ <span class="cn-register-mapping__group-header">
66
+ <span class="cn-register-mapping__group-title">{{ group.name }}</span>
67
+ <span class="cn-register-mapping__group-status" data-testid="cn-register-mapping-group-count">
68
+ {{ configuredCount(groupIdx) }}/{{ group.types.length }} {{ labels.partiallyConfigured }}
69
+ </span>
70
+ </span>
71
+ </slot>
72
+ </button>
73
+ </h4>
74
+ <slot v-else
75
+ name="group-header"
46
76
  :group="group"
47
77
  :configuredCount="configuredCount(groupIdx)"
48
78
  :totalCount="group.types.length">
@@ -56,90 +86,95 @@
56
86
  </div>
57
87
  </slot>
58
88
 
59
- <!-- Group description -->
60
- <p v-if="group.description" class="cn-register-mapping__group-description">
61
- {{ group.description }}
62
- </p>
63
-
64
- <!-- Register selector -->
65
- <div class="cn-register-mapping__register-select">
66
- <label class="cn-register-mapping__label">{{ labels.register }}</label>
67
- <NcSelect
68
- :modelValue="selectedRegister(groupIdx)"
69
- :options="registerSelectOptions"
70
- :placeholder="labels.selectRegister"
71
- :inputLabel="labels.register"
72
- :loading="registersLoading"
73
- label="label"
74
- trackBy="value"
75
- @update:modelValue="handleRegisterChange(groupIdx, $event)" />
76
- </div>
77
-
78
- <!-- Type list -->
79
- <div v-if="selectedRegister(groupIdx)" class="cn-register-mapping__type-list">
80
- <!-- Header row -->
81
- <div class="cn-register-mapping__type-list-header">
82
- <span>Name</span>
83
- <span>Schema</span>
84
- <span />
85
- <span />
89
+ <div
90
+ v-show="isGroupOpen(groupIdx)"
91
+ :id="groupBodyId(groupIdx)"
92
+ class="cn-register-mapping__group-body"
93
+ data-testid="cn-register-mapping-group-body">
94
+ <!-- Group description -->
95
+ <p v-if="group.description" class="cn-register-mapping__group-description">
96
+ {{ group.description }}
97
+ </p>
98
+
99
+ <!-- Register selector -->
100
+ <div class="cn-register-mapping__register-select">
101
+ <NcSelect
102
+ :modelValue="selectedRegister(groupIdx)"
103
+ :options="registerSelectOptions"
104
+ :placeholder="labels.selectRegister"
105
+ :inputLabel="labels.register"
106
+ :loading="registersLoading"
107
+ label="label"
108
+ trackBy="value"
109
+ @update:modelValue="handleRegisterChange(groupIdx, $event)" />
86
110
  </div>
87
111
 
88
- <!-- Type rows -->
89
- <template v-for="type in group.types" :key="type.slug + '-row'">
90
- <div
91
- class="cn-register-mapping__type-row"
92
- :class="{ 'cn-register-mapping__type-row--expanded': isExpanded(groupIdx, type.slug) }"
93
- @click="toggleExpand(groupIdx, type.slug)">
94
- <span class="cn-register-mapping__type-name">{{ type.label }}</span>
95
- <span class="cn-register-mapping__type-schema">
96
- {{ schemaLabel(groupIdx, type) || labels.notConfigured }}
97
- </span>
98
- <span class="cn-register-mapping__type-status">
99
- <span
100
- class="cn-register-mapping__status-dot"
101
- :class="schemaValue(groupIdx, type)
102
- ? 'cn-register-mapping__status-dot--configured'
103
- : 'cn-register-mapping__status-dot--unconfigured'" />
104
- </span>
105
- <span class="cn-register-mapping__type-chevron">
106
- <ChevronUp v-if="isExpanded(groupIdx, type.slug)" :size="20" />
107
- <ChevronDown v-else :size="20" />
108
- </span>
112
+ <!-- Type list -->
113
+ <div v-if="selectedRegister(groupIdx)" class="cn-register-mapping__type-list">
114
+ <!-- Header row -->
115
+ <div class="cn-register-mapping__type-list-header">
116
+ <span>{{ t('nextcloud-vue', 'Object type') }}</span>
117
+ <span>{{ labels.schema }}</span>
118
+ <span />
119
+ <span />
109
120
  </div>
110
121
 
111
- <!-- Expanded detail -->
112
- <transition name="slide">
122
+ <!-- Type rows -->
123
+ <template v-for="type in group.types" :key="type.slug + '-row'">
113
124
  <div
114
- v-if="isExpanded(groupIdx, type.slug)"
115
- class="cn-register-mapping__type-detail">
116
- <p v-if="type.description" class="cn-register-mapping__type-description">
117
- {{ type.description }}
118
- </p>
119
- <NcSelect
120
- :modelValue="selectedSchema(groupIdx, type)"
121
- :options="schemaSelectOptions(groupIdx)"
122
- :placeholder="labels.selectSchema"
123
- :inputLabel="labels.schema"
124
- label="label"
125
- trackBy="value"
126
- @update:modelValue="handleSchemaChange(groupIdx, type, $event)" />
125
+ class="cn-register-mapping__type-row"
126
+ :class="{ 'cn-register-mapping__type-row--expanded': isExpanded(groupIdx, type.slug) }"
127
+ @click="toggleExpand(groupIdx, type.slug)">
128
+ <span class="cn-register-mapping__type-name">{{ type.label }}</span>
129
+ <span class="cn-register-mapping__type-schema">
130
+ {{ schemaLabel(groupIdx, type) || labels.notConfigured }}
131
+ </span>
132
+ <span class="cn-register-mapping__type-status">
133
+ <span
134
+ class="cn-register-mapping__status-dot"
135
+ :class="schemaValue(groupIdx, type)
136
+ ? 'cn-register-mapping__status-dot--configured'
137
+ : 'cn-register-mapping__status-dot--unconfigured'" />
138
+ </span>
139
+ <span class="cn-register-mapping__type-chevron">
140
+ <ChevronUp v-if="isExpanded(groupIdx, type.slug)" :size="20" />
141
+ <ChevronDown v-else :size="20" />
142
+ </span>
127
143
  </div>
128
- </transition>
129
- </template>
130
- </div>
131
144
 
132
- <!-- No register selected -->
133
- <NcNoteCard v-else-if="!registersLoading" type="info">
134
- {{ labels.selectRegister }}
135
- </NcNoteCard>
136
-
137
- <!-- No schemas available -->
138
- <NcNoteCard
139
- v-if="selectedRegister(groupIdx) && schemaSelectOptions(groupIdx).length === 0 && !registersLoading"
140
- variant="warning">
141
- {{ labels.noSchemas }}
142
- </NcNoteCard>
145
+ <!-- Expanded detail -->
146
+ <transition name="slide">
147
+ <div
148
+ v-if="isExpanded(groupIdx, type.slug)"
149
+ class="cn-register-mapping__type-detail">
150
+ <p v-if="type.description" class="cn-register-mapping__type-description">
151
+ {{ type.description }}
152
+ </p>
153
+ <NcSelect
154
+ :modelValue="selectedSchema(groupIdx, type)"
155
+ :options="schemaSelectOptions(groupIdx)"
156
+ :placeholder="labels.selectSchema"
157
+ :inputLabel="labels.schema"
158
+ label="label"
159
+ trackBy="value"
160
+ @update:modelValue="handleSchemaChange(groupIdx, type, $event)" />
161
+ </div>
162
+ </transition>
163
+ </template>
164
+ </div>
165
+
166
+ <!-- No register selected -->
167
+ <NcNoteCard v-else-if="!registersLoading" type="info">
168
+ {{ labels.selectRegister }}
169
+ </NcNoteCard>
170
+
171
+ <!-- No schemas available -->
172
+ <NcNoteCard
173
+ v-if="selectedRegister(groupIdx) && schemaSelectOptions(groupIdx).length === 0 && !registersLoading"
174
+ variant="warning">
175
+ {{ labels.noSchemas }}
176
+ </NcNoteCard>
177
+ </div>
143
178
  </div>
144
179
  </div>
145
180
 
@@ -160,6 +195,8 @@ import Refresh from 'vue-material-design-icons/Refresh.vue'
160
195
  import { buildHeaders, prefixUrl } from '../../utils/headers.js'
161
196
  import { CnSettingsSection } from '../CnSettingsSection/index.js'
162
197
 
198
+ let mappingCount = 0
199
+
163
200
  /**
164
201
  * CnRegisterMapping - OpenRegister register/schema configuration component.
165
202
  *
@@ -284,6 +321,20 @@ export default {
284
321
  default: () => t('nextcloud-vue', 'Re-import configuration'),
285
322
  },
286
323
 
324
+ /**
325
+ * Draw each group as a header that opens and closes it, showing its
326
+ * configured count while closed. The groups start closed. `null` (the
327
+ * default) collapses them when there is more than one group, so a page
328
+ * with several registers is not one long card.
329
+ *
330
+ * @type {boolean|null}
331
+ * @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
332
+ */
333
+ collapseGroups: {
334
+ type: Boolean,
335
+ default: null,
336
+ },
337
+
287
338
  /** Auto-match schema titles to type slugs on register change */
288
339
  autoMatch: {
289
340
  type: Boolean,
@@ -319,6 +370,9 @@ export default {
319
370
  // Local state
320
371
  localConfig: {},
321
372
  expandedRows: {},
373
+ // The groups opened by the reader, by index.
374
+ openGroups: {},
375
+ uid: ++mappingCount,
322
376
  }
323
377
  },
324
378
 
@@ -326,11 +380,21 @@ export default {
326
380
  /** Register options for NcSelect */
327
381
  registerSelectOptions() {
328
382
  return this.registers.map((r) => ({
329
- label: r.title || r.slug || `Register ${r.id}`,
383
+ label: r.title || r.slug || t('nextcloud-vue', 'Register {id}', { id: r.id }),
330
384
  value: String(r.id),
331
385
  }))
332
386
  },
333
387
 
388
+ /**
389
+ * Whether the groups open and close.
390
+ *
391
+ * @return {boolean}
392
+ * @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
393
+ */
394
+ groupsCollapsible() {
395
+ return this.collapseGroups === null ? this.groups.length > 1 : this.collapseGroups
396
+ },
397
+
334
398
  /** Whether local config differs from prop */
335
399
  hasChanges() {
336
400
  return JSON.stringify(this.localConfig) !== JSON.stringify(this.configuration)
@@ -353,6 +417,39 @@ export default {
353
417
  },
354
418
 
355
419
  methods: {
420
+ t,
421
+
422
+ /**
423
+ * Whether a group's body shows.
424
+ *
425
+ * @param {number} groupIdx Group index
426
+ * @return {boolean}
427
+ */
428
+ isGroupOpen(groupIdx) {
429
+ return !this.groupsCollapsible || this.openGroups[groupIdx] === true
430
+ },
431
+
432
+ /**
433
+ * Open a closed group, or close an open one.
434
+ *
435
+ * @param {number} groupIdx Group index
436
+ * @return {void}
437
+ * @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
438
+ */
439
+ toggleGroup(groupIdx) {
440
+ this.openGroups = { ...this.openGroups, [groupIdx]: !this.isGroupOpen(groupIdx) }
441
+ },
442
+
443
+ /**
444
+ * The id of a group's body, for the header's `aria-controls`.
445
+ *
446
+ * @param {number} groupIdx Group index
447
+ * @return {string}
448
+ */
449
+ groupBodyId(groupIdx) {
450
+ return `cn-register-mapping-${this.uid}-group-${groupIdx}`
451
+ },
452
+
356
453
  /**
357
454
  * Get the config key for a group's register.
358
455
  *
@@ -437,7 +534,7 @@ export default {
437
534
  return selected.label
438
535
  }
439
536
  const value = this.schemaValue(groupIdx, type)
440
- return value ? `Schema ${value}` : ''
537
+ return value ? t('nextcloud-vue', 'Schema {id}', { id: value }) : ''
441
538
  },
442
539
 
443
540
  /**
@@ -453,7 +550,7 @@ export default {
453
550
  }
454
551
  const schemas = this.schemasByRegister[reg.value] || []
455
552
  return schemas.map((s) => ({
456
- label: s.title || s.slug || `Schema ${s.id}`,
553
+ label: s.title || s.slug || t('nextcloud-vue', 'Schema {id}', { id: s.id }),
457
554
  value: String(s.id),
458
555
  }))
459
556
  },
@@ -591,7 +688,7 @@ export default {
591
688
  })
592
689
 
593
690
  if (!response.ok) {
594
- this.registersError = `Failed to fetch registers: ${response.statusText}`
691
+ this.registersError = t('nextcloud-vue', 'The registers could not be loaded: {reason}', { reason: response.statusText || response.status })
595
692
  return
596
693
  }
597
694
 
@@ -612,7 +709,7 @@ export default {
612
709
  }
613
710
  }
614
711
  } catch (error) {
615
- this.registersError = error.message || 'Network error fetching registers'
712
+ this.registersError = error.message || t('nextcloud-vue', 'The registers could not be loaded: {reason}', { reason: t('nextcloud-vue', 'no connection') })
616
713
  } finally {
617
714
  this.registersLoading = false
618
715
  }
@@ -671,6 +768,52 @@ export default {
671
768
  margin-bottom: 12px;
672
769
  }
673
770
 
771
+ .cn-register-mapping__group-heading {
772
+ margin: 0 0 12px;
773
+ font: inherit;
774
+ }
775
+
776
+ .cn-register-mapping__group--collapsed .cn-register-mapping__group-heading {
777
+ margin-bottom: 0;
778
+ }
779
+
780
+ .cn-register-mapping__group-toggle {
781
+ box-sizing: border-box;
782
+ display: flex;
783
+ align-items: center;
784
+ gap: 8px;
785
+ width: 100%;
786
+ min-height: 44px;
787
+ margin: 0;
788
+ padding: 0;
789
+ border: 0;
790
+ background: transparent;
791
+ color: inherit;
792
+ font: inherit;
793
+ text-align: start;
794
+ cursor: pointer;
795
+ }
796
+
797
+ .cn-register-mapping__group-toggle:focus-visible {
798
+ outline: 2px solid var(--color-primary-element);
799
+ outline-offset: 2px;
800
+ border-radius: var(--border-radius);
801
+ }
802
+
803
+ .cn-register-mapping__group-chevron {
804
+ display: inline-flex;
805
+ flex: none;
806
+ color: var(--color-text-maxcontrast);
807
+ }
808
+
809
+ .cn-register-mapping__group-toggle .cn-register-mapping__group-header {
810
+ flex: 1 1 auto;
811
+ flex-wrap: wrap;
812
+ gap: 4px 12px;
813
+ min-width: 0;
814
+ margin-bottom: 0;
815
+ }
816
+
674
817
  .cn-register-mapping__group-title {
675
818
  font-size: 16px;
676
819
  font-weight: 600;
@@ -689,14 +832,6 @@ export default {
689
832
  margin: 0 0 12px 0;
690
833
  }
691
834
 
692
- .cn-register-mapping__label {
693
- display: block;
694
- font-weight: 500;
695
- font-size: 13px;
696
- color: var(--color-text-maxcontrast);
697
- margin-bottom: 4px;
698
- }
699
-
700
835
  .cn-register-mapping__register-select {
701
836
  margin-bottom: 16px;
702
837
  max-width: 400px;
@@ -303,6 +303,7 @@ import {
303
303
  import ContentSave from 'vue-material-design-icons/ContentSave.vue'
304
304
  import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
305
305
  import CnRegisterMapping from '../CnRegisterMapping/CnRegisterMapping.vue'
306
+ import CnToggleTableWidget from '../CnToggleTableWidget/CnToggleTableWidget.vue'
306
307
  import CnVersionInfoCard from '../CnVersionInfoCard/CnVersionInfoCard.vue'
307
308
  import { useLook } from '../../composables/useLook.js'
308
309
  import { prefixUrl } from '../../utils/headers.js'
@@ -340,6 +341,8 @@ const COMPONENT_DISCRIMINATOR = Symbol('cn-settings-component-widget')
340
341
  const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
341
342
  'version-info': CnVersionInfoCard,
342
343
  'register-mapping': CnRegisterMapping,
344
+ // Rows with a switch: objects (`bind.mode: 'each'`) or a config map (`'map'`).
345
+ 'toggle-table': CnToggleTableWidget,
343
346
  component: COMPONENT_DISCRIMINATOR,
344
347
  })
345
348
 
@@ -367,7 +370,8 @@ const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
367
370
  * - `widgets[]` — ordered list of widgets. Each entry has a `type`
368
371
  * string and optional `props`. Built-in widget types resolve
369
372
  * first (`version-info` → `CnVersionInfoCard`, `register-mapping`
370
- * → `CnRegisterMapping`); unknown types fall back to the
373
+ * → `CnRegisterMapping`, `toggle-table` → `CnToggleTableWidget`);
374
+ * unknown types fall back to the
371
375
  * `customComponents` registry.
372
376
  *
373
377
  * Widget events (e.g. CnRegisterMapping's `@save`, CnVersionInfoCard's