@conduction/nextcloud-vue 2.106.0 → 2.107.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/CnActionDialog/CnActionDialog.vue.js +9 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js.map +1 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +338 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +249 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js +2 -2
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +145 -138
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +102 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +83 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +4 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +389 -20
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -35
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +69 -5
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +38 -5
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +7 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +8 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +297 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +24 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js +9 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js +95 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js +123 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js +1 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js +13 -5
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js +3 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue.js +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js +49 -23
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js +74 -27
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js +0 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +18 -413
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +27 -214
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js +9 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js +190 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js +270 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +56 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +56 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/savedViewsList.js +235 -0
- package/dist/esm/mixins/savedViewsList.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +348 -108
- package/dist/esm/utils/actionsDispatcher.js +2 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/builtInModals.js +39 -0
- package/dist/esm/utils/builtInModals.js.map +1 -0
- package/dist/esm/utils/mapMarkerColor.js +121 -0
- package/dist/esm/utils/mapMarkerColor.js.map +1 -0
- package/dist/esm/utils/mergeManifestDelta.js +4 -0
- package/dist/esm/utils/mergeManifestDelta.js.map +1 -1
- package/dist/esm/utils/savedViewHelpers.js +59 -7
- package/dist/esm/utils/savedViewHelpers.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +154 -0
- package/dist/esm/utils/savedViewMenu.js.map +1 -0
- package/dist/esm/utils/searchGroupSharees.js +50 -15
- package/dist/esm/utils/searchGroupSharees.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 +13351 -10855
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +348 -108
- package/l10n/en.json +56 -1
- package/l10n/nl.json +56 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +545 -0
- package/src/components/CnActionDialog/index.js +12 -0
- package/src/components/CnAppNav/CnAppNav.vue +17 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +102 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +86 -7
- package/src/components/CnIndexPage/CnIndexPage.vue +409 -23
- package/src/components/CnIndexPage/useSelfFetchList.js +21 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +91 -1
- package/src/components/CnMapWidget/CnMapWidget.vue +16 -1
- package/src/components/CnPartyPicker/CnPartyPicker.vue +317 -0
- package/src/components/CnPartyPicker/index.js +11 -0
- package/src/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue +192 -0
- package/src/components/CnSavedViewEditDialog/index.js +11 -0
- package/src/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue +21 -6
- package/src/components/CnSavedViewShareFields/CnSavedViewShareFields.vue +130 -37
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +36 -599
- package/src/components/CnSavedViewsPanel/CnSavedViewsPanel.vue +459 -0
- package/src/components/CnSavedViewsPanel/index.js +11 -0
- package/src/components/index.js +4 -0
- package/src/css/index-page.css +16 -0
- package/src/css/look-board-index.css +5 -3
- package/src/css/look-board.css +18 -3
- package/src/index.js +4 -0
- package/src/mixins/savedViewsList.js +231 -0
- package/src/schemas/app-manifest-v2.schema.json +98 -3
- package/src/utils/actionsDispatcher.js +2 -2
- package/src/utils/builtInModals.js +35 -0
- package/src/utils/mapMarkerColor.js +117 -0
- package/src/utils/mergeManifestDelta.js +4 -0
- package/src/utils/savedViewHelpers.js +58 -6
- package/src/utils/savedViewMenu.js +150 -0
- package/src/utils/searchGroupSharees.js +49 -14
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* savedViewsList — what the saved-views panel and the toolbar chips share:
|
|
6
|
+
* the views as a tree with the defaults first, the sections, the labels,
|
|
7
|
+
* counts and access. Presentational only; the page owns fetching and writes.
|
|
8
|
+
*
|
|
9
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-6
|
|
10
|
+
*/
|
|
11
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
12
|
+
import { buildViewTree, labelsInUse, VIEW_GROUPS } from '../utils/buildViewTree.js'
|
|
13
|
+
import { viewAccess } from '../utils/savedViewHelpers.js'
|
|
14
|
+
|
|
15
|
+
export const savedViewsList = {
|
|
16
|
+
props: {
|
|
17
|
+
/** Views to list (View API objects from `GET /apps/openregister/api/views`, defaults included). */
|
|
18
|
+
views: {
|
|
19
|
+
type: Array,
|
|
20
|
+
default: () => [],
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/** True while the parent is fetching the view list. */
|
|
24
|
+
loading: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: false,
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/** The signed-in NC user id; a view without `@self.access` is the owner's when it matches. */
|
|
30
|
+
currentUserId: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: '',
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/** How deep the tree indents before it flattens (`savedViewTree.maxDepth`). */
|
|
36
|
+
maxDepth: {
|
|
37
|
+
type: Number,
|
|
38
|
+
default: 3,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* How many records each view matches, keyed by view id (or slug). `null`
|
|
43
|
+
* shows no counts.
|
|
44
|
+
*
|
|
45
|
+
* @type {{[viewId: string]: number}|null}
|
|
46
|
+
*/
|
|
47
|
+
counts: {
|
|
48
|
+
type: Object,
|
|
49
|
+
default: null,
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/** The id (or slug) of the applied view. */
|
|
53
|
+
selectedViewId: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: '',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
data() {
|
|
60
|
+
return {
|
|
61
|
+
/** The label currently filtering the list, or '' for all of them. */
|
|
62
|
+
labelFilter: '',
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
computed: {
|
|
67
|
+
/** @return {string} The seeded group name, for templates. */
|
|
68
|
+
SEEDED_GROUP() {
|
|
69
|
+
return VIEW_GROUPS.SEEDED
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The views in tree order, defaults first, filtered by the active label.
|
|
74
|
+
*
|
|
75
|
+
* @return {Array<object>} Rows of `{ view, depth, group, orphaned }`.
|
|
76
|
+
*/
|
|
77
|
+
rows() {
|
|
78
|
+
return buildViewTree({ views: this.views, labelFilter: this.labelFilter, maxDepth: this.maxDepth })
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The rows in their sections: one uncaptioned section when nothing is
|
|
83
|
+
* shared and nothing is a default, else Default views, My views and
|
|
84
|
+
* Shared with me.
|
|
85
|
+
*
|
|
86
|
+
* @return {Array<{key: string, label: string, rows: Array<object>}>} The sections.
|
|
87
|
+
* @spec openspec/changes/saved-views-shared-by-role/tasks.md#task-1
|
|
88
|
+
*/
|
|
89
|
+
sections() {
|
|
90
|
+
const shared = this.rows.filter((r) => r.group !== VIEW_GROUPS.SEEDED && this.accessOf(r) !== 'owner')
|
|
91
|
+
const seeded = this.rows.filter((r) => r.group === VIEW_GROUPS.SEEDED)
|
|
92
|
+
if (shared.length === 0 && seeded.length === 0) {
|
|
93
|
+
return [{ key: 'all', label: '', rows: this.rows }]
|
|
94
|
+
}
|
|
95
|
+
const mine = this.rows.filter((r) => r.group !== VIEW_GROUPS.SEEDED && this.accessOf(r) === 'owner')
|
|
96
|
+
return [
|
|
97
|
+
{ key: 'seeded', label: t('nextcloud-vue', 'Default views'), rows: seeded },
|
|
98
|
+
{ key: 'mine', label: t('nextcloud-vue', 'My views'), rows: mine },
|
|
99
|
+
{ key: 'shared', label: t('nextcloud-vue', 'Shared with me'), rows: shared },
|
|
100
|
+
].filter((section) => section.rows.length > 0)
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The labels in use, read from the UNFILTERED list so choosing one keeps
|
|
105
|
+
* the others on offer.
|
|
106
|
+
*
|
|
107
|
+
* @return {Array<string>} The labels.
|
|
108
|
+
*/
|
|
109
|
+
offeredLabels() {
|
|
110
|
+
return labelsInUse(this.views)
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
methods: {
|
|
115
|
+
t,
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a view is the applied one.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} view The view.
|
|
121
|
+
* @return {boolean}
|
|
122
|
+
*/
|
|
123
|
+
isSelected(view) {
|
|
124
|
+
return this.selectedViewId !== '' && (this.selectedViewId === String(view.id) || this.selectedViewId === String(view.slug))
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* What the current user may do with a row's view.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} row The row from buildViewTree().
|
|
131
|
+
* @return {'owner'|'write'|'read'} The access.
|
|
132
|
+
*/
|
|
133
|
+
accessOf(row) {
|
|
134
|
+
return viewAccess(row.view, this.currentUserId)
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The people and groups a view is shared with, as text.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} view The View API object.
|
|
141
|
+
* @return {string} The user and group ids, comma-separated; empty when none.
|
|
142
|
+
*/
|
|
143
|
+
groupsOf(view) {
|
|
144
|
+
return (Array.isArray(view.sharedWith) ? view.sharedWith : [])
|
|
145
|
+
.map((e) => e && (e.user || e.group))
|
|
146
|
+
.filter((g) => typeof g === 'string' && g !== '')
|
|
147
|
+
.join(', ')
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The name a row shows, indented to its depth with figure spaces so a
|
|
152
|
+
* screen reader gets the same shape.
|
|
153
|
+
*
|
|
154
|
+
* @param {object} row The row from buildViewTree().
|
|
155
|
+
* @param {boolean} [withGroups] Append who the view is shared with.
|
|
156
|
+
* @return {string} The name.
|
|
157
|
+
*/
|
|
158
|
+
rowName(row, withGroups = false) {
|
|
159
|
+
const groups = withGroups ? this.groupsOf(row.view) : ''
|
|
160
|
+
return `${' '.repeat(row.depth * 2)}${this.nameWithCount(row.view)}${groups !== '' ? ` · ${groups}` : ''}`
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The number of records a view matches, or null when none is known.
|
|
165
|
+
*
|
|
166
|
+
* @param {object} view The saved view.
|
|
167
|
+
* @return {number|null}
|
|
168
|
+
*/
|
|
169
|
+
countOf(view) {
|
|
170
|
+
const counts = this.counts || {}
|
|
171
|
+
let value = counts[view.id]
|
|
172
|
+
if (value === undefined || value === null) {
|
|
173
|
+
value = counts[view.slug]
|
|
174
|
+
}
|
|
175
|
+
if (value === undefined || value === null) {
|
|
176
|
+
value = view.count
|
|
177
|
+
}
|
|
178
|
+
return (typeof value === 'number' && Number.isFinite(value)) ? value : null
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A view's name, followed by its count when one is known.
|
|
183
|
+
*
|
|
184
|
+
* @param {object} view The saved view.
|
|
185
|
+
* @return {string}
|
|
186
|
+
*/
|
|
187
|
+
nameWithCount(view) {
|
|
188
|
+
const count = this.countOf(view)
|
|
189
|
+
return count === null ? view.name : `${view.name} (${count})`
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* What a row is called to somebody who cannot see the indentation: the
|
|
194
|
+
* level in words, and a note when its parent is out of reach.
|
|
195
|
+
*
|
|
196
|
+
* @param {object} row The row from buildViewTree().
|
|
197
|
+
* @return {string} The accessible label.
|
|
198
|
+
*/
|
|
199
|
+
rowLabel(row) {
|
|
200
|
+
const viewName = this.nameWithCount(row.view)
|
|
201
|
+
const name = row.depth === 0
|
|
202
|
+
? viewName
|
|
203
|
+
: t('nextcloud-vue', '{name}, level {level}', { name: viewName, level: row.depth + 1 })
|
|
204
|
+
if (row.orphaned !== true) {
|
|
205
|
+
return name
|
|
206
|
+
}
|
|
207
|
+
return t('nextcloud-vue', '{name}. Parts of this view come from one you cannot see.', { name })
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The accessible label of a label filter entry.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} label The label.
|
|
214
|
+
* @return {string}
|
|
215
|
+
*/
|
|
216
|
+
labelFilterLabel(label) {
|
|
217
|
+
return this.labelFilter === label
|
|
218
|
+
? t('nextcloud-vue', 'Stop filtering on "{label}"', { label })
|
|
219
|
+
: t('nextcloud-vue', 'Show only views labelled "{label}"', { label })
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Turn a label filter on, or off when it is already on.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} label The label.
|
|
226
|
+
*/
|
|
227
|
+
onLabelFilter(label) {
|
|
228
|
+
this.labelFilter = this.labelFilter === label ? '' : label
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
}
|
|
@@ -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.131.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -1060,7 +1060,8 @@
|
|
|
1060
1060
|
},
|
|
1061
1061
|
"seeded": {
|
|
1062
1062
|
"type": "array",
|
|
1063
|
-
"
|
|
1063
|
+
"deprecated": true,
|
|
1064
|
+
"description": "DEPRECATED since 2.129.0: declare default views with the index page's `config.defaultSavedViews`, which carries the criteria this key never had and is what the page actually lists. Nothing ever read this key, so moving off it loses nothing. Views that ship with the app. They render as their own group above the user's own, and a user may copy one but not delete it: a view the product promises is not a view one person can remove from everybody's install.",
|
|
1064
1065
|
"items": {
|
|
1065
1066
|
"type": "object",
|
|
1066
1067
|
"additionalProperties": false,
|
|
@@ -4477,6 +4478,52 @@
|
|
|
4477
4478
|
"type": "string",
|
|
4478
4479
|
"description": "type='index': which pages share this page's saved views. By default a view is shared by every page over the same register and schema and by no other, because a view is filters over one schema's fields. Set this to share views across pages over different sources, or to keep two pages over one source apart. Written into the saved view's `query.scope`; views saved before scoping stay visible everywhere. Maps to CnIndexPage `savedViewsScope`."
|
|
4479
4480
|
},
|
|
4481
|
+
"defaultSavedViews": {
|
|
4482
|
+
"type": "array",
|
|
4483
|
+
"description": "type='index': views the app ships for this page. Listed read-only above each user's own saved views, under Default views, and never stored: a new app version updates them for everybody and no user can delete one. A user may apply a default or save a copy, which they then own. `@`-tokens in `filters` (`@me`, `@today`, `@today+7d`) resolve when the default is applied; a copy keeps them. Maps to CnIndexPage `defaultSavedViews`.",
|
|
4484
|
+
"items": {
|
|
4485
|
+
"type": "object",
|
|
4486
|
+
"additionalProperties": false,
|
|
4487
|
+
"required": ["slug", "name", "filters"],
|
|
4488
|
+
"properties": {
|
|
4489
|
+
"slug": {
|
|
4490
|
+
"type": "string",
|
|
4491
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
4492
|
+
"description": "The stable name of the default. Lowercase and dash only."
|
|
4493
|
+
},
|
|
4494
|
+
"name": {
|
|
4495
|
+
"type": "string",
|
|
4496
|
+
"minLength": 1,
|
|
4497
|
+
"description": "What the filter is called in the control. Translatable."
|
|
4498
|
+
},
|
|
4499
|
+
"filters": {
|
|
4500
|
+
"type": "object",
|
|
4501
|
+
"description": "The filters, keyed by property, in the shape a quick filter's `filter` takes."
|
|
4502
|
+
},
|
|
4503
|
+
"search": {
|
|
4504
|
+
"type": "string",
|
|
4505
|
+
"description": "A search term the default applies."
|
|
4506
|
+
},
|
|
4507
|
+
"sort": {
|
|
4508
|
+
"type": "array",
|
|
4509
|
+
"description": "Sort keys, in priority order.",
|
|
4510
|
+
"items": {
|
|
4511
|
+
"type": "object",
|
|
4512
|
+
"additionalProperties": false,
|
|
4513
|
+
"required": ["key"],
|
|
4514
|
+
"properties": {
|
|
4515
|
+
"key": { "type": "string" },
|
|
4516
|
+
"order": { "type": "string", "enum": ["asc", "desc"] }
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
},
|
|
4520
|
+
"label": {
|
|
4521
|
+
"type": "string",
|
|
4522
|
+
"description": "One label for the control's label filter."
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
},
|
|
4480
4527
|
"splitCloseButton": {
|
|
4481
4528
|
"type": "boolean",
|
|
4482
4529
|
"default": true,
|
|
@@ -4574,13 +4621,32 @@
|
|
|
4574
4621
|
"description": "type='dashboard': page-level filter controls in the header. Each selection is written into the page's workspace context, so a widget reads it through a `@page.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.",
|
|
4575
4622
|
"items": {
|
|
4576
4623
|
"type": "object",
|
|
4577
|
-
"required": ["key"
|
|
4624
|
+
"required": ["key"],
|
|
4625
|
+
"anyOf": [{ "required": ["options"] }, { "required": ["optionsFrom"] }],
|
|
4578
4626
|
"additionalProperties": false,
|
|
4579
4627
|
"properties": {
|
|
4580
4628
|
"key": { "type": "string", "minLength": 1, "description": "Workspace-context key written on change (the `@page.<key>` / `@workspace.<key>` token name)." },
|
|
4581
4629
|
"label": { "type": "string", "description": "Control label (i18n key or plain text)." },
|
|
4582
4630
|
"type": { "type": "string", "enum": ["select"], "default": "select", "description": "Control type, carried over from v1." },
|
|
4583
4631
|
"control": { "type": "string", "enum": ["select", "segmented"], "default": "select", "description": "How the filter is drawn: a select, or a compact segmented group." },
|
|
4632
|
+
"clearable": { "type": "boolean", "default": false, "description": "Lets the reader clear the select back to all; `placeholder` names that state (2.130.0)." },
|
|
4633
|
+
"placeholder": { "type": "string", "description": "What a cleared select shows, e.g. \"All case types\", through the app's translate (2.130.0)." },
|
|
4634
|
+
"optionsFrom": {
|
|
4635
|
+
"type": "object",
|
|
4636
|
+
"additionalProperties": false,
|
|
4637
|
+
"description": "Read the options instead of listing them: from an app endpoint (`url`, `params`, `responsePath`) or from OpenRegister objects (`register`, `schema`, `filter`). `valueField` and `labelField` pick each option's value and label (default `id` and `title` or `name`); tokens resolve (2.130.0).",
|
|
4638
|
+
"anyOf": [{ "required": ["url"] }, { "required": ["register", "schema"] }],
|
|
4639
|
+
"properties": {
|
|
4640
|
+
"url": { "type": "string", "minLength": 1 },
|
|
4641
|
+
"params": { "type": "object", "additionalProperties": true },
|
|
4642
|
+
"responsePath": { "type": "string", "minLength": 1 },
|
|
4643
|
+
"register": { "type": "string", "minLength": 1 },
|
|
4644
|
+
"schema": { "type": "string", "minLength": 1 },
|
|
4645
|
+
"filter": { "type": "object", "additionalProperties": true },
|
|
4646
|
+
"valueField": { "type": "string", "minLength": 1 },
|
|
4647
|
+
"labelField": { "type": "string", "minLength": 1 }
|
|
4648
|
+
}
|
|
4649
|
+
},
|
|
4584
4650
|
"placement": { "type": "string", "enum": ["row", "description"], "default": "row", "description": "Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)." },
|
|
4585
4651
|
"default": { "type": ["string", "number"], "description": "Initial value seeded into the context on mount; the first option's value when omitted." },
|
|
4586
4652
|
"options": {
|
|
@@ -4916,6 +4982,35 @@
|
|
|
4916
4982
|
"type": "boolean",
|
|
4917
4983
|
"default": true,
|
|
4918
4984
|
"description": "Whether coincident markers are grouped into a cluster that spiderfies on click. ON by default here — the opposite of CnMapWidget's own default — because an index map plots whatever the filter returns, and several rows at one address is the normal case: unclustered they land on identical pixels, the topmost takes every click and the rest are unreachable with nothing on screen saying they exist. Non-point geometry skips the cluster group, so areas are unaffected. Set false for the old behaviour."
|
|
4985
|
+
},
|
|
4986
|
+
"markerColor": {
|
|
4987
|
+
"type": "object",
|
|
4988
|
+
"required": ["field"],
|
|
4989
|
+
"additionalProperties": false,
|
|
4990
|
+
"description": "Colour each marker by a field of its row. Without `source` the value is looked up in `colorMap`; with `source` the field is a reference and the referenced row's `colorField` is the colour, or the key `colorMap` maps to one. A value nothing maps to gets `default`, else the map's own colour.",
|
|
4991
|
+
"properties": {
|
|
4992
|
+
"field": { "type": "string", "description": "The row field (dotted path, also under `@self`)." },
|
|
4993
|
+
"colorMap": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Value (or source colour key) to CSS colour; `var(--x, #fallback)` is resolved for the SVG marker." },
|
|
4994
|
+
"source": {
|
|
4995
|
+
"type": "object",
|
|
4996
|
+
"required": ["colorField"],
|
|
4997
|
+
"properties": {
|
|
4998
|
+
"register": { "type": "string" },
|
|
4999
|
+
"schema": { "type": "string" },
|
|
5000
|
+
"colorField": { "type": "string" },
|
|
5001
|
+
"filter": { "type": "object" }
|
|
5002
|
+
}
|
|
5003
|
+
},
|
|
5004
|
+
"default": { "type": "string" }
|
|
5005
|
+
}
|
|
5006
|
+
},
|
|
5007
|
+
"plotAll": {
|
|
5008
|
+
"oneOf": [{ "type": "boolean" }, { "type": "integer", "minimum": 1 }],
|
|
5009
|
+
"description": "Plot every row that matches the filters instead of the current page: `true` for up to 500, or a number. When more rows match, a note under the map says the first N are shown."
|
|
5010
|
+
},
|
|
5011
|
+
"count": {
|
|
5012
|
+
"oneOf": [{ "type": "boolean" }, { "type": "string" }],
|
|
5013
|
+
"description": "A count line under the map: `true` for \"Showing {located} of {total} located\", or a template with `{located}` and `{total}` through the app's translate."
|
|
4919
5014
|
}
|
|
4920
5015
|
}
|
|
4921
5016
|
},
|
|
@@ -51,6 +51,7 @@ import { translate as t } from '@nextcloud/l10n'
|
|
|
51
51
|
import { parseDispositionFilename, triggerBlobDownload } from '../components/CnIndexPage/selfModeIO.js'
|
|
52
52
|
import { interpolateUrlTokens } from '../composables/useEndpointSource.js'
|
|
53
53
|
import { routeHref } from './actionLink.js'
|
|
54
|
+
import { resolveModalEntry } from './builtInModals.js'
|
|
54
55
|
import {
|
|
55
56
|
dropOptionalUnresolved,
|
|
56
57
|
dropOptionalUnresolvedDeep,
|
|
@@ -836,8 +837,7 @@ export function dispatchAction(action, context = {}) {
|
|
|
836
837
|
|
|
837
838
|
case 'open-modal': {
|
|
838
839
|
const target = action.target
|
|
839
|
-
const
|
|
840
|
-
const entry = registry[target]
|
|
840
|
+
const entry = resolveModalEntry(context.registry, target)
|
|
841
841
|
|
|
842
842
|
if (!entry) {
|
|
843
843
|
// eslint-disable-next-line no-console
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* Modals every app can open by name with an `open-modal` action, without
|
|
6
|
+
* registering them. An app's own registry entry of the same name wins.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/builtInModals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineAsyncComponent } from 'vue'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The built-in modals, as registry entries (`{ kind: 'modal', component }`):
|
|
15
|
+
* `CnActionDialog`, the generic summary, checklist, reason and confirm dialog,
|
|
16
|
+
* its `config` in the action's `props`. Loaded on first open.
|
|
17
|
+
*
|
|
18
|
+
* @type {Readonly<Record<string, {kind: string, component: object}>>}
|
|
19
|
+
* @spec openspec/changes/action-dialog/specs/action-dialog/spec.md#requirement-a-manifest-configures-an-action-dialog
|
|
20
|
+
*/
|
|
21
|
+
export const BUILT_IN_MODALS = Object.freeze({
|
|
22
|
+
CnActionDialog: { kind: 'modal', component: defineAsyncComponent(() => import('../components/CnActionDialog/CnActionDialog.vue')) },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The registry entry for a modal name: the app's own, else a built-in one.
|
|
27
|
+
*
|
|
28
|
+
* @param {object|null} registry The app's registry.
|
|
29
|
+
* @param {string} name The modal's name.
|
|
30
|
+
* @return {object|null} The entry, or null.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveModalEntry(registry, name) {
|
|
33
|
+
const own = registry && typeof registry === 'object' ? registry[name] : undefined
|
|
34
|
+
return own || BUILT_IN_MODALS[name] || null
|
|
35
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* mapMarkerColor — the colour of an index map's marker, read from a field of
|
|
6
|
+
* its row (`mapConfig.markerColor`).
|
|
7
|
+
*
|
|
8
|
+
* ```json
|
|
9
|
+
* "markerColor": {
|
|
10
|
+
* "field": "status",
|
|
11
|
+
* "colorMap": { "open": "#0b71ab", "closed": "var(--nl-color-grey, #696969)" },
|
|
12
|
+
* "source": { "schema": "statusType", "colorField": "colour" },
|
|
13
|
+
* "default": "#696969"
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Without `source` the field's value is looked up in `colorMap`. With
|
|
18
|
+
* `source` the field holds a reference: the referenced row's `colorField`
|
|
19
|
+
* is the colour, or the key `colorMap` maps to one ("blue" -> a CSS colour).
|
|
20
|
+
* A value nothing maps to gets `default`, else no colour (the map's own).
|
|
21
|
+
*
|
|
22
|
+
* @module utils/mapMarkerColor
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { refIdOf } from './boardReferenceColumns.js'
|
|
26
|
+
|
|
27
|
+
/** A colour that can go into a style or an SVG attribute: no quotes, no `;`, no `url(`. */
|
|
28
|
+
const SAFE_COLOR = /^(?!.*url\()[#a-zA-Z0-9(),.%\s_-]{1,120}$/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The `markerColor` block in a usable shape, or null when it names no field.
|
|
32
|
+
*
|
|
33
|
+
* @param {unknown} cfg The configured block.
|
|
34
|
+
* @return {{field: string, colorMap: object, source: (object|null), fallback: string}|null} The block.
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeMarkerColor(cfg) {
|
|
37
|
+
if (!cfg || typeof cfg !== 'object' || typeof cfg.field !== 'string' || cfg.field === '') {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
const source = cfg.source && typeof cfg.source === 'object' && typeof cfg.source.colorField === 'string' ? cfg.source : null
|
|
41
|
+
return {
|
|
42
|
+
field: cfg.field,
|
|
43
|
+
colorMap: cfg.colorMap && typeof cfg.colorMap === 'object' ? cfg.colorMap : {},
|
|
44
|
+
source,
|
|
45
|
+
fallback: typeof cfg.default === 'string' ? cfg.default : '',
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read a dotted path off a row, also under `@self`.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} row The row.
|
|
53
|
+
* @param {string} path The path.
|
|
54
|
+
* @return {unknown} The value.
|
|
55
|
+
*/
|
|
56
|
+
function valueAt(row, path) {
|
|
57
|
+
const read = (base) => path.split('.').reduce((o, k) => (o === null || o === undefined ? undefined : o[k]), base)
|
|
58
|
+
const direct = read(row)
|
|
59
|
+
return direct === undefined && row && row['@self'] ? read(row['@self']) : direct
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The marker colour of one row.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} row The row.
|
|
66
|
+
* @param {object|null} cfg The normalised block (see {@link normalizeMarkerColor}).
|
|
67
|
+
* @param {Map<string, object>} [refsById] The source rows by id, when `cfg.source` is set.
|
|
68
|
+
* @return {string} A CSS colour, or '' for the map's own.
|
|
69
|
+
*/
|
|
70
|
+
export function markerColorOf(row, cfg, refsById) {
|
|
71
|
+
if (!cfg || !row) {
|
|
72
|
+
return ''
|
|
73
|
+
}
|
|
74
|
+
const raw = valueAt(row, cfg.field)
|
|
75
|
+
let key = ''
|
|
76
|
+
if (cfg.source) {
|
|
77
|
+
const ref = refsById && refsById.get(refIdOf(raw))
|
|
78
|
+
key = ref ? String(ref[cfg.source.colorField] ?? '') : ''
|
|
79
|
+
} else if (raw !== null && raw !== undefined && typeof raw !== 'object') {
|
|
80
|
+
key = String(raw)
|
|
81
|
+
}
|
|
82
|
+
let color
|
|
83
|
+
if (key !== '' && Object.hasOwn(cfg.colorMap, key)) {
|
|
84
|
+
color = String(cfg.colorMap[key])
|
|
85
|
+
} else if (key !== '' && cfg.source) {
|
|
86
|
+
color = key
|
|
87
|
+
} else {
|
|
88
|
+
color = cfg.fallback
|
|
89
|
+
}
|
|
90
|
+
return SAFE_COLOR.test(color) ? color : ''
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve `var(--name, fallback)` to a concrete colour, for an SVG
|
|
95
|
+
* attribute, where a custom property does not resolve.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} color The colour.
|
|
98
|
+
* @param {Element} [root] The element whose custom properties count.
|
|
99
|
+
* @return {string} The colour without `var()`, or the input when it has none.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveCssColor(color, root) {
|
|
102
|
+
const match = /^var\(\s*(--[\w-]+)\s*(?:,\s*(.+))?\)$/.exec(String(color || '').trim())
|
|
103
|
+
if (!match) {
|
|
104
|
+
return color
|
|
105
|
+
}
|
|
106
|
+
let value
|
|
107
|
+
try {
|
|
108
|
+
const el = root || (typeof document !== 'undefined' ? document.documentElement : null)
|
|
109
|
+
value = el && typeof getComputedStyle === 'function' ? getComputedStyle(el).getPropertyValue(match[1]).trim() : ''
|
|
110
|
+
} catch {
|
|
111
|
+
value = ''
|
|
112
|
+
}
|
|
113
|
+
if (value !== '') {
|
|
114
|
+
return value
|
|
115
|
+
}
|
|
116
|
+
return match[2] ? resolveCssColor(match[2].trim(), root) : ''
|
|
117
|
+
}
|
|
@@ -45,6 +45,10 @@ export const KEYED_ARRAYS = Object.freeze({
|
|
|
45
45
|
// (or a backend `/api/manifest` override) can add/patch/remove individual
|
|
46
46
|
// children of a group without replacing the whole `children[]` array.
|
|
47
47
|
children: 'id',
|
|
48
|
+
// An index page's default views merge by `slug`, so the per-user delta
|
|
49
|
+
// (`/api/manifest`) can add a user's own defaults (dossiq's case type
|
|
50
|
+
// pins) without wiping the defaults the page ships.
|
|
51
|
+
defaultSavedViews: 'slug',
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
/** Reserved delta markers — never part of a base manifest. */
|
|
@@ -213,22 +213,30 @@ export function buildViewCreatePayload({ name, description, isPublic, isDefault,
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
|
-
* Clean an audience list: keep `{ group, mode }`
|
|
217
|
-
* coerce any mode but `write`
|
|
216
|
+
* Clean an audience list: keep `{ group, mode }` and `{ user, mode }` entries
|
|
217
|
+
* that name exactly one target, drop repeats, and coerce any mode but `write`
|
|
218
|
+
* to `read`.
|
|
218
219
|
*
|
|
219
220
|
* @param {unknown} sharedWith The audience as given.
|
|
220
|
-
* @return {Array<{group
|
|
221
|
+
* @return {Array<{group?: string, user?: string, mode: 'read'|'write'}>} The cleaned list.
|
|
222
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-5
|
|
221
223
|
*/
|
|
222
224
|
export function normalizeSharedWith(sharedWith) {
|
|
223
225
|
const seen = new Set()
|
|
224
226
|
const out = []
|
|
225
227
|
for (const entry of Array.isArray(sharedWith) ? sharedWith : []) {
|
|
226
228
|
const group = entry && typeof entry.group === 'string' ? entry.group : ''
|
|
227
|
-
|
|
229
|
+
const user = entry && typeof entry.user === 'string' ? entry.user : ''
|
|
230
|
+
if ((group === '') === (user === '')) {
|
|
228
231
|
continue
|
|
229
232
|
}
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
const key = user !== '' ? `user:${user}` : `group:${group}`
|
|
234
|
+
if (seen.has(key)) {
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
seen.add(key)
|
|
238
|
+
const mode = entry.mode === 'write' ? 'write' : 'read'
|
|
239
|
+
out.push(user !== '' ? { user, mode } : { group, mode })
|
|
232
240
|
}
|
|
233
241
|
return out
|
|
234
242
|
}
|
|
@@ -376,3 +384,47 @@ export function extractViewState(view) {
|
|
|
376
384
|
export function isOwnView(view, currentUserId) {
|
|
377
385
|
return !!view && !!currentUserId && view.owner === currentUserId
|
|
378
386
|
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The default views an index page declares (`config.defaultSavedViews`), as
|
|
390
|
+
* read-only views the saved-views control lists. They are never stored: the
|
|
391
|
+
* manifest installs them, so every user has the same set and none can delete
|
|
392
|
+
* one. `seeded: true` puts them in the control's default group; the query
|
|
393
|
+
* keeps its `@`-tokens, which resolve when a default is applied.
|
|
394
|
+
*
|
|
395
|
+
* @param {Array<object>|null|undefined} defaults The declared entries `{ slug, name, filters, search?, sort?, label? }`.
|
|
396
|
+
* @param {{ scope?: string, register?: string, schema?: object|string|null }} [page] The page, as `savedViewScope` takes it.
|
|
397
|
+
* @return {Array<object>} The views, in declaration order; entries without a slug or name are left out.
|
|
398
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4
|
|
399
|
+
*/
|
|
400
|
+
export function defaultFilterViews(defaults, page = {}) {
|
|
401
|
+
if (!Array.isArray(defaults)) {
|
|
402
|
+
return []
|
|
403
|
+
}
|
|
404
|
+
return defaults
|
|
405
|
+
.filter((entry) => entry && typeof entry.slug === 'string' && entry.slug !== '' && typeof entry.name === 'string' && entry.name !== '')
|
|
406
|
+
.map((entry) => {
|
|
407
|
+
const { query } = buildViewCreatePayload({
|
|
408
|
+
name: entry.name,
|
|
409
|
+
state: {
|
|
410
|
+
filters: (entry.filters && typeof entry.filters === 'object' && !Array.isArray(entry.filters)) ? entry.filters : {},
|
|
411
|
+
search: typeof entry.search === 'string' ? entry.search : '',
|
|
412
|
+
sortKeys: Array.isArray(entry.sort) ? entry.sort : [],
|
|
413
|
+
},
|
|
414
|
+
scope: savedViewScope(page),
|
|
415
|
+
register: page.register,
|
|
416
|
+
schema: page.schema,
|
|
417
|
+
})
|
|
418
|
+
return {
|
|
419
|
+
id: `default:${entry.slug}`,
|
|
420
|
+
slug: entry.slug,
|
|
421
|
+
name: entry.name,
|
|
422
|
+
owner: '',
|
|
423
|
+
seeded: true,
|
|
424
|
+
isPublic: false,
|
|
425
|
+
labels: typeof entry.label === 'string' && entry.label !== '' ? [entry.label] : [],
|
|
426
|
+
'@self': { access: 'read' },
|
|
427
|
+
query,
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
}
|