@conduction/nextcloud-vue 0.1.0-beta.3 → 0.1.0-beta.5
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/README.md +226 -226
- package/dist/nextcloud-vue.cjs +67614 -0
- package/dist/nextcloud-vue.cjs.js +58386 -6112
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.map +1 -0
- package/dist/nextcloud-vue.css +1819 -285
- package/dist/nextcloud-vue.esm.js +58342 -6088
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +82 -62
- package/src/components/CnActionsBar/CnActionsBar.vue +17 -7
- package/src/components/CnActionsBar/index.js +1 -1
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
- package/src/components/CnAdvancedFormDialog/index.js +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +1 -1
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/index.js +1 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
- package/src/components/CnChartWidget/index.js +1 -0
- package/src/components/CnConfigurationCard/index.js +1 -1
- package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -250
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
- package/src/components/CnDashboardGrid/index.js +1 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
- package/src/components/CnDashboardPage/index.js +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +1 -1
- package/src/components/CnDataTable/index.js +1 -1
- package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -170
- package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
- package/src/components/CnDetailCard/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +285 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +9 -1
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +302 -11
- package/src/components/CnIcon/index.js +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +71 -3
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +121 -102
- package/src/components/CnIndexSidebar/index.js +1 -1
- package/src/components/CnItemCard/CnItemCard.vue +132 -0
- package/src/components/CnItemCard/index.js +1 -0
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/index.js +1 -1
- package/src/components/CnMassImportDialog/index.js +1 -1
- package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
- package/src/components/CnNoteCard/index.js +1 -0
- package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
- package/src/components/CnNotesCard/index.js +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
- package/src/components/CnObjectCard/index.js +1 -1
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
- package/src/components/CnObjectSidebar/index.js +1 -0
- package/src/components/CnPageHeader/index.js +1 -1
- package/src/components/CnPagination/index.js +1 -1
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
- package/src/components/CnRowActions/CnRowActions.vue +25 -3
- package/src/components/CnRowActions/index.js +1 -1
- package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
- package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
- package/src/components/CnSchemaFormDialog/index.js +1 -0
- package/src/components/CnSettingsCard/index.js +1 -1
- package/src/components/CnSettingsSection/index.js +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +62 -8
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatusBadge/index.js +1 -1
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
- package/src/components/CnTabbedFormDialog/index.js +1 -0
- package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
- package/src/components/CnTasksCard/index.js +1 -0
- package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
- package/src/components/CnTileWidget/index.js +1 -0
- package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
- package/src/components/CnTimelineStages/index.js +1 -0
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
- package/src/components/CnUserActionMenu/index.js +1 -0
- package/src/components/CnVersionInfoCard/index.js +1 -1
- package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
- package/src/components/CnWidgetRenderer/index.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
- package/src/components/CnWidgetWrapper/index.js +1 -0
- package/src/components/index.js +43 -29
- package/src/composables/index.js +4 -3
- package/src/composables/useDashboardView.js +240 -0
- package/src/composables/useDetailView.js +289 -132
- package/src/composables/useListView.js +363 -362
- package/src/composables/useSubResource.js +142 -142
- package/src/constants/metadata.js +30 -30
- package/src/css/CnSchemaFormDialog.css +546 -0
- package/src/css/__sample_nextcloud_tokens.css +110 -0
- package/src/css/actions-bar.css +48 -48
- package/src/css/badge.css +51 -51
- package/src/css/card.css +128 -128
- package/src/css/dashboard.css +70 -0
- package/src/css/detail-page.css +168 -0
- package/src/css/detail.css +68 -68
- package/src/css/index-page.css +44 -32
- package/src/css/index-sidebar.css +193 -187
- package/src/css/index.css +16 -12
- package/src/css/layout.css +90 -90
- package/src/css/page-header.css +33 -33
- package/src/css/pagination.css +72 -72
- package/src/css/table.css +142 -142
- package/src/css/timeline-stages.css +218 -0
- package/src/css/utilities.css +46 -46
- package/src/index.js +72 -53
- package/src/store/createSubResourcePlugin.js +135 -135
- package/src/store/index.js +3 -3
- package/src/store/plugins/auditTrails.js +17 -17
- package/src/store/plugins/files.js +250 -186
- package/src/store/plugins/index.js +7 -5
- package/src/store/plugins/lifecycle.js +180 -180
- package/src/store/plugins/relations.js +68 -68
- package/src/store/plugins/search.js +372 -0
- package/src/store/plugins/selection.js +104 -0
- package/src/store/useObjectStore.js +829 -686
- package/src/types/auditTrail.d.ts +32 -32
- package/src/types/file.d.ts +23 -23
- package/src/types/index.d.ts +35 -35
- package/src/types/notification.d.ts +36 -36
- package/src/types/object.d.ts +40 -40
- package/src/types/organisation.d.ts +41 -41
- package/src/types/register.d.ts +25 -25
- package/src/types/schema.d.ts +39 -39
- package/src/types/shared.d.ts +79 -79
- package/src/types/source.d.ts +14 -14
- package/src/types/task.d.ts +31 -31
- package/src/utils/errors.js +96 -96
- package/src/utils/headers.js +68 -50
- package/src/utils/id.js +13 -0
- package/src/utils/index.js +3 -3
- package/src/utils/schema.js +422 -419
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The type slug used internally by the search plugin.
|
|
5
|
+
* Consumers can import this constant to avoid hard-coding the string 'search'.
|
|
6
|
+
*
|
|
7
|
+
* @type {string}
|
|
8
|
+
*/
|
|
9
|
+
export const SEARCH_TYPE = 'search'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build the API URL for fetching a register by ID.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} registerId OpenRegister register ID
|
|
15
|
+
* @return {string} Full API URL path
|
|
16
|
+
*/
|
|
17
|
+
export function getRegisterApiUrl(registerId) {
|
|
18
|
+
return prefixUrl(`/apps/openregister/api/registers/${registerId}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build the API URL for fetching a schema by ID.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} schemaId OpenRegister schema ID
|
|
25
|
+
* @return {string} Full API URL path
|
|
26
|
+
*/
|
|
27
|
+
export function getSchemaApiUrl(schemaId) {
|
|
28
|
+
return prefixUrl(`/apps/openregister/api/schemas/${schemaId}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Search plugin for the object store.
|
|
33
|
+
*
|
|
34
|
+
* Adds a dedicated search context to the store: a separate collection, pagination,
|
|
35
|
+
* loading state, schema, register, and facet cache — all scoped to the current
|
|
36
|
+
* `searchParams`. Unlike the main type-keyed collections, the search collection is
|
|
37
|
+
* a single slot that is refetched whenever `refetchSearchCollection` is called.
|
|
38
|
+
*
|
|
39
|
+
* The plugin owns two URL-builder helpers (`getRegisterApiUrl`, `getSchemaApiUrl`)
|
|
40
|
+
* that are also exported at module level for use outside the store.
|
|
41
|
+
*
|
|
42
|
+
* State added:
|
|
43
|
+
* - `searchParams` — Query parameters for the current search (register, schema, filters, …)
|
|
44
|
+
* - `searchVisibleColumns` — Column keys visible in the search results table
|
|
45
|
+
*
|
|
46
|
+
* Getters added:
|
|
47
|
+
* - `searchCollection` — Array of search result objects
|
|
48
|
+
* - `searchPagination` — `{ total, page, pages, limit }` for the last fetch
|
|
49
|
+
* - `searchLoading` — `true` while a fetch is in progress
|
|
50
|
+
* - `searchSchema` — Schema object for the current search register/schema pair
|
|
51
|
+
* - `searchRegister` — Register object for the current search register/schema pair
|
|
52
|
+
* - `searchFacets` — Facet data in CnIndexSidebar-compatible format
|
|
53
|
+
*
|
|
54
|
+
* Actions added:
|
|
55
|
+
* - `setSearchParams(params)` — Update search params (clears stale schema/register cache)
|
|
56
|
+
* - `setSearchVisibleColumns(columns)` — Replace the visible columns list
|
|
57
|
+
* - `clearSearchCollection()` — Reset collection, pagination, and facets
|
|
58
|
+
* - `refetchSearchCollection()` — Fetch collection using current `searchParams`
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* import { createObjectStore, searchPlugin, SEARCH_TYPE } from '@conduction/nextcloud-vue'
|
|
62
|
+
*
|
|
63
|
+
* const useMyStore = createObjectStore('myapp', {
|
|
64
|
+
* plugins: [searchPlugin()],
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* const store = useMyStore()
|
|
68
|
+
*
|
|
69
|
+
* // Set params and fetch
|
|
70
|
+
* store.setSearchParams({ register: 'reg-1', schema: 'schema-1', _search: 'foo' })
|
|
71
|
+
* await store.refetchSearchCollection()
|
|
72
|
+
*
|
|
73
|
+
* // Read results
|
|
74
|
+
* console.log(store.searchCollection) // [{ id: '…', … }, …]
|
|
75
|
+
* console.log(store.searchPagination) // { total: 42, page: 1, pages: 3, limit: 20 }
|
|
76
|
+
* console.log(store.searchSchema) // { title: '…', properties: { … } }
|
|
77
|
+
*
|
|
78
|
+
* // Clear
|
|
79
|
+
* store.clearSearchCollection()
|
|
80
|
+
*
|
|
81
|
+
* @return {object} Plugin definition
|
|
82
|
+
*/
|
|
83
|
+
export function searchPlugin() {
|
|
84
|
+
return {
|
|
85
|
+
name: 'search',
|
|
86
|
+
|
|
87
|
+
state: () => ({
|
|
88
|
+
/**
|
|
89
|
+
* Query parameters for the active search.
|
|
90
|
+
* Must include `register` and `schema` for `refetchSearchCollection` to work.
|
|
91
|
+
* All other keys are forwarded as query-string parameters (e.g. `_search`, `_page`).
|
|
92
|
+
* @type {object}
|
|
93
|
+
*/
|
|
94
|
+
searchParams: {},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Column keys that are visible in the search results table.
|
|
98
|
+
* @type {string[]}
|
|
99
|
+
*/
|
|
100
|
+
searchVisibleColumns: [],
|
|
101
|
+
|
|
102
|
+
/** @private @type {Array} */
|
|
103
|
+
_searchCollection: [],
|
|
104
|
+
|
|
105
|
+
/** @private @type {{ total: number, page: number, pages: number, limit: number }} */
|
|
106
|
+
_searchPagination: { total: 0, page: 1, pages: 1, limit: 20 },
|
|
107
|
+
|
|
108
|
+
/** @private @type {boolean} */
|
|
109
|
+
_searchLoading: false,
|
|
110
|
+
|
|
111
|
+
/** @private @type {object|null} */
|
|
112
|
+
_searchSchema: null,
|
|
113
|
+
|
|
114
|
+
/** @private @type {object|null} */
|
|
115
|
+
_searchRegister: null,
|
|
116
|
+
|
|
117
|
+
/** @private @type {object} */
|
|
118
|
+
_searchFacets: {},
|
|
119
|
+
|
|
120
|
+
/** @private @type {number} Request sequence counter to prevent race conditions */
|
|
121
|
+
_searchRequestId: 0,
|
|
122
|
+
}),
|
|
123
|
+
|
|
124
|
+
getters: {
|
|
125
|
+
/**
|
|
126
|
+
* The current search result objects.
|
|
127
|
+
* @param {object} state
|
|
128
|
+
* @return {Array}
|
|
129
|
+
*/
|
|
130
|
+
searchCollection: (state) => state._searchCollection,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pagination state for the last search fetch.
|
|
134
|
+
* @param {object} state
|
|
135
|
+
* @return {{ total: number, page: number, pages: number, limit: number }}
|
|
136
|
+
*/
|
|
137
|
+
searchPagination: (state) => state._searchPagination,
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* True while a search fetch is in progress.
|
|
141
|
+
* @param {object} state
|
|
142
|
+
* @return {boolean}
|
|
143
|
+
*/
|
|
144
|
+
searchLoading: (state) => state._searchLoading,
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The schema object for the current search register/schema pair.
|
|
148
|
+
* Populated automatically by `refetchSearchCollection`.
|
|
149
|
+
* @param {object} state
|
|
150
|
+
* @return {object|null}
|
|
151
|
+
*/
|
|
152
|
+
searchSchema: (state) => state._searchSchema,
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The register object for the current search register/schema pair.
|
|
156
|
+
* Populated automatically by `refetchSearchCollection`.
|
|
157
|
+
* @param {object} state
|
|
158
|
+
* @return {object|null}
|
|
159
|
+
*/
|
|
160
|
+
searchRegister: (state) => state._searchRegister,
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Facet data from the last search fetch, in CnIndexSidebar-compatible format:
|
|
164
|
+
* `{ fieldName: { values: [{ value, count }] } }`.
|
|
165
|
+
* @param {object} state
|
|
166
|
+
* @return {object}
|
|
167
|
+
*/
|
|
168
|
+
searchFacets: (state) => state._searchFacets,
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
actions: {
|
|
172
|
+
/**
|
|
173
|
+
* Update the search parameters.
|
|
174
|
+
* If `register` or `schema` changes, the cached register/schema objects are
|
|
175
|
+
* cleared so the next `refetchSearchCollection` re-fetches them.
|
|
176
|
+
*
|
|
177
|
+
* @param {object} params New search params. Must include `register` and `schema`.
|
|
178
|
+
*/
|
|
179
|
+
setSearchParams(params) {
|
|
180
|
+
if (params.register !== this.searchParams.register) {
|
|
181
|
+
this._searchRegister = null
|
|
182
|
+
}
|
|
183
|
+
if (params.schema !== this.searchParams.schema) {
|
|
184
|
+
this._searchSchema = null
|
|
185
|
+
}
|
|
186
|
+
this.searchParams = { ...params }
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Merge the given properties into the current search parameters.
|
|
191
|
+
* Only the provided keys are overwritten; all other keys remain unchanged.
|
|
192
|
+
* If `register` or `schema` changes, the cached register/schema objects are
|
|
193
|
+
* cleared so the next `refetchSearchCollection` re-fetches them.
|
|
194
|
+
*
|
|
195
|
+
* @param {object} params Partial search params to merge
|
|
196
|
+
*/
|
|
197
|
+
updateSearchParams(params) {
|
|
198
|
+
if ('register' in params && params.register !== this.searchParams.register) {
|
|
199
|
+
this._searchRegister = null
|
|
200
|
+
}
|
|
201
|
+
if ('schema' in params && params.schema !== this.searchParams.schema) {
|
|
202
|
+
this._searchSchema = null
|
|
203
|
+
}
|
|
204
|
+
this.searchParams = { ...this.searchParams, ...params }
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Replace the list of visible column keys for the search results table.
|
|
209
|
+
*
|
|
210
|
+
* @param {string[]} columns Column key array
|
|
211
|
+
*/
|
|
212
|
+
setSearchVisibleColumns(columns) {
|
|
213
|
+
this.searchVisibleColumns = Array.isArray(columns) ? columns : []
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Clear the search collection, pagination, and facets.
|
|
218
|
+
* Does not reset `searchParams` or `searchVisibleColumns`.
|
|
219
|
+
*/
|
|
220
|
+
clearSearchCollection() {
|
|
221
|
+
this._searchCollection = []
|
|
222
|
+
this._searchPagination = { total: 0, page: 1, pages: 1, limit: 20 }
|
|
223
|
+
this._searchFacets = {}
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Fetch the search collection using the current `searchParams`.
|
|
228
|
+
* `searchParams` must include `register` and `schema`; all other keys are
|
|
229
|
+
* forwarded as query-string parameters to the objects endpoint.
|
|
230
|
+
*
|
|
231
|
+
* Side-effects:
|
|
232
|
+
* - Fetches and caches `_searchSchema` and `_searchRegister` (non-blocking,
|
|
233
|
+
* only when not already cached).
|
|
234
|
+
* - Updates `_searchCollection`, `_searchPagination`, and `_searchFacets`.
|
|
235
|
+
*
|
|
236
|
+
* @return {Promise<Array>} The fetched collection (empty array on error)
|
|
237
|
+
*/
|
|
238
|
+
async refetchSearchCollection() {
|
|
239
|
+
const { register, schema, ...queryParams } = this.searchParams
|
|
240
|
+
|
|
241
|
+
if (!register || !schema) {
|
|
242
|
+
console.warn('[searchPlugin] refetchSearchCollection called without register/schema in searchParams')
|
|
243
|
+
return []
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Increment request counter to detect stale responses
|
|
247
|
+
const requestId = ++this._searchRequestId
|
|
248
|
+
|
|
249
|
+
this._searchLoading = true
|
|
250
|
+
|
|
251
|
+
// Auto-register the type so saveObject/deleteObject work
|
|
252
|
+
const type = this.createObjectTypeSlug(register, schema)
|
|
253
|
+
if (type && !this.objectTypes.includes(type)) {
|
|
254
|
+
this.registerObjectType(type, schema, register)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Kick off schema/register fetches in parallel (non-blocking)
|
|
258
|
+
if (!this._searchSchema) {
|
|
259
|
+
this._fetchSearchSchema(schema)
|
|
260
|
+
}
|
|
261
|
+
if (!this._searchRegister) {
|
|
262
|
+
this._fetchSearchRegister(register)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const baseUrl = this._options?.baseUrl || '/apps/openregister/api/objects'
|
|
267
|
+
const url = `${baseUrl}/${register}/${schema}` + buildQueryString(queryParams)
|
|
268
|
+
|
|
269
|
+
const response = await fetch(url, {
|
|
270
|
+
method: 'GET',
|
|
271
|
+
headers: buildHeaders(),
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// A newer request was fired while this one was in-flight — discard
|
|
275
|
+
if (requestId !== this._searchRequestId) {
|
|
276
|
+
return []
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
console.error('[searchPlugin] Failed to fetch search collection:', response.status)
|
|
281
|
+
return []
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const data = await response.json()
|
|
285
|
+
|
|
286
|
+
// Re-check after JSON parsing (also async)
|
|
287
|
+
if (requestId !== this._searchRequestId) {
|
|
288
|
+
return []
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const results = data.results || data
|
|
292
|
+
|
|
293
|
+
this._searchCollection = results
|
|
294
|
+
this._searchPagination = {
|
|
295
|
+
total: data.total || results.length,
|
|
296
|
+
page: data.page || 1,
|
|
297
|
+
pages: data.pages || 1,
|
|
298
|
+
limit: queryParams._limit || 20,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (data.facets) {
|
|
302
|
+
const transformed = {}
|
|
303
|
+
for (const [key, facet] of Object.entries(data.facets)) {
|
|
304
|
+
if (facet.buckets || facet.data?.buckets) {
|
|
305
|
+
const buckets = facet.buckets || facet.data.buckets
|
|
306
|
+
transformed[key] = {
|
|
307
|
+
values: buckets.map((b) => ({
|
|
308
|
+
value: b.key ?? b.value,
|
|
309
|
+
count: b.count || 0,
|
|
310
|
+
})),
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
this._searchFacets = transformed
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return results
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('[searchPlugin] Error fetching search collection:', error)
|
|
320
|
+
return []
|
|
321
|
+
} finally {
|
|
322
|
+
// Only clear loading if this is still the latest request
|
|
323
|
+
if (requestId === this._searchRequestId) {
|
|
324
|
+
this._searchLoading = false
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Fetch and cache the schema object for the given schema ID.
|
|
331
|
+
* Internal — called by `refetchSearchCollection`.
|
|
332
|
+
*
|
|
333
|
+
* @param {string} schemaId OpenRegister schema ID
|
|
334
|
+
* @return {Promise<void>}
|
|
335
|
+
*/
|
|
336
|
+
async _fetchSearchSchema(schemaId) {
|
|
337
|
+
try {
|
|
338
|
+
const response = await fetch(getSchemaApiUrl(schemaId), {
|
|
339
|
+
method: 'GET',
|
|
340
|
+
headers: buildHeaders(),
|
|
341
|
+
})
|
|
342
|
+
if (response.ok) {
|
|
343
|
+
this._searchSchema = await response.json()
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
// Non-critical — searchSchema stays null
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Fetch and cache the register object for the given register ID.
|
|
352
|
+
* Internal — called by `refetchSearchCollection`.
|
|
353
|
+
*
|
|
354
|
+
* @param {string} registerId OpenRegister register ID
|
|
355
|
+
* @return {Promise<void>}
|
|
356
|
+
*/
|
|
357
|
+
async _fetchSearchRegister(registerId) {
|
|
358
|
+
try {
|
|
359
|
+
const response = await fetch(getRegisterApiUrl(registerId), {
|
|
360
|
+
method: 'GET',
|
|
361
|
+
headers: buildHeaders(),
|
|
362
|
+
})
|
|
363
|
+
if (response.ok) {
|
|
364
|
+
this._searchRegister = await response.json()
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
// Non-critical — searchRegister stays null
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection plugin for the object store.
|
|
3
|
+
*
|
|
4
|
+
* Adds selection management across all object types: select individual
|
|
5
|
+
* objects by ID, check whether an entire collection is selected, and
|
|
6
|
+
* toggle all-selected in one call.
|
|
7
|
+
*
|
|
8
|
+
* State added:
|
|
9
|
+
* - `selectedObjects` — Array of selected object IDs (strings)
|
|
10
|
+
*
|
|
11
|
+
* Getters added:
|
|
12
|
+
* - `isAllSelected(type)` — true when every object in the collection is selected
|
|
13
|
+
*
|
|
14
|
+
* Actions added:
|
|
15
|
+
* - `setSelectedObjects(ids)` — replace selection with an array of IDs
|
|
16
|
+
* - `clearSelectedObjects()` — deselect everything
|
|
17
|
+
* - `toggleSelectAllObjects(type)` — toggle between all-selected and none-selected
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* import { createObjectStore, selectionPlugin } from '@conduction/nextcloud-vue'
|
|
21
|
+
*
|
|
22
|
+
* const useMyStore = createObjectStore('myapp', {
|
|
23
|
+
* plugins: [selectionPlugin()],
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* const store = useMyStore()
|
|
27
|
+
* store.setSelectedObjects(['abc', 'def'])
|
|
28
|
+
* store.toggleSelectAllObjects('invoice')
|
|
29
|
+
* console.log(store.isAllSelected('invoice')) // true | false
|
|
30
|
+
*
|
|
31
|
+
* @return {object} Plugin definition
|
|
32
|
+
*/
|
|
33
|
+
export function selectionPlugin() {
|
|
34
|
+
return {
|
|
35
|
+
name: 'selection',
|
|
36
|
+
|
|
37
|
+
state: () => ({
|
|
38
|
+
/**
|
|
39
|
+
* IDs of currently selected objects.
|
|
40
|
+
* @type {string[]}
|
|
41
|
+
*/
|
|
42
|
+
selectedObjects: [],
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
getters: {
|
|
46
|
+
/**
|
|
47
|
+
* Check if all objects in a type's collection are selected.
|
|
48
|
+
* Returns false when the collection is empty.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} state pinia injected state
|
|
51
|
+
*
|
|
52
|
+
* @return {Function} (type: string) => boolean
|
|
53
|
+
*/
|
|
54
|
+
isAllSelected: (state) => (type) => {
|
|
55
|
+
const collection = state.collections?.[type] || []
|
|
56
|
+
if (!collection.length) return false
|
|
57
|
+
return collection.every((r) => {
|
|
58
|
+
const id = r.id ?? r['@self']?.id
|
|
59
|
+
return id != null && state.selectedObjects.includes(id)
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
actions: {
|
|
65
|
+
/**
|
|
66
|
+
* Replace the selection with the given array of IDs.
|
|
67
|
+
*
|
|
68
|
+
* @param {string[]} ids Object IDs to select
|
|
69
|
+
*/
|
|
70
|
+
setSelectedObjects(ids) {
|
|
71
|
+
this.selectedObjects = Array.isArray(ids) ? ids : []
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clear all selected objects.
|
|
76
|
+
*/
|
|
77
|
+
clearSelectedObjects() {
|
|
78
|
+
this.selectedObjects = []
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Toggle selection of all objects in a type's collection.
|
|
83
|
+
* If all objects are already selected the selection is cleared;
|
|
84
|
+
* otherwise every object in the collection is selected.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} type The registered type slug
|
|
87
|
+
*/
|
|
88
|
+
toggleSelectAllObjects(type) {
|
|
89
|
+
const collection = this.getCollection(type)
|
|
90
|
+
const ids = collection.map((r) => r.id ?? r['@self']?.id).filter(Boolean)
|
|
91
|
+
if (this.isAllSelected(type)) {
|
|
92
|
+
// Remove only this type's IDs, keep other types' selections
|
|
93
|
+
const idsSet = new Set(ids)
|
|
94
|
+
this.selectedObjects = this.selectedObjects.filter((id) => !idsSet.has(id))
|
|
95
|
+
} else {
|
|
96
|
+
// Add this type's IDs to existing selection (deduplicated)
|
|
97
|
+
const existing = new Set(this.selectedObjects)
|
|
98
|
+
for (const id of ids) existing.add(id)
|
|
99
|
+
this.selectedObjects = [...existing]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|