@conduction/nextcloud-vue 2.103.0 → 2.105.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.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +23 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +94 -72
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +18 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -3
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +39 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +14 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +99 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +213 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +105 -85
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +10 -4
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +57 -5
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +37 -4
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +18 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +7 -7
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +8 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +8 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +680 -328
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/nextcloudApps.js +45 -0
- package/dist/esm/utils/nextcloudApps.js.map +1 -0
- package/dist/esm/utils/nextcloudDirectory.js +18 -1
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
- package/dist/esm/utils/scopeListLayout.js +9 -2
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +61 -0
- package/dist/esm/utils/sortOrder.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +12 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +4046 -2999
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +680 -328
- package/l10n/en.json +8 -1
- package/l10n/nl.json +8 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +39 -2
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
- package/src/components/CnAppRoot/CnAppRoot.md +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +21 -4
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +49 -0
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +99 -3
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
- package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
- package/src/components/CnIndexPage/CnIndexPage.md +5 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +228 -9
- package/src/components/CnIndexPage/useSelfFetchList.js +9 -3
- package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
- package/src/components/CnNoticeWidget/index.js +12 -0
- package/src/components/CnObjectRow/CnObjectRow.vue +95 -1
- package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +7 -2
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +18 -2
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +7 -7
- package/src/css/dialog.css +20 -2
- package/src/css/index-page.css +19 -0
- package/src/css/look-board-detail.css +77 -4
- package/src/css/look-board-index.css +84 -2
- package/src/css/look-board.css +97 -12
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +58 -3
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/nextcloudApps.js +50 -0
- package/src/utils/nextcloudDirectory.js +17 -0
- package/src/utils/scopeListLayout.js +9 -2
- package/src/utils/sortOrder.js +58 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +12 -4
|
@@ -163,8 +163,15 @@
|
|
|
163
163
|
gap: 8px;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/* A chip is a <button>: the nldesign theme sets every button's radius and
|
|
167
|
+
weight with `!important` through its button tokens, which drew the chips
|
|
168
|
+
as 8px boxes at 700 (DqTeamwachtrij), so the chip hands its pill radius and
|
|
169
|
+
600 to those tokens.
|
|
170
|
+
@spec openspec/changes/board-chips-theme/specs/board-chips-theme/spec.md#requirement-board-chips-stay-pills-under-a-theme */
|
|
166
171
|
.cn-look-board .cn-saved-views__chips .cn-saved-views__chip,
|
|
167
172
|
.cn-look-board .cn-quick-filter-bar .cn-quick-filter-bar__tab {
|
|
173
|
+
--nldesign-component-button-border-radius: 19px;
|
|
174
|
+
--nldesign-component-button-font-weight: 600;
|
|
168
175
|
display: inline-flex;
|
|
169
176
|
align-items: center;
|
|
170
177
|
gap: 8px;
|
|
@@ -267,12 +274,30 @@
|
|
|
267
274
|
padding: 0;
|
|
268
275
|
}
|
|
269
276
|
|
|
270
|
-
/*
|
|
277
|
+
/* The toolbar's buttons are the board's neutral control (DqTeamwachtrij
|
|
278
|
+
"Filter"): the main text at 600 on the main background with a grey border.
|
|
279
|
+
The nldesign theme paints a secondary button in the primary colour with
|
|
280
|
+
`!important` through its button tokens, so the bar hands the board's values
|
|
281
|
+
to those tokens.
|
|
282
|
+
@spec openspec/changes/board-toolbar-buttons-theme/specs/board-toolbar-buttons-theme/spec.md#requirement-board-toolbar-buttons-are-neutral-under-a-theme */
|
|
283
|
+
.cn-look-board .cn-actions-bar:not(.cn-look-nextcloud *) {
|
|
284
|
+
--nldesign-component-button-font-weight: 600;
|
|
285
|
+
--nldesign-component-button-secondary-action-color: var(--color-main-text);
|
|
286
|
+
--nldesign-component-button-secondary-action-background-color: var(--color-main-background);
|
|
287
|
+
--nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
|
|
288
|
+
--nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Filter: a labelled secondary button, 36px, radius 8, 12px at both sides
|
|
292
|
+
and 14px text, with the active count. NcButton with an icon pads its start
|
|
293
|
+
4px and sets 15px. */
|
|
271
294
|
.cn-look-board .cn-actions-bar__filter-button.button-vue,
|
|
272
295
|
.cn-look-board .cn-actions-bar__filter-menu .button-vue {
|
|
273
296
|
min-height: 36px;
|
|
274
297
|
height: 36px;
|
|
298
|
+
padding: 0 12px;
|
|
275
299
|
border-radius: 8px;
|
|
300
|
+
font-size: 14px;
|
|
276
301
|
font-weight: 600;
|
|
277
302
|
}
|
|
278
303
|
|
|
@@ -372,6 +397,17 @@
|
|
|
372
397
|
cursor: not-allowed;
|
|
373
398
|
}
|
|
374
399
|
|
|
400
|
+
/* `searchPlacement: "toolbar"`: the search field on row 1 takes the room
|
|
401
|
+
before Filter and the view switch, which keep the end of the row. */
|
|
402
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-actions-bar__search--toolbar {
|
|
403
|
+
flex: 1 1 220px;
|
|
404
|
+
min-width: 180px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-actions-bar__search--toolbar ~ .cn-actions-bar__view-controls {
|
|
408
|
+
margin-inline-start: 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
375
411
|
/* The search field: 40px, radius 8, not a pill. */
|
|
376
412
|
.cn-look-board .cn-actions-bar--board .cn-actions-bar__search {
|
|
377
413
|
flex: 1 1 260px;
|
|
@@ -502,9 +538,18 @@
|
|
|
502
538
|
|
|
503
539
|
/* The header row is near-white on the boards (#fafbfc), not the hover grey,
|
|
504
540
|
its labels 14px muted. The ink also goes to --nldesign-color-on-surface,
|
|
505
|
-
which a theme that forces the body ink onto every span (thematiq) reads.
|
|
541
|
+
which a theme that forces the body ink onto every span (thematiq) reads.
|
|
542
|
+
The nldesign theme paints every `th` with `!important` through its table
|
|
543
|
+
header tokens, and the sort button in it through its button weight token
|
|
544
|
+
(bold), so the cell hands the board's ground, ink and weight to those
|
|
545
|
+
tokens too.
|
|
546
|
+
@spec openspec/changes/board-table-head-theme/specs/board-table-head-theme/spec.md#requirement-a-board-table-header-keeps-the-boards-ground-ink-and-weight-under-a-theme */
|
|
506
547
|
.cn-look-board .cn-table-container--board .cn-data-table th {
|
|
507
548
|
--nldesign-color-on-surface: var(--color-text-maxcontrast);
|
|
549
|
+
--nldesign-component-table-header-background-color: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
|
|
550
|
+
--nldesign-component-table-header-color: var(--color-text-maxcontrast);
|
|
551
|
+
--nldesign-component-table-header-font-weight: 600;
|
|
552
|
+
--nldesign-component-button-font-weight: 600;
|
|
508
553
|
padding: 12px 16px;
|
|
509
554
|
border-bottom: 0;
|
|
510
555
|
background: var(--cn-board-table-head-bg, color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background)));
|
|
@@ -1108,3 +1153,40 @@
|
|
|
1108
1153
|
.cn-look-board:has(.cn-index-page) > .app-sidebar__toggle {
|
|
1109
1154
|
display: none;
|
|
1110
1155
|
}
|
|
1156
|
+
|
|
1157
|
+
/* The folder pane as a card (DqAfdelingen): 280px, a hairline border, 12px
|
|
1158
|
+
corners, with each folder's count in a grey pill. */
|
|
1159
|
+
.cn-look-board .cn-index-page__folder-pane {
|
|
1160
|
+
flex: 0 0 280px;
|
|
1161
|
+
align-self: flex-start;
|
|
1162
|
+
margin-inline-end: 20px;
|
|
1163
|
+
padding: 14px;
|
|
1164
|
+
border: 1px solid var(--cn-board-hairline);
|
|
1165
|
+
border-radius: var(--cn-board-card-radius, 12px);
|
|
1166
|
+
background: var(--color-main-background);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.cn-look-board .cn-index-page__folder-pane .cn-folder-tree__count {
|
|
1170
|
+
display: inline-flex;
|
|
1171
|
+
align-items: center;
|
|
1172
|
+
justify-content: center;
|
|
1173
|
+
min-width: 20px;
|
|
1174
|
+
height: 20px;
|
|
1175
|
+
padding: 0 6px;
|
|
1176
|
+
box-sizing: border-box;
|
|
1177
|
+
border-radius: 10px;
|
|
1178
|
+
background: var(--color-background-dark);
|
|
1179
|
+
color: var(--color-main-text);
|
|
1180
|
+
font-size: 12px;
|
|
1181
|
+
font-weight: 600;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/* The selected folder's heading above the list: a 20px title over a soft line. */
|
|
1185
|
+
.cn-look-board .cn-index-page__scope-heading {
|
|
1186
|
+
margin-bottom: 16px;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.cn-look-board .cn-index-page__scope-title {
|
|
1190
|
+
font-size: 20px;
|
|
1191
|
+
font-weight: 700;
|
|
1192
|
+
}
|
package/src/css/look-board.css
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
/* Cards. */
|
|
28
28
|
--cn-board-card-radius: 12px;
|
|
29
29
|
--cn-board-card-padding: 20px 22px;
|
|
30
|
+
/* The side inset of a dashboard card: its title and its rows. */
|
|
31
|
+
--cn-board-dashboard-card-inset: 24px;
|
|
30
32
|
/* Hairline inside a card, and the soft secondary text. */
|
|
31
33
|
--cn-board-hairline: var(--color-border);
|
|
32
34
|
--cn-board-text-soft: var(--color-main-text);
|
|
@@ -56,6 +58,23 @@
|
|
|
56
58
|
margin-inline: 0;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
/* The page shows the content area's ground (the theme's content surface,
|
|
62
|
+
light grey on the boards) and draws none of its own; only its cards are
|
|
63
|
+
white. A theme rule that paints every `[class*="panel"]` with the main
|
|
64
|
+
background (the nldesign theme's element overrides) caught an index page
|
|
65
|
+
with a side panel (`cn-index-page--with-side-panel`) and drew the whole
|
|
66
|
+
page white (DqTeamwachtrij). That rule carries an id through
|
|
67
|
+
`:not(#header *)`, so this one names an id in `:not()` to outrank it.
|
|
68
|
+
@spec openspec/changes/board-page-ground/specs/board-page-ground/spec.md#requirement-a-board-page-draws-no-ground-of-its-own */
|
|
69
|
+
.cn-look-board .cn-index-page:not(#cn-board-page-ground):not(.cn-look-nextcloud *),
|
|
70
|
+
.cn-look-board .cn-detail-page:not(#cn-board-page-ground):not(.cn-look-nextcloud *),
|
|
71
|
+
.cn-look-board .cn-dashboard-page:not(#cn-board-page-ground):not(.cn-look-nextcloud *),
|
|
72
|
+
.cn-look-board .cn-settings-page:not(#cn-board-page-ground):not(.cn-look-nextcloud *),
|
|
73
|
+
.cn-look-board .cn-store-page:not(#cn-board-page-ground):not(.cn-look-nextcloud *),
|
|
74
|
+
.cn-look-board .cn-admin-settings-shell:not(#cn-board-page-ground):not(.cn-look-nextcloud *) {
|
|
75
|
+
background-color: transparent;
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
.cn-look-board .cn-index-page:not(.cn-look-nextcloud *),
|
|
60
79
|
.cn-look-board .cn-settings-page:not(.cn-look-nextcloud *) {
|
|
61
80
|
gap: var(--cn-board-section-gap);
|
|
@@ -155,6 +174,27 @@
|
|
|
155
174
|
gap: 10px;
|
|
156
175
|
}
|
|
157
176
|
|
|
177
|
+
/* The nldesign theme paints every primary and secondary button with
|
|
178
|
+
`!important` through its button tokens: weight 700, and a secondary in
|
|
179
|
+
the primary colour with a primary border on no ground. The board draws
|
|
180
|
+
header buttons at 600, the secondary on the main background with a grey
|
|
181
|
+
border (DqMijnWerk, DqZaak, DqZaken). The look hands the theme those values
|
|
182
|
+
through the same tokens, so the theme's own rules draw the board's button.
|
|
183
|
+
@spec openspec/changes/board-header-buttons-theme/specs/board-header-buttons-theme/spec.md#requirement-board-header-buttons-keep-the-boards-weight-and-ground-under-a-theme */
|
|
184
|
+
.cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *),
|
|
185
|
+
.cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *),
|
|
186
|
+
.cn-look-board .cn-dashboard-page__header-actions:not(.cn-look-nextcloud *),
|
|
187
|
+
.cn-look-board .cn-settings-page__header-buttons:not(.cn-look-nextcloud *),
|
|
188
|
+
.cn-look-board .cn-admin-settings-shell__header-buttons:not(.cn-look-nextcloud *),
|
|
189
|
+
.cn-look-board .cn-action-buttons:not(.cn-look-nextcloud *) {
|
|
190
|
+
--nldesign-component-button-font-weight: 600;
|
|
191
|
+
--nldesign-component-button-primary-action-font-weight: 600;
|
|
192
|
+
--nldesign-component-button-secondary-action-color: var(--color-main-text);
|
|
193
|
+
--nldesign-component-button-secondary-action-background-color: var(--color-main-background);
|
|
194
|
+
--nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
|
|
195
|
+
--nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
|
|
196
|
+
}
|
|
197
|
+
|
|
158
198
|
.cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *) .button-vue,
|
|
159
199
|
.cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *) .button-vue,
|
|
160
200
|
.cn-look-board .cn-dashboard-page__header-actions:not(.cn-look-nextcloud *) .button-vue,
|
|
@@ -1158,6 +1198,20 @@
|
|
|
1158
1198
|
line-height: 1.3;
|
|
1159
1199
|
}
|
|
1160
1200
|
|
|
1201
|
+
/* The board draws no rule under a dashboard card's title (DqMijnWerk,
|
|
1202
|
+
DqDashboard, PqMijnWerk): the title sits 20px from the card's top and on
|
|
1203
|
+
the card's 24px side inset, the line its rows start on, 12px above the
|
|
1204
|
+
content. A borderless card has no edge to inset from and keeps the
|
|
1205
|
+
wrapper's padding.
|
|
1206
|
+
@spec openspec/changes/board-dashboard-card-header/specs/board-dashboard-card-header/spec.md#requirement-a-board-dashboard-card-draws-no-header-rule */
|
|
1207
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard) .cn-widget-wrapper__header {
|
|
1208
|
+
border-bottom: none;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--nc-dashboard):not(.cn-widget-wrapper--borderless) .cn-widget-wrapper__header {
|
|
1212
|
+
padding: 20px var(--cn-board-dashboard-card-inset) 12px;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1161
1215
|
/* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
|
|
1162
1216
|
/* The dashboard's card body is flush, so the rows carry their own inset: the
|
|
1163
1217
|
board's card pads 24px at the sides and each row 14px, a soft hairline
|
|
@@ -1274,6 +1328,47 @@
|
|
|
1274
1328
|
display: none;
|
|
1275
1329
|
}
|
|
1276
1330
|
|
|
1331
|
+
/* ---- Widgets that drew their rows on the card's edge -------------------- */
|
|
1332
|
+
/* The card body is flush, so a widget carries the card's side inset itself,
|
|
1333
|
+
as the record list and saved views do. Without it the task list's
|
|
1334
|
+
checkboxes, the step bar and its legend, the week strip's days, the bar
|
|
1335
|
+
list and the activity items started on the card's edge, and a table's
|
|
1336
|
+
first column 16px in (DqMijnWerk "Mijn taken", "Mijn 14 zaken per stap",
|
|
1337
|
+
"Termijnen deze week", "Recente activiteit"; DqDashboard "Zaken per
|
|
1338
|
+
status", "Vastgelopen zaken"). The top inset is 4px: with the header's
|
|
1339
|
+
12px it is the board's 16px from title to bar. This also replaces the
|
|
1340
|
+
step bar's and week strip's opt-in `content.inset` on a board dashboard.
|
|
1341
|
+
A borderless card stands on the page ground and keeps its rows flush.
|
|
1342
|
+
@spec openspec/changes/board-dashboard-widget-inset/specs/board-dashboard-widget-inset/spec.md#requirement-a-board-dashboard-widget-keeps-the-cards-side-inset */
|
|
1343
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-tasks-widget {
|
|
1344
|
+
padding: 0 var(--cn-board-dashboard-card-inset) 10px;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-stacked-bar,
|
|
1348
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-week-strip {
|
|
1349
|
+
box-sizing: border-box;
|
|
1350
|
+
padding: 4px var(--cn-board-dashboard-card-inset) 22px;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-chart-widget__html {
|
|
1354
|
+
padding: 4px var(--cn-board-dashboard-card-inset) 20px;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/* An activity item pads itself 4px, so the body takes 4px less. */
|
|
1358
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-nc-widget-widget__body {
|
|
1359
|
+
padding: 0 calc(var(--cn-board-dashboard-card-inset) - 4px) 16px;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-widget-object-table .cn-data-table th:first-child,
|
|
1363
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-widget-object-table .cn-data-table td:first-child {
|
|
1364
|
+
padding-inline-start: var(--cn-board-dashboard-card-inset);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-widget-object-table .cn-data-table th:last-child,
|
|
1368
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:not(.cn-widget-wrapper--borderless) .cn-widget-object-table .cn-data-table td:last-child {
|
|
1369
|
+
padding-inline-end: var(--cn-board-dashboard-card-inset);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1277
1372
|
/* ---- Week strip (DqMijnWerk "Termijnen deze week") ---------------------- */
|
|
1278
1373
|
/* The days wrap inside the card instead of scrolling sideways: a row holds
|
|
1279
1374
|
as many days of at least 120px as the card fits, and the rest go to the
|
|
@@ -1293,23 +1388,13 @@
|
|
|
1293
1388
|
flex-wrap: wrap;
|
|
1294
1389
|
}
|
|
1295
1390
|
|
|
1296
|
-
/* ---- Bar-list and matrix cards (DqDashboard) ----------------------------- */
|
|
1297
|
-
/* The board draws no rule under the title of a card holding a bar list or a
|
|
1298
|
-
matrix. Other cards, plain stat tiles among them, keep theirs.
|
|
1299
|
-
@spec openspec/changes/chart-bar-list-and-matrix/specs/chart-bar-list-and-matrix/spec.md#requirement-board-bar-list-and-matrix-cards-draw-no-header-rule */
|
|
1300
|
-
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:has(.cn-chart-widget__html) .cn-widget-wrapper__header {
|
|
1301
|
-
border-bottom: none;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
1391
|
/* ---- A card of boxed stat tiles (DcRapportages "Vergaderingen en efficiëntie") -- */
|
|
1305
1392
|
/* The board draws this card 22px from its edge on every side but the bottom
|
|
1306
|
-
(20px)
|
|
1307
|
-
|
|
1308
|
-
the sides, with the wrapper's divider under the title.
|
|
1393
|
+
(20px) and the header 14px above the tiles. The card-fit cell padding
|
|
1394
|
+
(8px 14px) plus the grid's own 24px came to 38px at the sides.
|
|
1309
1395
|
@spec openspec/changes/stats-box-card-inset/specs/stats-box-card-inset/spec.md#requirement-a-card-of-boxed-stat-tiles-keeps-the-boards-inset */
|
|
1310
1396
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:has(.cn-stats-block-widget--tiles-box) .cn-widget-wrapper__header {
|
|
1311
1397
|
padding: 20px 22px 0;
|
|
1312
|
-
border-bottom: none;
|
|
1313
1398
|
}
|
|
1314
1399
|
|
|
1315
1400
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-widget-wrapper:has(.cn-stats-block-widget--tiles-box) .cn-widget-wrapper__content {
|
package/src/index.js
CHANGED
|
@@ -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.
|
|
6
|
+
"version": "2.128.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
"optionsByParent": { "type": "object", "description": "choice: map of parent-choice value → options[] for a dependent (dependsOn) choice. Same option item shape as `options`.", "additionalProperties": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["value", "label"], "properties": { "value": {}, "label": { "type": "string" } } } } },
|
|
121
121
|
"suggestFrom": { "type": "string", "description": "choice: the configKey of an earlier choice this step pre-fills a suggestion from. When that value is set and the user has not yet picked here, the option matching suggestMap[parentValue] is auto-selected (still user-overridable)." },
|
|
122
122
|
"default": { "description": "choice: the option value chosen when the step opens (an array of values with `multiple`). Not applied to a step the server already completed." },
|
|
123
|
+
"noteStyle": { "type": "string", "enum": ["line", "card"], "default": "line", "description": "choice: how `note` is drawn: a muted line under the options (`line`) or an info note card (`card`)." },
|
|
123
124
|
"suggestMap": { "type": "object", "description": "choice: map of suggestFrom value → suggested option value. Used to auto-suggest a default (e.g. RGS template suggested from organisation type).", "additionalProperties": { "type": "string" } },
|
|
124
125
|
"multiple": { "type": "boolean", "default": false, "description": "choice: allow multi-select." },
|
|
125
126
|
"loadAction": { "type": "string", "minLength": 1, "description": "choice with display cards: an actionId each option card loads itself with. Every card except the one whose value is `none` gets its own Load button, which POSTs { dataset: <value> } to /api/setup/action/{loadAction}, spins while it runs and shows the result on that card. A successful load also selects the card. With loadAction the separate run-action step that loaded the picked dataset is no longer needed; manifests that keep it still work." },
|
|
@@ -1998,6 +1999,7 @@
|
|
|
1998
1999
|
"items": { "$ref": "#/$defs/action" },
|
|
1999
2000
|
"description": "Declarative row/widget actions rendered by CnWidgetObjectTable. object-op patch/delete render per row; object-op create renders as a widget-scoped footer affordance."
|
|
2000
2001
|
},
|
|
2002
|
+
"pageSize": { "type": "integer", "minimum": 1, "description": "Rows per page: the table shows one page with the board pager under it (\"4 of 7\", DqCatalogus). Under a pager `source.limit` is how many rows are read (2.127.0)." },
|
|
2001
2003
|
"rowClass": {
|
|
2002
2004
|
"type": "array",
|
|
2003
2005
|
"description": "#91: declarative per-row CSS class rules compiled into CnDataTable's rowClass function. Each rule { when: { field, op?, value }, class } adds `class` to a row when the shared visibleWhen predicate holds against that row (field = dot-path into the row, op = eq|neq|gt|gte|lt|lte default eq, value = literal). Rules evaluate in order and every matching class is space-joined — so overdue / at-risk rows can be highlighted from the manifest. A host-supplied rowClass FUNCTION (runtime-only) still passes straight through.",
|
|
@@ -2208,6 +2210,31 @@
|
|
|
2208
2210
|
}
|
|
2209
2211
|
}
|
|
2210
2212
|
},
|
|
2213
|
+
{
|
|
2214
|
+
"description": "notice built-in widget (CnNoticeWidget): one toned box with an icon, a bold title and a text, through the host's translate, in the side column or the grid (DqSociaalDomein, 2.128.0).",
|
|
2215
|
+
"if": {
|
|
2216
|
+
"properties": { "widgetKey": { "const": "notice" } },
|
|
2217
|
+
"required": ["widgetKey"]
|
|
2218
|
+
},
|
|
2219
|
+
"then": {
|
|
2220
|
+
"properties": {
|
|
2221
|
+
"props": {
|
|
2222
|
+
"type": "object",
|
|
2223
|
+
"properties": {
|
|
2224
|
+
"content": {
|
|
2225
|
+
"type": "object",
|
|
2226
|
+
"properties": {
|
|
2227
|
+
"variant": { "type": "string", "enum": ["info", "warning", "error", "success"], "default": "info" },
|
|
2228
|
+
"title": { "type": "string" },
|
|
2229
|
+
"text": { "type": "string" },
|
|
2230
|
+
"icon": { "type": "string", "minLength": 1 }
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2211
2238
|
{
|
|
2212
2239
|
"description": "audit-trail built-in widget (CnAuditTrailWidget, Wave 1): the object change-log card. All props are optional — register/schema/objectId normally arrive via the detail-page object-context merge; explicit props win.",
|
|
2213
2240
|
"if": {
|
|
@@ -2268,6 +2295,8 @@
|
|
|
2268
2295
|
"emptyLabel": { "type": "string", "description": "Empty-state message rendered instead of the chart when the resolved series contain no data points." },
|
|
2269
2296
|
"barMax": { "type": "number", "exclusiveMinimum": 0, "description": "chartKind 'bar-list': the value a full track stands for (100 for percentages). Omitted = the largest value." },
|
|
2270
2297
|
"footnote": { "type": "string", "description": "A line under the chart, through the host's translate: under the rows of a bar-list or matrix, and under the plot of a line, area or bar chart." },
|
|
2298
|
+
"referenceLines": { "type": "array", "description": "line, area and bar charts: dashed horizontal reference lines on the value axis, such as a norm. Each `{ value, label?, color? }`; the label goes through the host's translate, the colour defaults to the theme's error colour.", "items": { "type": "object", "required": ["value"], "properties": { "value": { "type": "number" }, "label": { "type": "string" }, "color": { "type": "string" } } } },
|
|
2299
|
+
"categoryFormat": { "type": "string", "enum": ["", "month", "month-year"], "description": "How date categories read on the x-axis: `month` (\"jan\") or `month-year` (\"jan 2026\"), in the user's language. Categories that are not dates are left as is." },
|
|
2271
2300
|
"emptyCellLabel": { "type": "string", "description": "chartKind 'matrix': the text of a cell without a value (\"geen\"), through the host's translate. Empty leaves the cell blank. Passed through to CnChartWidget." },
|
|
2272
2301
|
"highlight": {
|
|
2273
2302
|
"type": "object",
|
|
@@ -3247,9 +3276,9 @@
|
|
|
3247
3276
|
},
|
|
3248
3277
|
"op": {
|
|
3249
3278
|
"type": "string",
|
|
3250
|
-
"enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty", "in", "anyOf"],
|
|
3279
|
+
"enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty", "in", "anyOf", "contains"],
|
|
3251
3280
|
"default": "eq",
|
|
3252
|
-
"description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
|
|
3281
|
+
"description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `anyOf` holds when the value, or any entry of a list value, is one of the listed `value` array: a record whose `dataCategories` holds \"health\" among others matches `anyOf [\"health\", \"ethnicity\"]` (2.126.0). `contains` holds when a list value holds the given `value`, e.g. a case type's overviewCards switching a card on (2.128.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
|
|
3253
3282
|
},
|
|
3254
3283
|
"value": {
|
|
3255
3284
|
"description": "The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."
|
|
@@ -4052,6 +4081,8 @@
|
|
|
4052
4081
|
"additionalProperties": true,
|
|
4053
4082
|
"description": "type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.",
|
|
4054
4083
|
"properties": {
|
|
4084
|
+
"heading": { "type": "boolean", "description": "true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)." },
|
|
4085
|
+
"countText": { "type": "string", "minLength": 1, "description": "The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)." },
|
|
4055
4086
|
"pane": { "type": "boolean", "default": true, "description": "false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)." },
|
|
4056
4087
|
"folders": {
|
|
4057
4088
|
"type": "array",
|
|
@@ -4215,6 +4246,16 @@
|
|
|
4215
4246
|
]
|
|
4216
4247
|
}
|
|
4217
4248
|
},
|
|
4249
|
+
"tabsFrom": {
|
|
4250
|
+
"type": "string",
|
|
4251
|
+
"minLength": 1,
|
|
4252
|
+
"description": "type='detail': a field path, read like a `visibleWhen` field, whose value picks the tabs of the page's tabs widgets and their order for this record, e.g. `@context.caseType.tabs` (DqAanbesteding). A non-empty list of tab ids keeps those tabs in that order; otherwise the declared tabs stay, and edit mode shows them all (2.128.0)."
|
|
4253
|
+
},
|
|
4254
|
+
"sideColumnFrom": {
|
|
4255
|
+
"type": "string",
|
|
4256
|
+
"minLength": 1,
|
|
4257
|
+
"description": "type='detail': a field path, read like a `visibleWhen` field, whose value picks the side column and its order for this record, e.g. `@context.caseType.sideCards` (a case type that names the cards its cases show). When the value is a non-empty list of widget ids, the side column is those of `sideColumn` in that order and the rest are left out; each still follows its own `visibleWhen`. Any other value, or no value, keeps `sideColumn` as declared. Maps to CnDetailPage `sideColumnFrom`."
|
|
4258
|
+
},
|
|
4218
4259
|
"headerActions": {
|
|
4219
4260
|
"type": "array",
|
|
4220
4261
|
"description": "Page-level header actions rendered inside CnActionsBar's overflow dropdown (between the built-in Refresh action and the `#action-items` slot). On a `type:'detail'` page the same entries render in the header's Actions menu, above the Request-a-feature / Report-a-bug / Documentation trio. Same shape as the row-level `actions[]` ($ref action), but handlers receive NO row context — they are page-level. Reserved ids (`refresh`, `import`, `export`, `copy`, `delete`) are dropped at render time to avoid shadowing built-ins. See manifest-icons-and-page-actions for the dispatch semantics.",
|
|
@@ -4714,10 +4755,24 @@
|
|
|
4714
4755
|
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "secondary" },
|
|
4715
4756
|
"icon": { "type": "string", "minLength": 1, "description": "MDI icon name." },
|
|
4716
4757
|
"format": { "type": "string", "enum": ["csv", "excel"], "default": "csv", "description": "For `export`." },
|
|
4758
|
+
"run": { "type": "string", "minLength": 1, "description": "For `export`: the id of a `headerActions` entry the button runs instead of the export dialog. On a named or endpoint source an Export without `run` (and without a mass-export listener) is not drawn (2.127.0)." },
|
|
4717
4759
|
"items": { "type": "array", "minItems": 1, "items": { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["heading"], "properties": { "heading": { "type": "string", "minLength": 1, "description": "i18n key of a group heading." } } } ] }, "description": "For `actions-menu`: the menu's entries, in order. Each is `header-actions` (every header action), the id of one `headerActions` entry, or a built-in of the actions bar's own menu, which the bar drops while header buttons show: `import`, `export`, `refresh`, `copy-selected`, `delete-selected` (disabled until a row is selected) or `documentation` (a link to the page's documentationUrl, left out without one). An unknown name is left out. Absent: the header actions only (screens-index-header-actions-menu, 2.90.0). Entries can be grouped: `{ \"heading\": \"<i18n key>\" }` draws a group heading and `\"separator\"` a rule, each only when an entry follows it, so a group whose entries are all left out leaves no empty heading (screens-index-header-menu-headings, 2.93.0)." }
|
|
4718
4760
|
}
|
|
4719
4761
|
}
|
|
4720
4762
|
},
|
|
4763
|
+
"search": { "type": "boolean", "default": true, "description": "type='index': `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page. Wins over `inlineSearch` (2.127.0)." },
|
|
4764
|
+
"searchPlacement": { "type": "string", "enum": ["", "toolbar"], "default": "", "description": "type='index', board look: where the search field sits. `toolbar` puts it on row 1 before the Filter button and the view switch instead of opening row 2 (DqTeamwachtrij, 2.127.0)." },
|
|
4765
|
+
"defaultOrder": { "description": "type='index', self-fetch: the order the page opens on when neither the address nor sortKeys gives one: `\"-createdAt\"`, `\"priority:desc,title\"`, an `_order` map or a `[{ key, order }]` list (2.127.0).", "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": { "type": "string", "enum": ["asc", "desc", "ASC", "DESC"] } }, { "type": "array", "items": { "type": "object", "required": ["key"], "properties": { "key": { "type": "string", "minLength": 1 }, "order": { "type": "string", "enum": ["asc", "desc"] } } } } ] },
|
|
4766
|
+
"listConfig": {
|
|
4767
|
+
"type": "object",
|
|
4768
|
+
"additionalProperties": true,
|
|
4769
|
+
"description": "type='index': the field mapping of the list view's rows (CnObjectRow): titleField, subtitleField, imageField, iconField, iconName, badgeField and the badge keys stay free-form. 2.127.0 types the row cells and the meta template (DqTeamwachtrij).",
|
|
4770
|
+
"properties": {
|
|
4771
|
+
"subtitleTemplate": { "type": "string", "minLength": 1, "description": "The meta line: `{field}` tokens filled from the row; empty parts and their separators drop." },
|
|
4772
|
+
"leading": {"type": "object", "required": ["key"], "additionalProperties": true, "properties": {"key": {"type": "string", "minLength": 1}, "widget": {"type": "string", "minLength": 1}, "widgetProps": {"type": "object", "additionalProperties": true}, "formatter": {"type": "string", "minLength": 1}, "width": {"type": "string", "minLength": 1, "description": "CSS width, lining the column up across rows."}}, "description": "One cell before the title, drawn as a table column draws it (`{ key: \"createdAt\", widget: \"age\" }`)."},
|
|
4773
|
+
"trailing": { "description": "A cell, or a list of cells, after the badges, each drawn as a table column draws it.", "anyOf": [ {"type": "object", "required": ["key"], "additionalProperties": true, "properties": {"key": {"type": "string", "minLength": 1}, "widget": {"type": "string", "minLength": 1}, "widgetProps": {"type": "object", "additionalProperties": true}, "formatter": {"type": "string", "minLength": 1}, "width": {"type": "string", "minLength": 1, "description": "CSS width, lining the column up across rows."}}}, { "type": "array", "items": {"type": "object", "required": ["key"], "additionalProperties": true, "properties": {"key": {"type": "string", "minLength": 1}, "widget": {"type": "string", "minLength": 1}, "widgetProps": {"type": "object", "additionalProperties": true}, "formatter": {"type": "string", "minLength": 1}, "width": {"type": "string", "minLength": 1, "description": "CSS width, lining the column up across rows."}}} } ] }
|
|
4774
|
+
}
|
|
4775
|
+
},
|
|
4721
4776
|
"rowTitle": {
|
|
4722
4777
|
"type": "string",
|
|
4723
4778
|
"enum": ["link", "plain"],
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { generateOcsUrl } from '@nextcloud/router'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The display names of the apps in Nextcloud's navigation, by app id, read
|
|
10
|
+
* once per page load from the OCS navigation endpoint with the reader's
|
|
11
|
+
* session. An app outside the navigation, or a failed read, has no entry.
|
|
12
|
+
*
|
|
13
|
+
* @module utils/nextcloudApps
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
let pending = null
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The app display names by app id.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} axios The axios instance.
|
|
22
|
+
* @return {Promise<Record<string, string>>} The names; empty when the read fails.
|
|
23
|
+
*/
|
|
24
|
+
export function appDisplayNames(axios) {
|
|
25
|
+
if (!pending) {
|
|
26
|
+
pending = axios.get(generateOcsUrl('core/navigation/apps'), { params: { format: 'json' }, headers: { 'OCS-APIRequest': 'true' } })
|
|
27
|
+
.then((res) => {
|
|
28
|
+
const entries = (res && res.data && res.data.ocs && res.data.ocs.data) || []
|
|
29
|
+
const names = {}
|
|
30
|
+
for (const entry of Array.isArray(entries) ? entries : Object.values(entries)) {
|
|
31
|
+
const id = entry && (entry.app || entry.id)
|
|
32
|
+
if (id && typeof entry.name === 'string' && entry.name !== '') {
|
|
33
|
+
names[String(id)] = entry.name
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return names
|
|
37
|
+
})
|
|
38
|
+
.catch(() => ({}))
|
|
39
|
+
}
|
|
40
|
+
return pending
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Forget the names read, so the next call reads them again.
|
|
45
|
+
*
|
|
46
|
+
* @return {void}
|
|
47
|
+
*/
|
|
48
|
+
export function clearAppDisplayNames() {
|
|
49
|
+
pending = null
|
|
50
|
+
}
|
|
@@ -64,6 +64,23 @@ export async function fetchGroupMembers(axios, groupId) {
|
|
|
64
64
|
return Array.isArray(users) ? users : Object.values(users)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Every user the reader may list, with their details: all users for an
|
|
69
|
+
* admin, the users of their groups for a sub-admin. Refused for others.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} axios The axios instance.
|
|
72
|
+
* @param {{limit?: number}} [options] How many to read.
|
|
73
|
+
* @return {Promise<Array<object>>} Nextcloud's user details, one per user.
|
|
74
|
+
*/
|
|
75
|
+
export async function fetchUsers(axios, options = {}) {
|
|
76
|
+
const res = await axios.get(generateOcsUrl('cloud/users/details'), {
|
|
77
|
+
params: { limit: options.limit || 500, format: 'json' },
|
|
78
|
+
headers: OCS_HEADERS,
|
|
79
|
+
})
|
|
80
|
+
const users = (ocsData(res) && ocsData(res).users) || {}
|
|
81
|
+
return Array.isArray(users) ? users : Object.values(users)
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
/**
|
|
68
85
|
* Whether a user is in today: an out-of-office period that runs now (the
|
|
69
86
|
* dav app's absence, Nextcloud 28 and later), else their user status.
|
|
@@ -153,8 +153,15 @@ export function resolveScopeLayout({ scope = null, row = null, pageColumns = []
|
|
|
153
153
|
return
|
|
154
154
|
}
|
|
155
155
|
// The page's own entry is used, not the scope's, so the label and
|
|
156
|
-
// formatter a column was declared with survive being selected.
|
|
157
|
-
|
|
156
|
+
// formatter a column was declared with survive being selected. A
|
|
157
|
+
// column the page hides by default shows: the scope asked for it.
|
|
158
|
+
const own = byKey.get(key)
|
|
159
|
+
if (own && typeof own === 'object' && own.hidden === true) {
|
|
160
|
+
const { hidden, ...shown } = own
|
|
161
|
+
columns.push(shown)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
columns.push(own)
|
|
158
165
|
})
|
|
159
166
|
|
|
160
167
|
return { columns, sortKeys, searchFields }
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read an order a manifest writes into the `[{ key, order }]` list a self-fetch
|
|
8
|
+
* index page sorts by. Accepted forms:
|
|
9
|
+
*
|
|
10
|
+
* - `"title"` ascending, `"-createdAt"` descending, `"priority:desc"`;
|
|
11
|
+
* - several of those, comma separated, in priority order (`"-priority,title"`);
|
|
12
|
+
* - an OpenRegister `_order` map, `{ "priority": "desc", "title": "asc" }`;
|
|
13
|
+
* - a `[{ key, order? }]` list.
|
|
14
|
+
*
|
|
15
|
+
* @param {string|object|Array|null|undefined} value The order.
|
|
16
|
+
* @return {Array<{key: string, order: 'asc'|'desc'}>} The sort keys, possibly empty.
|
|
17
|
+
*/
|
|
18
|
+
export function orderToSortKeys(value) {
|
|
19
|
+
const direction = (order) => (String(order || '').toLowerCase() === 'desc' ? 'desc' : 'asc')
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
return value
|
|
22
|
+
.filter((entry) => entry && typeof entry.key === 'string' && entry.key !== '')
|
|
23
|
+
.map((entry) => ({ key: entry.key, order: direction(entry.order) }))
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
return Object.entries(value)
|
|
27
|
+
.filter(([key]) => key !== '')
|
|
28
|
+
.map(([key, order]) => ({ key, order: direction(order) }))
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== 'string') {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
return value.split(',')
|
|
34
|
+
.map((part) => part.trim())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.map((part) => {
|
|
37
|
+
if (part.startsWith('-')) {
|
|
38
|
+
return { key: part.slice(1), order: 'desc' }
|
|
39
|
+
}
|
|
40
|
+
const [key, order] = part.split(':')
|
|
41
|
+
return { key, order: direction(order) }
|
|
42
|
+
})
|
|
43
|
+
.filter((entry) => entry.key !== '')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether two sort key lists order the same way.
|
|
48
|
+
*
|
|
49
|
+
* @param {Array<{key: string, order: string}>} a One list.
|
|
50
|
+
* @param {Array<{key: string, order: string}>} b The other.
|
|
51
|
+
* @return {boolean} True when they name the same keys, directions and priority.
|
|
52
|
+
*/
|
|
53
|
+
export function sameSortKeys(a, b) {
|
|
54
|
+
const left = Array.isArray(a) ? a : []
|
|
55
|
+
const right = Array.isArray(b) ? b : []
|
|
56
|
+
return left.length === right.length
|
|
57
|
+
&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))
|
|
58
|
+
}
|