@conduction/nextcloud-vue 0.1.0-beta.3 → 0.1.0-beta.4
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.js +60455 -8755
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +2062 -528
- package/dist/nextcloud-vue.esm.js +60411 -8731
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/package.json +75 -62
- package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
- 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 +152 -152
- package/src/components/CnCardGrid/index.js +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
- 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/CnConfigurationCard.vue +77 -77
- package/src/components/CnConfigurationCard/index.js +1 -1
- 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 +349 -349
- package/src/components/CnDataTable/index.js +1 -1
- package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
- package/src/components/CnDetailCard/index.js +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
- package/src/components/CnDetailPage/index.js +1 -0
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
- package/src/components/CnFacetSidebar/index.js +1 -1
- package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
- package/src/components/CnFilterBar/index.js +1 -1
- package/src/components/CnIcon/CnIcon.vue +89 -89
- package/src/components/CnIcon/index.js +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
- package/src/components/CnIndexPage/index.js +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
- 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/CnKpiGrid.vue +89 -89
- package/src/components/CnKpiGrid/index.js +1 -1
- package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
- package/src/components/CnMassActionBar/index.js +1 -1
- package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
- package/src/components/CnMassCopyDialog/index.js +1 -1
- package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
- package/src/components/CnMassDeleteDialog/index.js +1 -1
- package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
- package/src/components/CnMassExportDialog/index.js +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
- 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 +292 -292
- 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/CnPageHeader.vue +57 -57
- package/src/components/CnPageHeader/index.js +1 -1
- package/src/components/CnPagination/CnPagination.vue +252 -252
- package/src/components/CnPagination/index.js +1 -1
- package/src/components/CnRowActions/CnRowActions.vue +73 -73
- 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/CnSettingsCard.vue +92 -92
- package/src/components/CnSettingsCard/index.js +1 -1
- package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
- package/src/components/CnSettingsSection/index.js +1 -1
- package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
- package/src/components/CnStatsBlock/index.js +1 -1
- package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
- 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/CnVersionInfoCard.vue +312 -312
- 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
|
@@ -1,686 +1,829 @@
|
|
|
1
|
-
import { defineStore } from 'pinia'
|
|
2
|
-
import { buildHeaders, buildQueryString } from '../utils/headers.js'
|
|
3
|
-
import { parseResponseError, networkError, genericError } from '../utils/errors.js'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* @
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get
|
|
126
|
-
* @
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
*
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
*
|
|
169
|
-
* @return {Function} (type: string) =>
|
|
170
|
-
*/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
* @param
|
|
198
|
-
* @
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
*
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* @param {string}
|
|
236
|
-
* @
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* @
|
|
276
|
-
* @
|
|
277
|
-
*/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* @param {string} type The
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.
|
|
432
|
-
...this.
|
|
433
|
-
[type]: {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
[type]:
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { buildHeaders, buildQueryString, prefixUrl } from '../utils/headers.js'
|
|
3
|
+
import { parseResponseError, networkError, genericError } from '../utils/errors.js'
|
|
4
|
+
import { extractId } from '../utils/id.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generic Pinia store for OpenRegister object CRUD operations.
|
|
8
|
+
*
|
|
9
|
+
* Provides a unified interface for managing objects across registers and schemas.
|
|
10
|
+
* Apps register their object types with schema/register IDs, then use type slugs
|
|
11
|
+
* for all operations. Supports plugins for sub-resources (files, audit trails, etc.).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Basic usage (CRUD only)
|
|
15
|
+
* import { useObjectStore } from '@conduction/nextcloud-vue'
|
|
16
|
+
* const store = useObjectStore()
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // With plugins
|
|
20
|
+
* import { createObjectStore, filesPlugin, auditTrailsPlugin } from '@conduction/nextcloud-vue'
|
|
21
|
+
* const useMyStore = createObjectStore('object', {
|
|
22
|
+
* plugins: [filesPlugin(), auditTrailsPlugin()],
|
|
23
|
+
* })
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const DEFAULT_STORE_ID = 'conduction-objects'
|
|
27
|
+
const DEFAULT_BASE_URL = '/apps/openregister/api/objects'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Capitalize the first letter of a string.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} str Input string
|
|
33
|
+
* @return {string} Capitalized string
|
|
34
|
+
*/
|
|
35
|
+
function capitalize(str) {
|
|
36
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Merge plugin state factories into a single state object.
|
|
41
|
+
*
|
|
42
|
+
* @param {Array} plugins Array of plugin definitions
|
|
43
|
+
* @return {object} Merged state object
|
|
44
|
+
*/
|
|
45
|
+
function mergePluginState(plugins) {
|
|
46
|
+
const merged = {}
|
|
47
|
+
for (const plugin of plugins) {
|
|
48
|
+
if (plugin.state) {
|
|
49
|
+
Object.assign(merged, plugin.state())
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return merged
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge plugin getters into a single getters object.
|
|
57
|
+
*
|
|
58
|
+
* @param {Array} plugins Array of plugin definitions
|
|
59
|
+
* @return {object} Merged getters object
|
|
60
|
+
*/
|
|
61
|
+
function mergePluginGetters(plugins) {
|
|
62
|
+
const merged = {}
|
|
63
|
+
for (const plugin of plugins) {
|
|
64
|
+
if (plugin.getters) {
|
|
65
|
+
Object.assign(merged, plugin.getters)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return merged
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Merge plugin actions into a single actions object.
|
|
73
|
+
*
|
|
74
|
+
* @param {Array} plugins Array of plugin definitions
|
|
75
|
+
* @return {object} Merged actions object
|
|
76
|
+
*/
|
|
77
|
+
function mergePluginActions(plugins) {
|
|
78
|
+
const merged = {}
|
|
79
|
+
for (const plugin of plugins) {
|
|
80
|
+
if (plugin.actions) {
|
|
81
|
+
Object.assign(merged, plugin.actions)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return merged
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Base state ──────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function baseState(baseUrl = DEFAULT_BASE_URL) {
|
|
90
|
+
return {
|
|
91
|
+
/** @type {{string: {schema: string, register: string}}} */
|
|
92
|
+
objectTypeRegistry: {},
|
|
93
|
+
/** @type {{string: Array}} */
|
|
94
|
+
collections: {},
|
|
95
|
+
/** @type {{string: {string: object}}} */
|
|
96
|
+
objects: {},
|
|
97
|
+
/** @type {{string: boolean}} */
|
|
98
|
+
loading: {},
|
|
99
|
+
/** @type {{string: import('../utils/errors.js').ApiError|null}} */
|
|
100
|
+
errors: {},
|
|
101
|
+
/** @type {{string: {total: number, page: number, pages: number, limit: number}}} */
|
|
102
|
+
pagination: {},
|
|
103
|
+
/** @type {{string: string}} */
|
|
104
|
+
searchTerms: {},
|
|
105
|
+
/** @type {{string: object|null}} */
|
|
106
|
+
schemas: {},
|
|
107
|
+
/** @type {{string: object|null}} */
|
|
108
|
+
registers: {},
|
|
109
|
+
/**
|
|
110
|
+
* Facet data per type for CnIndexSidebar: { fieldName: { values: [{value, count}] } }
|
|
111
|
+
* @type {{string: object}}
|
|
112
|
+
*/
|
|
113
|
+
facets: {},
|
|
114
|
+
/** @type {{baseUrl: string}} */
|
|
115
|
+
_options: {
|
|
116
|
+
baseUrl,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Base getters ────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
const baseGetters = {
|
|
124
|
+
/**
|
|
125
|
+
* Get all registered object type slugs.
|
|
126
|
+
* @param state
|
|
127
|
+
* @return {string[]}
|
|
128
|
+
*/
|
|
129
|
+
objectTypes: (state) => Object.keys(state.objectTypeRegistry),
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the collection array for a type.
|
|
133
|
+
* @param state
|
|
134
|
+
* @return {Function} (type: string) => Array
|
|
135
|
+
*/
|
|
136
|
+
getCollection: (state) => (type) => state.collections[type] || [],
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get a single cached object by type and ID.
|
|
140
|
+
* @param state
|
|
141
|
+
* @return {Function} (type: string, id: string) => object|null
|
|
142
|
+
*/
|
|
143
|
+
getObject: (state) => (type, id) => state.objects[type]?.[id] || null,
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Alias for getObject — check cache without fetching.
|
|
147
|
+
* @param state
|
|
148
|
+
* @return {Function} (type: string, id: string) => object|null
|
|
149
|
+
*/
|
|
150
|
+
getCachedObject: (state) => (type, id) => state.objects[type]?.[id] || null,
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if a type is currently loading.
|
|
154
|
+
* @param state
|
|
155
|
+
* @return {Function} (type: string) => boolean
|
|
156
|
+
*/
|
|
157
|
+
isLoading: (state) => (type) => state.loading[type] || false,
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get the current error for a type.
|
|
161
|
+
* @param state
|
|
162
|
+
* @return {Function} (type: string) => ApiError|null
|
|
163
|
+
*/
|
|
164
|
+
getError: (state) => (type) => state.errors[type] || null,
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get pagination state for a type.
|
|
168
|
+
* @param state
|
|
169
|
+
* @return {Function} (type: string) => {total, page, pages, limit}
|
|
170
|
+
*/
|
|
171
|
+
getPagination: (state) => (type) =>
|
|
172
|
+
state.pagination[type] || { total: 0, page: 1, pages: 1, limit: 20 },
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get the current search term for a type.
|
|
176
|
+
* @param state
|
|
177
|
+
* @return {Function} (type: string) => string
|
|
178
|
+
*/
|
|
179
|
+
getSearchTerm: (state) => (type) => state.searchTerms[type] || '',
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get a cached schema for a type.
|
|
183
|
+
* @param state
|
|
184
|
+
* @return {Function} (type: string) => object|null
|
|
185
|
+
*/
|
|
186
|
+
getSchema: (state) => (type) => state.schemas[type] || null,
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get a cached register for a type.
|
|
190
|
+
* @param state
|
|
191
|
+
* @return {Function} (type: string) => object|null
|
|
192
|
+
*/
|
|
193
|
+
getRegister: (state) => (type) => state.registers[type] || null,
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get facet data for a type (CnIndexSidebar-compatible format).
|
|
197
|
+
* @param state
|
|
198
|
+
* @return {Function} (type: string) => object
|
|
199
|
+
*/
|
|
200
|
+
getFacets: (state) => (type) => state.facets[type] || {},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Base actions ────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
const baseActions = {
|
|
206
|
+
/**
|
|
207
|
+
* Configure the store with custom options.
|
|
208
|
+
* Call once before using the store if you need a custom base URL.
|
|
209
|
+
*
|
|
210
|
+
* @param {object} options Configuration options
|
|
211
|
+
* @param {string} [options.baseUrl] Custom base URL for API calls
|
|
212
|
+
*/
|
|
213
|
+
configure(options) {
|
|
214
|
+
Object.assign(this._options, options)
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Create a standard object type slug.
|
|
219
|
+
*
|
|
220
|
+
* takes a unspecified number of props and joins them from first to left with a `-`.
|
|
221
|
+
* However it is recommended to give it 1 register and 1 schema in that order.
|
|
222
|
+
* @param {*} params - unspecified number of props
|
|
223
|
+
* @return {string}
|
|
224
|
+
*/
|
|
225
|
+
createObjectTypeSlug(...params) {
|
|
226
|
+
const contentIds = params.map((x) => extractId(x))
|
|
227
|
+
|
|
228
|
+
return contentIds.join('-')
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Register an object type for CRUD operations.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} slug Short name for the type (e.g. 'client', 'case')
|
|
235
|
+
* @param {string} schemaId OpenRegister schema ID
|
|
236
|
+
* @param {string} registerId OpenRegister register ID
|
|
237
|
+
*/
|
|
238
|
+
registerObjectType(slug, schemaId, registerId) {
|
|
239
|
+
// Replace entire objects so Vue 2 reactivity detects the change
|
|
240
|
+
// (Vue 2 cannot track new properties added to existing reactive objects)
|
|
241
|
+
this.objectTypeRegistry = { ...this.objectTypeRegistry, [slug]: { schema: schemaId, register: registerId } }
|
|
242
|
+
this.collections = { ...this.collections, [slug]: [] }
|
|
243
|
+
this.objects = { ...this.objects, [slug]: {} }
|
|
244
|
+
this.loading = { ...this.loading, [slug]: false }
|
|
245
|
+
this.errors = { ...this.errors, [slug]: null }
|
|
246
|
+
this.pagination = { ...this.pagination, [slug]: { total: 0, page: 1, pages: 1, limit: 20 } }
|
|
247
|
+
this.searchTerms = { ...this.searchTerms, [slug]: '' }
|
|
248
|
+
this.schemas = { ...this.schemas, [slug]: null }
|
|
249
|
+
this.registers = { ...this.registers, [slug]: null }
|
|
250
|
+
this.facets = { ...this.facets, [slug]: {} }
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Unregister an object type and clean up all its state.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} slug The type slug to unregister
|
|
257
|
+
*/
|
|
258
|
+
unregisterObjectType(slug) {
|
|
259
|
+
delete this.objectTypeRegistry[slug]
|
|
260
|
+
delete this.collections[slug]
|
|
261
|
+
delete this.objects[slug]
|
|
262
|
+
delete this.loading[slug]
|
|
263
|
+
delete this.errors[slug]
|
|
264
|
+
delete this.pagination[slug]
|
|
265
|
+
delete this.searchTerms[slug]
|
|
266
|
+
delete this.schemas[slug]
|
|
267
|
+
delete this.registers[slug]
|
|
268
|
+
delete this.facets[slug]
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get the type config or throw if not registered.
|
|
273
|
+
*
|
|
274
|
+
* @param {string} type The type slug
|
|
275
|
+
* @return {{schema: string, register: string}} Type configuration
|
|
276
|
+
* @throws {Error} If the type is not registered
|
|
277
|
+
*/
|
|
278
|
+
_getTypeConfig(type) {
|
|
279
|
+
const config = this.objectTypeRegistry[type]
|
|
280
|
+
if (!config) {
|
|
281
|
+
throw new Error(`Object type "${type}" is not registered in the store. Call registerObjectType('${type}', schemaId, registerId) first.`)
|
|
282
|
+
}
|
|
283
|
+
return config
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Build the API URL for a type and optional object ID.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} type The type slug
|
|
290
|
+
* @param {string|null} [id] Optional object ID
|
|
291
|
+
* @return {string} Full API URL path
|
|
292
|
+
*/
|
|
293
|
+
_buildUrl(type, id = null) {
|
|
294
|
+
const config = this._getTypeConfig(type)
|
|
295
|
+
let url = `${this._options.baseUrl}/${config.register}/${config.schema}`
|
|
296
|
+
if (id) {
|
|
297
|
+
url += `/${id}`
|
|
298
|
+
}
|
|
299
|
+
return url
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Clear the error state for a type.
|
|
304
|
+
*
|
|
305
|
+
* @param {string} type The type slug
|
|
306
|
+
*/
|
|
307
|
+
clearError(type) {
|
|
308
|
+
this.errors = { ...this.errors, [type]: null }
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Set the search term for a type.
|
|
313
|
+
*
|
|
314
|
+
* @param {string} type The type slug
|
|
315
|
+
* @param {string} term The search term
|
|
316
|
+
*/
|
|
317
|
+
setSearchTerm(type, term) {
|
|
318
|
+
this.searchTerms = { ...this.searchTerms, [type]: term }
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Clear the search term for a type.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} type The type slug
|
|
325
|
+
*/
|
|
326
|
+
clearSearchTerm(type) {
|
|
327
|
+
this.searchTerms = { ...this.searchTerms, [type]: '' }
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Fetch the schema definition for a registered type.
|
|
332
|
+
* Uses cache — only fetches once per type per session.
|
|
333
|
+
*
|
|
334
|
+
* @param {string} type The registered type slug
|
|
335
|
+
* @return {Promise<object|null>} The schema object or null on error
|
|
336
|
+
*/
|
|
337
|
+
async fetchSchema(type) {
|
|
338
|
+
const config = this._getTypeConfig(type)
|
|
339
|
+
|
|
340
|
+
if (this.schemas[type]) {
|
|
341
|
+
return this.schemas[type]
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const response = await fetch(
|
|
346
|
+
`/apps/openregister/api/schemas/${config.schema}`,
|
|
347
|
+
{ method: 'GET', headers: buildHeaders() },
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
if (!response.ok) return null
|
|
351
|
+
|
|
352
|
+
const schema = await response.json()
|
|
353
|
+
this.schemas = { ...this.schemas, [type]: schema }
|
|
354
|
+
return schema
|
|
355
|
+
} catch {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Fetch the register definition for a registered type.
|
|
362
|
+
* Uses cache — only fetches once per type per session.
|
|
363
|
+
*
|
|
364
|
+
* @param {string} type The registered type slug
|
|
365
|
+
* @return {Promise<object|null>} The register object or null on error
|
|
366
|
+
*/
|
|
367
|
+
async fetchRegister(type) {
|
|
368
|
+
const config = this._getTypeConfig(type)
|
|
369
|
+
|
|
370
|
+
if (this.registers[type]) {
|
|
371
|
+
return this.registers[type]
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const response = await fetch(
|
|
376
|
+
`/apps/openregister/api/registers/${config.register}`,
|
|
377
|
+
{ method: 'GET', headers: buildHeaders() },
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if (!response.ok) return null
|
|
381
|
+
|
|
382
|
+
const register = await response.json()
|
|
383
|
+
this.registers = { ...this.registers, [type]: register }
|
|
384
|
+
return register
|
|
385
|
+
} catch {
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Fetch a collection of objects for a registered type.
|
|
392
|
+
*
|
|
393
|
+
* @param {string} type The registered type slug
|
|
394
|
+
* @param {object} [params] Query parameters (_limit, _page, _search, _order, filters)
|
|
395
|
+
* @return {Promise<Array>} The fetched collection (also stored in state)
|
|
396
|
+
*/
|
|
397
|
+
async fetchCollection(type, params = {}) {
|
|
398
|
+
this.loading = { ...this.loading, [type]: true }
|
|
399
|
+
this.errors = { ...this.errors, [type]: null }
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
// Auto-include _facets=extend when schema has facetable properties
|
|
403
|
+
const fetchParams = { ...params }
|
|
404
|
+
if (!fetchParams._facets) {
|
|
405
|
+
const schema = this.schemas[type]
|
|
406
|
+
const hasFacetable = schema
|
|
407
|
+
&& schema.properties
|
|
408
|
+
&& Object.values(schema.properties).some((p) => p.facetable)
|
|
409
|
+
if (hasFacetable) {
|
|
410
|
+
fetchParams._facets = 'extend'
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const url = this._buildUrl(type) + buildQueryString(fetchParams)
|
|
415
|
+
|
|
416
|
+
const response = await fetch(url, {
|
|
417
|
+
method: 'GET',
|
|
418
|
+
headers: buildHeaders(),
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
423
|
+
console.error(`Error fetching ${type} collection:`, this.errors[type])
|
|
424
|
+
return []
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const data = await response.json()
|
|
428
|
+
const results = data.results || data
|
|
429
|
+
|
|
430
|
+
this.collections = { ...this.collections, [type]: results }
|
|
431
|
+
this.pagination = {
|
|
432
|
+
...this.pagination,
|
|
433
|
+
[type]: {
|
|
434
|
+
total: data.total || results.length,
|
|
435
|
+
page: data.page || 1,
|
|
436
|
+
pages: data.pages || 1,
|
|
437
|
+
limit: params._limit || 20,
|
|
438
|
+
},
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Parse facet data from API response and transform to CnIndexSidebar format
|
|
442
|
+
if (data.facets) {
|
|
443
|
+
const transformed = {}
|
|
444
|
+
for (const [key, facet] of Object.entries(data.facets)) {
|
|
445
|
+
if (facet.buckets || facet.data?.buckets) {
|
|
446
|
+
const buckets = facet.buckets || facet.data.buckets
|
|
447
|
+
transformed[key] = {
|
|
448
|
+
values: buckets.map((b) => ({
|
|
449
|
+
value: b.key ?? b.value,
|
|
450
|
+
count: b.count || 0,
|
|
451
|
+
})),
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
this.facets = { ...this.facets, [type]: transformed }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return results
|
|
459
|
+
} catch (error) {
|
|
460
|
+
this.errors = {
|
|
461
|
+
...this.errors,
|
|
462
|
+
[type]: error.name === 'TypeError'
|
|
463
|
+
? networkError(error)
|
|
464
|
+
: genericError(error),
|
|
465
|
+
}
|
|
466
|
+
console.error(`Error fetching ${type} collection:`, error)
|
|
467
|
+
return []
|
|
468
|
+
} finally {
|
|
469
|
+
this.loading = { ...this.loading, [type]: false }
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Fetch a single object by type and ID.
|
|
475
|
+
*
|
|
476
|
+
* @param {string} type The registered type slug
|
|
477
|
+
* @param {string} id The object ID or UUID
|
|
478
|
+
* @return {Promise<object|null>} The fetched object (also cached in state)
|
|
479
|
+
*/
|
|
480
|
+
async fetchObject(type, id) {
|
|
481
|
+
this.loading = { ...this.loading, [type]: true }
|
|
482
|
+
this.errors = { ...this.errors, [type]: null }
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
const url = this._buildUrl(type, id)
|
|
486
|
+
|
|
487
|
+
const response = await fetch(url, {
|
|
488
|
+
method: 'GET',
|
|
489
|
+
headers: buildHeaders(),
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
494
|
+
console.error(`Error fetching ${type}/${id}:`, this.errors[type])
|
|
495
|
+
return null
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const data = await response.json()
|
|
499
|
+
|
|
500
|
+
this.objects = {
|
|
501
|
+
...this.objects,
|
|
502
|
+
[type]: { ...(this.objects[type] || {}), [id]: data },
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return data
|
|
506
|
+
} catch (error) {
|
|
507
|
+
this.errors = {
|
|
508
|
+
...this.errors,
|
|
509
|
+
[type]: error.name === 'TypeError'
|
|
510
|
+
? networkError(error)
|
|
511
|
+
: genericError(error),
|
|
512
|
+
}
|
|
513
|
+
console.error(`Error fetching ${type}/${id}:`, error)
|
|
514
|
+
return null
|
|
515
|
+
} finally {
|
|
516
|
+
this.loading = { ...this.loading, [type]: false }
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Create or update an object. Uses POST for new objects, PUT for updates.
|
|
522
|
+
*
|
|
523
|
+
* @param {string} type The registered type slug
|
|
524
|
+
* @param {object} objectData The object data (include `id` for updates)
|
|
525
|
+
* @return {Promise<object|null>} The saved object or null on error
|
|
526
|
+
*/
|
|
527
|
+
async saveObject(type, objectData) {
|
|
528
|
+
this.loading = { ...this.loading, [type]: true }
|
|
529
|
+
this.errors = { ...this.errors, [type]: null }
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const isUpdate = !!objectData.id
|
|
533
|
+
const url = isUpdate
|
|
534
|
+
? this._buildUrl(type, objectData.id)
|
|
535
|
+
: this._buildUrl(type)
|
|
536
|
+
const method = isUpdate ? 'PUT' : 'POST'
|
|
537
|
+
|
|
538
|
+
const response = await fetch(url, {
|
|
539
|
+
method,
|
|
540
|
+
headers: buildHeaders(),
|
|
541
|
+
body: JSON.stringify(objectData),
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
if (!response.ok) {
|
|
545
|
+
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
546
|
+
console.error(`Error saving ${type}:`, this.errors[type])
|
|
547
|
+
return null
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const data = await response.json()
|
|
551
|
+
const savedId = data.id || objectData.id
|
|
552
|
+
|
|
553
|
+
this.objects = {
|
|
554
|
+
...this.objects,
|
|
555
|
+
[type]: { ...(this.objects[type] || {}), [savedId]: data },
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return data
|
|
559
|
+
} catch (error) {
|
|
560
|
+
this.errors = {
|
|
561
|
+
...this.errors,
|
|
562
|
+
[type]: error.name === 'TypeError'
|
|
563
|
+
? networkError(error)
|
|
564
|
+
: genericError(error),
|
|
565
|
+
}
|
|
566
|
+
console.error(`Error saving ${type}:`, error)
|
|
567
|
+
return null
|
|
568
|
+
} finally {
|
|
569
|
+
this.loading = { ...this.loading, [type]: false }
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Delete an object by type and ID.
|
|
575
|
+
*
|
|
576
|
+
* @param {string} type The registered type slug
|
|
577
|
+
* @param {string} id The object ID
|
|
578
|
+
* @return {Promise<boolean>} True if deleted successfully
|
|
579
|
+
*/
|
|
580
|
+
async deleteObject(type, id) {
|
|
581
|
+
this.loading = { ...this.loading, [type]: true }
|
|
582
|
+
this.errors = { ...this.errors, [type]: null }
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const url = this._buildUrl(type, id)
|
|
586
|
+
|
|
587
|
+
const response = await fetch(url, {
|
|
588
|
+
method: 'DELETE',
|
|
589
|
+
headers: buildHeaders(),
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
if (!response.ok) {
|
|
593
|
+
this.errors = { ...this.errors, [type]: await parseResponseError(response, type) }
|
|
594
|
+
console.error(`Error deleting ${type}/${id}:`, this.errors[type])
|
|
595
|
+
return false
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (this.objects[type]) {
|
|
599
|
+
const { [id]: _, ...remaining } = this.objects[type]
|
|
600
|
+
this.objects = { ...this.objects, [type]: remaining }
|
|
601
|
+
}
|
|
602
|
+
if (this.collections[type]) {
|
|
603
|
+
this.collections = {
|
|
604
|
+
...this.collections,
|
|
605
|
+
[type]: this.collections[type].filter((obj) => obj.id !== id),
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return true
|
|
610
|
+
} catch (error) {
|
|
611
|
+
this.errors = {
|
|
612
|
+
...this.errors,
|
|
613
|
+
[type]: error.name === 'TypeError'
|
|
614
|
+
? networkError(error)
|
|
615
|
+
: genericError(error),
|
|
616
|
+
}
|
|
617
|
+
console.error(`Error deleting ${type}/${id}:`, error)
|
|
618
|
+
return false
|
|
619
|
+
} finally {
|
|
620
|
+
this.loading = { ...this.loading, [type]: false }
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Delete multiple objects by type and IDs in parallel.
|
|
626
|
+
* Each delete is run via Promise.all; partial success is reported so the UI can show which succeeded or failed.
|
|
627
|
+
*
|
|
628
|
+
* @param {string} type The registered type slug
|
|
629
|
+
* @param {string[]} ids Array of object IDs to delete
|
|
630
|
+
* @return {Promise<{ successfulIds: string[], failedIds: string[] }>} Result with successful and failed IDs
|
|
631
|
+
*/
|
|
632
|
+
async deleteObjects(type, ids) {
|
|
633
|
+
const result = { successfulIds: [], failedIds: [] }
|
|
634
|
+
if (!ids?.length) return result
|
|
635
|
+
|
|
636
|
+
this.loading = { ...this.loading, [type]: true }
|
|
637
|
+
this.errors = { ...this.errors, [type]: null }
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
const runOne = async (id) => {
|
|
641
|
+
try {
|
|
642
|
+
const url = this._buildUrl(type, id)
|
|
643
|
+
const response = await fetch(url, {
|
|
644
|
+
method: 'DELETE',
|
|
645
|
+
headers: buildHeaders(),
|
|
646
|
+
})
|
|
647
|
+
return { id, success: response.ok }
|
|
648
|
+
} catch (error) {
|
|
649
|
+
console.error(`Error deleting ${type}/${id}:`, error)
|
|
650
|
+
return { id, success: false }
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const outcomes = await Promise.all(ids.map(runOne))
|
|
655
|
+
for (const { id, success } of outcomes) {
|
|
656
|
+
if (success) result.successfulIds.push(id)
|
|
657
|
+
else result.failedIds.push(id)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (result.successfulIds.length > 0) {
|
|
661
|
+
const successSet = new Set(result.successfulIds)
|
|
662
|
+
if (this.objects[type]) {
|
|
663
|
+
const remaining = {}
|
|
664
|
+
for (const [k, v] of Object.entries(this.objects[type])) {
|
|
665
|
+
if (!successSet.has(k)) remaining[k] = v
|
|
666
|
+
}
|
|
667
|
+
this.objects = { ...this.objects, [type]: remaining }
|
|
668
|
+
}
|
|
669
|
+
if (this.collections[type]) {
|
|
670
|
+
this.collections = {
|
|
671
|
+
...this.collections,
|
|
672
|
+
[type]: this.collections[type].filter((obj) => !successSet.has(obj.id)),
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (result.failedIds.length > 0) {
|
|
678
|
+
this.errors = {
|
|
679
|
+
...this.errors,
|
|
680
|
+
[type]: genericError(new Error(`Failed to delete ${result.failedIds.length} item(s)`)),
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return result
|
|
685
|
+
} finally {
|
|
686
|
+
this.loading = { ...this.loading, [type]: false }
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Batch-resolve references by fetching multiple objects by their IDs.
|
|
692
|
+
* Uses the cache first, only fetches uncached objects.
|
|
693
|
+
*
|
|
694
|
+
* @param {string} type The registered type slug
|
|
695
|
+
* @param {string[]} ids Array of object IDs to resolve
|
|
696
|
+
* @return {Promise<Object<string, object>>} Map of id -> object
|
|
697
|
+
*/
|
|
698
|
+
async resolveReferences(type, ids) {
|
|
699
|
+
if (!ids || ids.length === 0) return {}
|
|
700
|
+
|
|
701
|
+
const uniqueIds = [...new Set(ids.filter(Boolean))]
|
|
702
|
+
const result = {}
|
|
703
|
+
const toFetch = []
|
|
704
|
+
|
|
705
|
+
for (const id of uniqueIds) {
|
|
706
|
+
const cached = this.objects[type]?.[id]
|
|
707
|
+
if (cached) {
|
|
708
|
+
result[id] = cached
|
|
709
|
+
} else {
|
|
710
|
+
toFetch.push(id)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (toFetch.length > 0) {
|
|
715
|
+
const fetches = toFetch.map(async (id) => {
|
|
716
|
+
try {
|
|
717
|
+
const url = this._buildUrl(type, id)
|
|
718
|
+
const response = await fetch(url, {
|
|
719
|
+
method: 'GET',
|
|
720
|
+
headers: buildHeaders(),
|
|
721
|
+
})
|
|
722
|
+
if (response.ok) {
|
|
723
|
+
const data = await response.json()
|
|
724
|
+
this.objects = {
|
|
725
|
+
...this.objects,
|
|
726
|
+
[type]: { ...(this.objects[type] || {}), [id]: data },
|
|
727
|
+
}
|
|
728
|
+
result[id] = data
|
|
729
|
+
}
|
|
730
|
+
} catch {
|
|
731
|
+
// Non-blocking — leave unresolved
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
await Promise.all(fetches)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return result
|
|
738
|
+
},
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ── Store factory ───────────────────────────────────────────────────────
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Create the object store definition with a given store ID and optional plugins.
|
|
745
|
+
*
|
|
746
|
+
* Plugins are merged into the store at definition time. Each plugin provides
|
|
747
|
+
* additional state, getters, and actions (e.g. for sub-resources like files,
|
|
748
|
+
* audit trails, relations).
|
|
749
|
+
*
|
|
750
|
+
* @param {string} storeId Pinia store identifier
|
|
751
|
+
* @param {Array} [plugins] Array of plugin definitions
|
|
752
|
+
* @param {string} [baseUrl] Base API URL override
|
|
753
|
+
* @return {Function} Pinia store composable
|
|
754
|
+
*/
|
|
755
|
+
function defineObjectStore(storeId, plugins = [], baseUrl = DEFAULT_BASE_URL) {
|
|
756
|
+
const pluginState = mergePluginState(plugins)
|
|
757
|
+
const pluginGetters = mergePluginGetters(plugins)
|
|
758
|
+
const pluginActions = mergePluginActions(plugins)
|
|
759
|
+
|
|
760
|
+
return defineStore(storeId, {
|
|
761
|
+
state: () => ({
|
|
762
|
+
...baseState(baseUrl),
|
|
763
|
+
...pluginState,
|
|
764
|
+
}),
|
|
765
|
+
|
|
766
|
+
getters: {
|
|
767
|
+
...baseGetters,
|
|
768
|
+
...pluginGetters,
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
actions: {
|
|
772
|
+
...baseActions,
|
|
773
|
+
...pluginActions,
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Clear all sub-resource data from active plugins.
|
|
777
|
+
* Calls each plugin's clear method (e.g. clearFiles, clearAuditTrails).
|
|
778
|
+
*/
|
|
779
|
+
clearAllSubResources() {
|
|
780
|
+
for (const plugin of plugins) {
|
|
781
|
+
const clearFn = `clear${capitalize(plugin.name)}`
|
|
782
|
+
if (typeof this[clearFn] === 'function') {
|
|
783
|
+
this[clearFn]()
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Default object store instance with ID 'conduction-objects'.
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* import { useObjectStore } from '@conduction/nextcloud-vue'
|
|
796
|
+
* const store = useObjectStore()
|
|
797
|
+
*/
|
|
798
|
+
export const useObjectStore = defineObjectStore(DEFAULT_STORE_ID)
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Factory function to create an object store with a custom Pinia store ID
|
|
802
|
+
* and optional plugins for sub-resources.
|
|
803
|
+
*
|
|
804
|
+
* @param {string} storeId Custom Pinia store identifier
|
|
805
|
+
* @param {object} [options] Configuration options
|
|
806
|
+
* @param {Array} [options.plugins] Array of sub-resource plugins
|
|
807
|
+
* @param {string} [options.baseUrl] Base API URL override
|
|
808
|
+
* @return {Function} Pinia store composable
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* // Basic (backwards compatible)
|
|
812
|
+
* const useMyStore = createObjectStore('object')
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* // With plugins
|
|
816
|
+
* import { filesPlugin, auditTrailsPlugin } from '@conduction/nextcloud-vue'
|
|
817
|
+
* const useMyStore = createObjectStore('object', {
|
|
818
|
+
* plugins: [filesPlugin(), auditTrailsPlugin()],
|
|
819
|
+
* })
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* // With custom baseUrl
|
|
823
|
+
* const useMyStore = createObjectStore('object', {
|
|
824
|
+
* baseUrl: '/apps/myapp/api/objects',
|
|
825
|
+
* })
|
|
826
|
+
*/
|
|
827
|
+
export function createObjectStore(storeId, options = {}) {
|
|
828
|
+
return defineObjectStore(storeId, options.plugins || [], options.baseUrl || prefixUrl(DEFAULT_BASE_URL))
|
|
829
|
+
}
|