@conduction/nextcloud-vue 2.105.0 → 2.106.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/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +53 -4
- 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/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +159 -50
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -4
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.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 +21 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +21 -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 +803 -453
- package/dist/esm/utils/dueText.js +18 -3
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +11 -1
- package/dist/esm/utils/sortOrder.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8281 -6350
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +803 -453
- package/l10n/en.json +21 -1
- package/l10n/nl.json +21 -1
- package/package.json +1 -1
- package/src/components/CnCardGrid/CnCardGrid.md +2 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -2
- package/src/components/CnDetailPage/CnDetailPage.vue +53 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
- package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
- package/src/components/CnIndexPage/CnIndexPage.md +7 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +187 -58
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +22 -4
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
- package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
- package/src/components/CnTabs/CnTabs.vue +11 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
- package/src/components/CnTeamWorkloadWidget/index.js +14 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/src/components/index.js +1 -0
- package/src/css/index-page.css +14 -2
- package/src/css/look-board-detail.css +48 -4
- package/src/css/look-board-index.css +64 -2
- package/src/css/look-board.css +25 -2
- package/src/index.js +1 -1
- package/src/schemas/app-manifest-v2.schema.json +75 -6
- package/src/utils/dueText.js +18 -3
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/sortOrder.js +10 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
/* ---- The header: two rows on the ground ------------------------------- */
|
|
26
26
|
|
|
27
27
|
.cn-look-board .cn-detail-page__header--board {
|
|
28
|
-
align-items:
|
|
28
|
+
align-items: flex-start;
|
|
29
29
|
column-gap: 16px;
|
|
30
30
|
flex-wrap: wrap;
|
|
31
31
|
margin-bottom: 22px;
|
|
@@ -33,10 +33,16 @@
|
|
|
33
33
|
row-gap: 6px;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/* Row 1: the title takes what the buttons leave, from a
|
|
36
|
+
/* Row 1: the title takes what the buttons leave, from a zero basis, and the
|
|
37
|
+
buttons sit at the row's top beside it (DqZaak: both at 96). From a 320px
|
|
38
|
+
basis the title and its follow control claimed the row first, so on a 960px
|
|
39
|
+
header the buttons centred against a two-line title, and below that they
|
|
40
|
+
dropped to a row of their own. Only when less than 16rem of title fits
|
|
41
|
+
beside the buttons do they still wrap.
|
|
42
|
+
@spec openspec/changes/board-detail-header-row/specs/board-detail-header-row/spec.md#requirement-the-board-detail-header-keeps-its-actions-on-the-title-row */
|
|
37
43
|
.cn-look-board .cn-detail-page__header--board > :first-child {
|
|
38
|
-
flex: 1 1
|
|
39
|
-
min-width:
|
|
44
|
+
flex: 1 1 0;
|
|
45
|
+
min-width: min(100%, 16rem);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
.cn-look-board .cn-detail-page__header--board .cn-detail-page__title {
|
|
@@ -58,6 +64,44 @@
|
|
|
58
64
|
min-height: var(--cn-board-control-height);
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
/* The quick actions, Edit and the Meer menu are the board's neutral buttons
|
|
68
|
+
(DqZaak): the main background, a grey border, the main text at 600. They
|
|
69
|
+
outrank a theme that gives every secondary button a primary outline with
|
|
70
|
+
`!important` (nldesign), as the index header does. The buildiq square and
|
|
71
|
+
the primary button keep their own rules. */
|
|
72
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary,
|
|
73
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions .action-item--secondary .action-item__menutoggle {
|
|
74
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
75
|
+
height: var(--cn-board-control-height) !important;
|
|
76
|
+
min-height: var(--cn-board-control-height) !important;
|
|
77
|
+
padding: 0 14px !important;
|
|
78
|
+
border: 1px solid var(--color-border-dark) !important;
|
|
79
|
+
border-radius: var(--cn-board-control-radius) !important;
|
|
80
|
+
background: var(--color-main-background) !important;
|
|
81
|
+
color: var(--color-main-text) !important;
|
|
82
|
+
font-size: 14px !important;
|
|
83
|
+
font-weight: 600 !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary:hover,
|
|
87
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions .action-item--secondary .action-item__menutoggle:hover {
|
|
88
|
+
background: var(--color-background-hover) !important;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Each header icon is 16px with 8px to its label; NcButton draws 20px in a 30px slot. */
|
|
92
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon {
|
|
93
|
+
width: 16px;
|
|
94
|
+
min-width: 16px;
|
|
95
|
+
height: 16px;
|
|
96
|
+
min-height: 16px;
|
|
97
|
+
margin-inline-end: 8px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon svg {
|
|
101
|
+
width: 16px;
|
|
102
|
+
height: 16px;
|
|
103
|
+
}
|
|
104
|
+
|
|
61
105
|
/* Row 2: pills, breadcrumb, dot, meta, chips. */
|
|
62
106
|
.cn-look-board .cn-detail-page__header-row2 {
|
|
63
107
|
align-items: center;
|
|
@@ -10,12 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
/* ------------------------------------------------------------------ header */
|
|
12
12
|
|
|
13
|
-
/* Row 1: the h1 and the buttons at its end
|
|
13
|
+
/* Row 1: the h1 and the buttons at its end, both at the row's top. Row 2:
|
|
14
|
+
the count line, 6px under. The header starts 4px under the content's top
|
|
15
|
+
(DqZaken: the title at 96, the count line at 142).
|
|
16
|
+
@spec openspec/changes/board-page-header-top/specs/board-page-header-top/spec.md#requirement-a-board-page-title-sits-4px-under-the-content-top */
|
|
14
17
|
.cn-look-board .cn-index-page .cn-page-header {
|
|
15
18
|
display: grid;
|
|
16
19
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
17
|
-
align-items:
|
|
20
|
+
align-items: start;
|
|
18
21
|
row-gap: 6px;
|
|
22
|
+
padding-top: 4px;
|
|
19
23
|
margin-bottom: var(--cn-board-section-gap, 20px);
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -1057,6 +1061,64 @@
|
|
|
1057
1061
|
border-radius: 8px;
|
|
1058
1062
|
}
|
|
1059
1063
|
|
|
1064
|
+
/* The record layout (`config.cardLayout`, DqAanMijToegewezen): a 20px 22px
|
|
1065
|
+
inset, 10px between its parts; the head pill at the start and the number
|
|
1066
|
+
in 13px muted at the end; a 17px bold title, not underlined; a 14px muted
|
|
1067
|
+
meta line; a footer over a hairline with a pill and the 14px semibold date.
|
|
1068
|
+
@spec openspec/changes/board-record-card-layout/specs/board-record-card-layout/spec.md#requirement-a-board-card-can-draw-the-record-layout-from-config */
|
|
1069
|
+
.cn-look-board .cn-object-card.cn-object-card--board:has(> .cn-object-card__record) {
|
|
1070
|
+
padding: 20px 22px;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record {
|
|
1074
|
+
gap: 10px;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record-head {
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: center;
|
|
1080
|
+
gap: 8px;
|
|
1081
|
+
min-height: 24px;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record-number {
|
|
1085
|
+
margin-inline-start: auto;
|
|
1086
|
+
color: var(--color-text-maxcontrast);
|
|
1087
|
+
font-size: 13px;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record .cn-object-card__title {
|
|
1091
|
+
margin: 0;
|
|
1092
|
+
font-size: 17px;
|
|
1093
|
+
font-weight: 700;
|
|
1094
|
+
line-height: 1.3;
|
|
1095
|
+
text-decoration: none;
|
|
1096
|
+
white-space: normal;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record-meta {
|
|
1100
|
+
margin: 0;
|
|
1101
|
+
color: var(--color-text-maxcontrast);
|
|
1102
|
+
font-size: 14px;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record-foot {
|
|
1106
|
+
display: flex;
|
|
1107
|
+
align-items: center;
|
|
1108
|
+
justify-content: space-between;
|
|
1109
|
+
gap: 8px;
|
|
1110
|
+
margin-top: auto;
|
|
1111
|
+
padding-top: 10px;
|
|
1112
|
+
border-top: 1px solid var(--cn-board-hairline);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.cn-look-board .cn-object-card--board .cn-object-card__record-date {
|
|
1116
|
+
margin-inline-start: auto;
|
|
1117
|
+
color: var(--color-main-text);
|
|
1118
|
+
font-size: 14px;
|
|
1119
|
+
font-weight: 600;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1060
1122
|
/* The catalogue card of the store page. */
|
|
1061
1123
|
.cn-look-board .cn-store-page__card.cn-store-page__card--board {
|
|
1062
1124
|
gap: 10px;
|
package/src/css/look-board.css
CHANGED
|
@@ -101,6 +101,15 @@
|
|
|
101
101
|
margin-bottom: var(--cn-board-section-gap);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* The boards start a page's header 4px under the content's top, so the title
|
|
105
|
+
sits at 96 (DqZaak, DqMijnWerk), not 92. The index page's header carries
|
|
106
|
+
the same inset in look-board-index.css.
|
|
107
|
+
@spec openspec/changes/board-page-header-top/specs/board-page-header-top/spec.md#requirement-a-board-page-title-sits-4px-under-the-content-top */
|
|
108
|
+
.cn-look-board .cn-detail-page:not(.cn-look-nextcloud *) .cn-detail-page__header,
|
|
109
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header {
|
|
110
|
+
padding-top: 4px;
|
|
111
|
+
}
|
|
112
|
+
|
|
104
113
|
/* The 56px inset clears the navigation toggle, which overlays the content
|
|
105
114
|
only while the navigation is closed. The store page's own header too: it
|
|
106
115
|
kept the inset with the navigation open, 56px right of the board's title. */
|
|
@@ -140,7 +149,7 @@
|
|
|
140
149
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__title-row {
|
|
141
150
|
grid-column: 1;
|
|
142
151
|
grid-row: 1;
|
|
143
|
-
align-self:
|
|
152
|
+
align-self: start;
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left > :not(.cn-dashboard-page__title-row) {
|
|
@@ -650,6 +659,7 @@
|
|
|
650
659
|
}
|
|
651
660
|
|
|
652
661
|
.cn-look-board .cn-admin-settings-shell__title:not(.cn-look-nextcloud *) {
|
|
662
|
+
min-height: var(--cn-board-control-height);
|
|
653
663
|
margin: 0;
|
|
654
664
|
font-size: 28px;
|
|
655
665
|
font-weight: 700;
|
|
@@ -662,10 +672,23 @@
|
|
|
662
672
|
align-items: center;
|
|
663
673
|
gap: 8px;
|
|
664
674
|
margin-top: 6px;
|
|
665
|
-
font-size:
|
|
675
|
+
font-size: 14px;
|
|
676
|
+
line-height: normal;
|
|
666
677
|
color: var(--color-text-maxcontrast);
|
|
667
678
|
}
|
|
668
679
|
|
|
680
|
+
/* The admin settings header as the board draws it (DqInstellingen): 4px
|
|
681
|
+
under the content's top and 8px above the grid's gap, the crumbs 6px under
|
|
682
|
+
the 40px title row in 14px, and the tab strip 20px under the header (at
|
|
683
|
+
187). The shell is a grid whose gap already spaces its rows, so the
|
|
684
|
+
header's own 20px bottom margin came on top of it and the strip sat at 194.
|
|
685
|
+
@spec openspec/changes/board-admin-settings-header-rhythm/specs/board-admin-settings-header-rhythm/spec.md#requirement-the-board-admin-settings-header-keeps-the-boards-rhythm */
|
|
686
|
+
.cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .cn-admin-settings-shell__header,
|
|
687
|
+
.cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .cn-admin-settings-shell__header {
|
|
688
|
+
margin-bottom: 0;
|
|
689
|
+
padding: 4px 0 8px;
|
|
690
|
+
}
|
|
691
|
+
|
|
669
692
|
.cn-look-board .cn-admin-settings-shell__crumb-list:not(.cn-look-nextcloud *) {
|
|
670
693
|
display: flex;
|
|
671
694
|
flex-wrap: wrap;
|
package/src/index.js
CHANGED
|
@@ -376,7 +376,7 @@ export { createOpenRegisterSource } from './components/index.js'
|
|
|
376
376
|
// Workplace dashboard primitives (openspec: workplace-dashboard-primitives):
|
|
377
377
|
// the week strip and stacked bar widgets with their forms, the segmented
|
|
378
378
|
// control, and the brand stripe.
|
|
379
|
-
export { CnBrandStripe, CnSegmentedControl, CnStackedBarWidget, CnStackedBarWidgetForm, CnWeekStripWidget, CnWeekStripWidgetForm } from './components/index.js'
|
|
379
|
+
export { CnBrandStripe, CnSegmentedControl, CnStackedBarWidget, CnStackedBarWidgetForm, CnTeamWorkloadWidget, CnWeekStripWidget, CnWeekStripWidgetForm } from './components/index.js'
|
|
380
380
|
|
|
381
381
|
// Page components the page renderer mounts by `pages[].type` that were missing
|
|
382
382
|
// from this entry. A component reachable from a manifest but not importable
|
|
@@ -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.129.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -2178,7 +2178,7 @@
|
|
|
2178
2178
|
}
|
|
2179
2179
|
},
|
|
2180
2180
|
{
|
|
2181
|
-
"description": "record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0). Other content keys stay free-form.",
|
|
2181
|
+
"description": "record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0), and a row's colour `dot` and `flags` (2.129.0). Other content keys stay free-form.",
|
|
2182
2182
|
"if": {
|
|
2183
2183
|
"properties": { "widgetKey": { "const": "record-list" } },
|
|
2184
2184
|
"required": ["widgetKey"]
|
|
@@ -2202,7 +2202,9 @@
|
|
|
2202
2202
|
"disabledWhen": { "$ref": "#/$defs/visibleWhen" },
|
|
2203
2203
|
"disabledReason": { "type": "string" }
|
|
2204
2204
|
}
|
|
2205
|
-
}
|
|
2205
|
+
},
|
|
2206
|
+
"dot": { "description": "A colour dot before the title: a field name, or `{ field, colorMap? }` (2.129.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "required": ["field"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string" } } } }] },
|
|
2207
|
+
"flags": { "type": "array", "description": "The labels of the flags a row has, at its end (\"Start\", \"End\"): `[{ field, label, value? }]` (DqZaaktypeOverzicht, 2.129.0).", "items": { "type": "object", "required": ["field", "label"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "value": {} } } }
|
|
2206
2208
|
}
|
|
2207
2209
|
}
|
|
2208
2210
|
}
|
|
@@ -2235,6 +2237,53 @@
|
|
|
2235
2237
|
}
|
|
2236
2238
|
}
|
|
2237
2239
|
},
|
|
2240
|
+
{
|
|
2241
|
+
"description": "related built-in widget (CnRelatedObjectsWidget): grouping by relation (2.129.0). Other content keys stay free-form.",
|
|
2242
|
+
"if": {
|
|
2243
|
+
"properties": { "widgetKey": { "const": "related" } },
|
|
2244
|
+
"required": ["widgetKey"]
|
|
2245
|
+
},
|
|
2246
|
+
"then": {
|
|
2247
|
+
"properties": {
|
|
2248
|
+
"props": {
|
|
2249
|
+
"type": "object",
|
|
2250
|
+
"properties": {
|
|
2251
|
+
"content": {
|
|
2252
|
+
"type": "object",
|
|
2253
|
+
"properties": {
|
|
2254
|
+
"groupByRelation": { "type": "boolean", "description": "Split the Objects group by the relation label OpenRegister answers per direction." },
|
|
2255
|
+
"layout": { "type": "string", "enum": ["tabs", "list", "sections"], "description": "`sections` draws every group under its own heading in one list." },
|
|
2256
|
+
"groupActions": { "type": "object", "additionalProperties": { "type": "object" }, "description": "An action on a group's heading in `layout: \"sections\"`, keyed by the group's key (DqZaakGerelateerd \"Vervolg plannen\")." }
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
},
|
|
2264
|
+
{
|
|
2265
|
+
"description": "nc-widget built-in widget (CnNcWidgetWidget): leaving items out (2.129.0). Other content keys stay free-form.",
|
|
2266
|
+
"if": {
|
|
2267
|
+
"properties": { "widgetKey": { "const": "nc-widget" } },
|
|
2268
|
+
"required": ["widgetKey"]
|
|
2269
|
+
},
|
|
2270
|
+
"then": {
|
|
2271
|
+
"properties": {
|
|
2272
|
+
"props": {
|
|
2273
|
+
"type": "object",
|
|
2274
|
+
"properties": {
|
|
2275
|
+
"content": {
|
|
2276
|
+
"type": "object",
|
|
2277
|
+
"properties": {
|
|
2278
|
+
"excludeApps": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Apps whose items are left out (an item's app is its own app field, else the /apps/<app>/ part of its link or icon), e.g. [\"systemtags\"]." },
|
|
2279
|
+
"excludeTypes": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Item types that are left out." }
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
},
|
|
2238
2287
|
{
|
|
2239
2288
|
"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.",
|
|
2240
2289
|
"if": {
|
|
@@ -3815,7 +3864,7 @@
|
|
|
3815
3864
|
"description": "Name a registered NON-OBJECT entity collection for a type='index' page, instead of register+schema. Deliberately NOT called `source`: that key is already taken on page config and is polymorphic (a URL string in some manifests, an object with `params` in others), so reusing it would give one key two meanings and break six deployed shillinq pages. A flow definition is deliberately not an OpenRegister object, so an index had nothing to bind to and such lists became bespoke type='custom' pages. With a source the index loads the list itself. Takes precedence over register/schema; non-empty `objects` still wins over both. Registered sources live in src/composables/indexSources.js. `tasks` lists the viewer's task inbox from OpenRegister's flow-tasks read (cn-tasks-entity-source). `organisations` lists the OpenRegister organisations the viewer belongs to. On a type='detail' page a source that reads one record (today `organisations`) loads the record whose id is the route's objectId, instead of register+schema, and feeds the header, pills, widgets and @objectId / @object.* tokens like a fetched object (organisations-entity-source)."
|
|
3816
3865
|
},
|
|
3817
3866
|
"bulkBandAlways": { "type": "boolean", "default": false, "description": "type='index', board look: draw the bulk band (its actions and hint) before a selection too, its buttons disabled until rows are selected (DqZaken, DqWerkbord). Maps to CnIndexPage `bulkBandAlways`." },
|
|
3818
|
-
"quickFilterPlacement": { "type": "string", "enum": ["", "toolbar"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). Maps to CnIndexPage `quickFilterPlacement`." },
|
|
3867
|
+
"quickFilterPlacement": { "type": "string", "enum": ["", "toolbar", "row"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). `row` puts them on a row of their own above the toolbar, wrapping onto more lines. Maps to CnIndexPage `quickFilterPlacement`." },
|
|
3819
3868
|
"rowClass": {
|
|
3820
3869
|
"type": "array",
|
|
3821
3870
|
"description": "type='index': per-row CSS class rules, compiled into the table's rowClass function as on the object-table widget. Each rule { when: { field, op?, value }, class } adds `class` to a row whose `field` (a dot-path) compares to `value` with `op` (default eq; the visibleWhen operators and tokens such as `@today`, with two dates compared as moments), e.g. an overdue task `{ when: { field: \"dueAt\", op: \"lt\", value: \"@today\" }, class: \"cn-row--overdue\" }`. Maps to CnIndexPage `rowClass`.",
|
|
@@ -4081,6 +4130,7 @@
|
|
|
4081
4130
|
"additionalProperties": true,
|
|
4082
4131
|
"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`.",
|
|
4083
4132
|
"properties": {
|
|
4133
|
+
"scopeViews": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)." },
|
|
4084
4134
|
"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
4135
|
"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)." },
|
|
4086
4136
|
"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)." },
|
|
@@ -4175,7 +4225,7 @@
|
|
|
4175
4225
|
"nextStep": {
|
|
4176
4226
|
"type": "object",
|
|
4177
4227
|
"additionalProperties": false,
|
|
4178
|
-
"required": ["stages"],
|
|
4228
|
+
"anyOf": [{ "required": ["stages"] }, { "required": ["stagesFrom"] }],
|
|
4179
4229
|
"description": "type='detail': the 'what now' card above the body. For the record's stage it shows a short checklist next to the primary button. A stage without an entry shows no card. Rendered by CnNextStepCard.",
|
|
4180
4230
|
"properties": {
|
|
4181
4231
|
"field": {
|
|
@@ -4183,6 +4233,11 @@
|
|
|
4183
4233
|
"minLength": 1,
|
|
4184
4234
|
"description": "Dot-path to the stage, when it differs from `stageField`."
|
|
4185
4235
|
},
|
|
4236
|
+
"stagesFrom": {
|
|
4237
|
+
"type": "string",
|
|
4238
|
+
"minLength": 1,
|
|
4239
|
+
"description": "A field path, read like a `visibleWhen` field, whose value holds the stages in the `stages` shape, e.g. `@context.caseType.nextStep`, so each case type keeps its own stage texts and checklist. The declared `stages` apply otherwise (2.129.0)."
|
|
4240
|
+
},
|
|
4186
4241
|
"title": {
|
|
4187
4242
|
"type": "string",
|
|
4188
4243
|
"description": "i18n key / heading used by a stage that names none."
|
|
@@ -4240,7 +4295,8 @@
|
|
|
4240
4295
|
"id": { "type": "string" },
|
|
4241
4296
|
"type": { "type": "string", "minLength": 1 },
|
|
4242
4297
|
"title": { "type": "string" },
|
|
4243
|
-
"content": { "type": "object", "additionalProperties": true }
|
|
4298
|
+
"content": { "type": "object", "additionalProperties": true },
|
|
4299
|
+
"tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this card shows beside; it is hidden while another tab is open (DqZaaktypeVelden, 2.129.0)." }
|
|
4244
4300
|
}
|
|
4245
4301
|
}
|
|
4246
4302
|
]
|
|
@@ -4708,6 +4764,19 @@
|
|
|
4708
4764
|
"description": "type='index': the property keys the cards view shows as the facts list of each card, in order; defaults to the first four list columns under the board look (screens-card-parity). Maps to CnIndexPage `cardFields`.",
|
|
4709
4765
|
"items": { "type": "string", "minLength": 1 }
|
|
4710
4766
|
},
|
|
4767
|
+
"cardLayout": {
|
|
4768
|
+
"type": "object",
|
|
4769
|
+
"additionalProperties": false,
|
|
4770
|
+
"description": "type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.",
|
|
4771
|
+
"properties": {
|
|
4772
|
+
"headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
|
|
4773
|
+
"number": { "type": "string", "minLength": 1 },
|
|
4774
|
+
"title": { "type": "string", "minLength": 1 },
|
|
4775
|
+
"meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } } ] } },
|
|
4776
|
+
"footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
|
|
4777
|
+
"footerDate": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 } } }
|
|
4778
|
+
}
|
|
4779
|
+
},
|
|
4711
4780
|
"afterList": { "type": "array", "description": "type='index': cards after the list, stacked full width in the listed order, the counterpart of statRow above it (index-scope-presentation). Each entry is a widget definition resolved like statRow's. A query preset or a folder scope can set its own. Maps to CnIndexPage `afterList`.", "items": { "type": "object", "required": ["id", "type"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "subtitle": { "type": "string" }, "content": { "type": "object" }, "headerLink": { "type": "object" } } } },
|
|
4712
4781
|
"statRow": {
|
|
4713
4782
|
"type": "array",
|
package/src/utils/dueText.js
CHANGED
|
@@ -29,7 +29,8 @@ import { parseDateValue } from './dateVariant.js'
|
|
|
29
29
|
* `upcomingWeeksLabel` `{ one, other }` (from `upcomingWeeksFrom` days left,
|
|
30
30
|
* default 35, it reads the weeks left, rounded). Texts go through `translate`
|
|
31
31
|
* before `{count}` is filled. Tones: `danger` (overdue), `today`, `upcoming`,
|
|
32
|
-
* `empty`.
|
|
32
|
+
* `empty`. With `soonWithin` (days), today and a date at most that many days
|
|
33
|
+
* ahead also carry `soon: true`, drawn in the warning colour.
|
|
33
34
|
*
|
|
34
35
|
* @param {unknown} value A Date, a timestamp or an ISO string.
|
|
35
36
|
* @param {object} [config] The `due` config.
|
|
@@ -54,9 +55,23 @@ export function resolveDue(value, config, translate = (text) => text, now = new
|
|
|
54
55
|
}
|
|
55
56
|
if (days === 0) {
|
|
56
57
|
const label = typeof cfg.todayLabel === 'string' && cfg.todayLabel !== '' ? translate(cfg.todayLabel) : t('nextcloud-vue', 'Due today')
|
|
57
|
-
return { text: label, tone: 'today' }
|
|
58
|
+
return withSoon({ text: label, tone: 'today' }, 0, cfg)
|
|
58
59
|
}
|
|
59
|
-
return { text: upcomingText(-days, cfg, translate) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }
|
|
60
|
+
return withSoon({ text: upcomingText(-days, cfg, translate) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }, -days, cfg)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Marks a due date within `soonWithin` days as soon.
|
|
65
|
+
*
|
|
66
|
+
* @param {{text: string, tone: string}} result The due text and tone.
|
|
67
|
+
* @param {number} left Whole days left, 0 for today.
|
|
68
|
+
* @param {object} cfg The `due` config.
|
|
69
|
+
* @return {{text: string, tone: string, soon?: boolean}} The result, with `soon: true` when it is due soon.
|
|
70
|
+
* @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon
|
|
71
|
+
*/
|
|
72
|
+
function withSoon(result, left, cfg) {
|
|
73
|
+
const within = Number(cfg.soonWithin)
|
|
74
|
+
return Number.isFinite(within) && within >= 0 && left <= within ? { ...result, soon: true } : result
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
/**
|
package/src/utils/sortOrder.js
CHANGED
|
@@ -56,3 +56,13 @@ export function sameSortKeys(a, b) {
|
|
|
56
56
|
return left.length === right.length
|
|
57
57
|
&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The same keys, each direction turned round.
|
|
62
|
+
*
|
|
63
|
+
* @param {Array<{key: string, order?: string}>} keys The sort keys.
|
|
64
|
+
* @return {Array<{key: string, order: 'asc'|'desc'}>} The reversed keys.
|
|
65
|
+
*/
|
|
66
|
+
export function reverseSortKeys(keys) {
|
|
67
|
+
return (Array.isArray(keys) ? keys : []).map((entry) => ({ key: entry.key, order: entry.order === 'desc' ? 'asc' : 'desc' }))
|
|
68
|
+
}
|