@conduction/nextcloud-vue 0.1.0-beta.6 → 0.1.0-beta.8

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.
Files changed (82) hide show
  1. package/dist/nextcloud-vue.cjs.js +13575 -2374
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +1238 -270
  4. package/dist/nextcloud-vue.esm.js +13517 -2336
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +11 -7
  7. package/src/components/CnActionsBar/CnActionsBar.vue +20 -2
  8. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +1 -11
  9. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +5 -1
  10. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
  11. package/src/components/CnCard/CnCard.vue +415 -0
  12. package/src/components/CnCard/index.js +1 -0
  13. package/src/components/CnCardGrid/CnCardGrid.vue +20 -20
  14. package/src/components/CnChartWidget/CnChartWidget.vue +3 -1
  15. package/src/components/CnCopyDialog/CnCopyDialog.vue +7 -1
  16. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +4 -0
  17. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -0
  18. package/src/components/CnDataTable/CnDataTable.vue +6 -2
  19. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +7 -1
  20. package/src/components/CnDetailCard/CnDetailCard.vue +12 -1
  21. package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
  22. package/src/components/CnDetailGrid/index.js +1 -0
  23. package/src/components/CnDetailPage/CnDetailPage.vue +157 -11
  24. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -1
  25. package/src/components/CnFormDialog/CnFormDialog.vue +934 -920
  26. package/src/components/CnIcon/CnIcon.vue +1 -1
  27. package/src/components/CnIndexPage/CnIndexPage.vue +63 -9
  28. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +37 -9
  29. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  30. package/src/components/CnInfoWidget/index.js +1 -0
  31. package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
  32. package/src/components/CnJsonViewer/index.js +1 -0
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
  34. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +7 -1
  35. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +7 -1
  36. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +1 -1
  37. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +1 -1
  38. package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
  39. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
  40. package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
  41. package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
  42. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +45 -668
  43. package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
  44. package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
  45. package/src/components/CnObjectSidebar/index.js +5 -0
  46. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  47. package/src/components/CnProgressBar/index.js +1 -0
  48. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +1 -1
  49. package/src/components/CnStatsBlock/CnStatsBlock.vue +27 -11
  50. package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
  51. package/src/components/CnStatsPanel/index.js +1 -0
  52. package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
  53. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +5 -1
  54. package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
  55. package/src/components/CnTableWidget/index.js +1 -0
  56. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +36 -1
  57. package/src/components/index.js +11 -0
  58. package/src/composables/useDashboardView.js +58 -12
  59. package/src/composables/useDetailView.js +3 -2
  60. package/src/composables/useListView.js +7 -6
  61. package/src/composables/useSubResource.js +3 -3
  62. package/src/css/badge.css +32 -0
  63. package/src/css/card.css +1 -0
  64. package/src/css/detail-page.css +74 -7
  65. package/src/index.js +16 -0
  66. package/src/mixins/gridLayout.js +118 -0
  67. package/src/store/createCrudStore.js +360 -0
  68. package/src/store/createSubResourcePlugin.js +5 -15
  69. package/src/store/index.js +1 -0
  70. package/src/store/plugins/auditTrails.js +346 -6
  71. package/src/store/plugins/lifecycle.js +4 -4
  72. package/src/store/plugins/registerMapping.js +18 -8
  73. package/src/store/plugins/relations.js +1 -1
  74. package/src/store/plugins/search.js +21 -8
  75. package/src/store/useObjectStore.js +30 -36
  76. package/src/utils/getTheme.js +9 -0
  77. package/src/utils/headers.js +13 -3
  78. package/src/utils/index.js +1 -0
  79. package/src/utils/schema.js +3 -3
  80. package/src/utils/widgetVisibility.js +162 -0
  81. package/src/components/CnObjectCard/eslint-setup.md +0 -235
  82. package/src/components/CnObjectCard/package.json-or.json +0 -132
@@ -1,17 +1,357 @@
1
- import { createSubResourcePlugin } from '../createSubResourcePlugin.js'
1
+ import { createSubResourcePlugin, emptyPaginated } from '../createSubResourcePlugin.js'
2
+ import { buildHeaders, buildQueryString } from '../../utils/headers.js'
3
+ import { parseResponseError, networkError, genericError } from '../../utils/errors.js'
2
4
 
3
5
  /**
4
6
  * Audit trails plugin for the object store.
5
7
  *
6
- * Adds state, getters, and actions for fetching audit trail records
7
- * for an object. Read-only no create/update/delete.
8
+ * Extends the generic sub-resource plugin with global audit trail operations:
9
+ * fetching all audit trails (not scoped to one object), statistics, and delete.
8
10
  *
11
+ * **Object-scoped** (from createSubResourcePlugin):
9
12
  * State: auditTrails, auditTrailsLoading, auditTrailsError
10
13
  * Actions: fetchAuditTrails(type, objectId, params), clearAuditTrails()
11
14
  * Getters: getAuditTrails, isAuditTrailsLoading, getAuditTrailsError
12
15
  *
16
+ * **Global** (added by this plugin):
17
+ * State: globalAuditTrails, globalAuditTrailsLoading, globalAuditTrailsError,
18
+ * auditTrailStatistics, auditTrailStatisticsLoading, auditTrailStatisticsError,
19
+ * auditTrailItem, auditTrailFilters, auditTrailSearch
20
+ * Actions: fetchGlobalAuditTrails(params), fetchAuditTrailStatistics(),
21
+ * deleteGlobalAuditTrail(id), deleteMultipleGlobalAuditTrails(ids),
22
+ * refreshGlobalAuditTrails(), setAuditTrailItem(item),
23
+ * setAuditTrailFilters(filters), setAuditTrailSearch(search),
24
+ * clearAuditTrailFilters(), clearGlobalAuditTrails()
25
+ * Getters: getGlobalAuditTrails, isGlobalAuditTrailsLoading, getGlobalAuditTrailsError,
26
+ * getAuditTrailStatistics, isAuditTrailStatisticsLoading, getAuditTrailStatisticsError,
27
+ * getAuditTrailItem, getAuditTrailFilters, getAuditTrailSearch
28
+ *
13
29
  * @param {object} [options={}] Plugin options
14
- * @param {number} [options.limit=20] Default page size
15
- * @return {Function} Plugin factory
30
+ * @param {number} [options.limit=20] Default page size for object-scoped queries
31
+ * @param {number} [options.globalLimit=50] Default page size for global queries
32
+ * @return {object} Plugin definition
33
+ *
34
+ * @example
35
+ * const useStore = createObjectStore('object', {
36
+ * plugins: [auditTrailsPlugin()],
37
+ * })
38
+ * const store = useStore()
39
+ *
40
+ * // Object-scoped (unchanged)
41
+ * await store.fetchAuditTrails('case', caseId)
42
+ *
43
+ * // Global
44
+ * await store.fetchGlobalAuditTrails({ _limit: 50, _page: 1 })
45
+ * await store.fetchAuditTrailStatistics()
46
+ * await store.deleteGlobalAuditTrail(id)
47
+ */
48
+
49
+ const GLOBAL_PATH = '/audit-trails'
50
+
51
+ const EMPTY_STATISTICS = {
52
+ total: 0,
53
+ create: 0,
54
+ update: 0,
55
+ delete: 0,
56
+ read: 0,
57
+ }
58
+
59
+ /**
60
+ * Build the global audit trails API URL from the store base URL.
61
+ * e.g. /apps/openregister/api/objects -> /apps/openregister/api/audit-trails
62
+ *
63
+ * @param {string} baseUrl Store base URL
64
+ * @return {string} Global audit trails endpoint URL
16
65
  */
17
- export const auditTrailsPlugin = createSubResourcePlugin('auditTrails', 'audit-trails')
66
+ function buildGlobalUrl(baseUrl) {
67
+ return baseUrl.replace(/\/objects\/?$/, '') + GLOBAL_PATH
68
+ }
69
+
70
+ export function auditTrailsPlugin(options = {}) {
71
+ const base = createSubResourcePlugin('auditTrails', 'audit-trails', options)()
72
+ const globalLimit = options.globalLimit || 50
73
+
74
+ return {
75
+ ...base,
76
+
77
+ state: () => ({
78
+ ...base.state(),
79
+ globalAuditTrails: emptyPaginated(globalLimit),
80
+ globalAuditTrailsLoading: false,
81
+ globalAuditTrailsError: null,
82
+ auditTrailStatistics: { ...EMPTY_STATISTICS },
83
+ auditTrailStatisticsLoading: false,
84
+ auditTrailStatisticsError: null,
85
+ auditTrailItem: null,
86
+ auditTrailFilters: {},
87
+ auditTrailSearch: '',
88
+ }),
89
+
90
+ getters: {
91
+ ...base.getters,
92
+ getGlobalAuditTrails: (state) => state.globalAuditTrails,
93
+ isGlobalAuditTrailsLoading: (state) => state.globalAuditTrailsLoading || false,
94
+ getGlobalAuditTrailsError: (state) => state.globalAuditTrailsError || null,
95
+ getAuditTrailStatistics: (state) => state.auditTrailStatistics,
96
+ isAuditTrailStatisticsLoading: (state) => state.auditTrailStatisticsLoading || false,
97
+ getAuditTrailStatisticsError: (state) => state.auditTrailStatisticsError || null,
98
+ getAuditTrailItem: (state) => state.auditTrailItem || null,
99
+ getAuditTrailFilters: (state) => state.auditTrailFilters || {},
100
+ getAuditTrailSearch: (state) => state.auditTrailSearch || '',
101
+ },
102
+
103
+ actions: {
104
+ ...base.actions,
105
+
106
+ /**
107
+ * Fetch audit trails from the global endpoint (not scoped to an object).
108
+ *
109
+ * @param {object} [params] Query parameters (_limit, _page, _search, _order, filters)
110
+ * @return {Promise<Array>} The fetched results
111
+ */
112
+ async fetchGlobalAuditTrails(params = {}) {
113
+ this.globalAuditTrailsLoading = true
114
+ this.globalAuditTrailsError = null
115
+
116
+ try {
117
+ const url = buildGlobalUrl(this._options.baseUrl) + buildQueryString(params)
118
+
119
+ const response = await fetch(url, {
120
+ method: 'GET',
121
+ headers: buildHeaders(),
122
+ })
123
+
124
+ if (!response.ok) {
125
+ this.globalAuditTrailsError = await parseResponseError(response, 'audit trails')
126
+ console.error('Error fetching global audit trails:', this.globalAuditTrailsError)
127
+ return []
128
+ }
129
+
130
+ const data = await response.json()
131
+ const results = data.results || data
132
+
133
+ this.globalAuditTrails = {
134
+ results,
135
+ total: data.total || results.length,
136
+ page: data.page || 1,
137
+ pages: data.pages || 0,
138
+ limit: params._limit || globalLimit,
139
+ offset: data.offset || 0,
140
+ }
141
+
142
+ return results
143
+ } catch (error) {
144
+ this.globalAuditTrailsError = error.name === 'TypeError'
145
+ ? networkError(error)
146
+ : genericError(error)
147
+ console.error('Error fetching global audit trails:', error)
148
+ return []
149
+ } finally {
150
+ this.globalAuditTrailsLoading = false
151
+ }
152
+ },
153
+
154
+ /**
155
+ * Fetch audit trail statistics from the global statistics endpoint.
156
+ *
157
+ * @return {Promise<object>} The statistics object
158
+ */
159
+ async fetchAuditTrailStatistics() {
160
+ this.auditTrailStatisticsLoading = true
161
+ this.auditTrailStatisticsError = null
162
+
163
+ try {
164
+ const url = buildGlobalUrl(this._options.baseUrl) + '/statistics'
165
+
166
+ const response = await fetch(url, {
167
+ method: 'GET',
168
+ headers: buildHeaders(),
169
+ })
170
+
171
+ if (!response.ok) {
172
+ this.auditTrailStatisticsError = await parseResponseError(response, 'audit trail statistics')
173
+ console.error('Error fetching audit trail statistics:', this.auditTrailStatisticsError)
174
+ return { ...EMPTY_STATISTICS }
175
+ }
176
+
177
+ const data = await response.json()
178
+ this.auditTrailStatistics = { ...EMPTY_STATISTICS, ...data }
179
+ return this.auditTrailStatistics
180
+ } catch (error) {
181
+ this.auditTrailStatisticsError = error.name === 'TypeError'
182
+ ? networkError(error)
183
+ : genericError(error)
184
+ console.error('Error fetching audit trail statistics:', error)
185
+ return { ...EMPTY_STATISTICS }
186
+ } finally {
187
+ this.auditTrailStatisticsLoading = false
188
+ }
189
+ },
190
+
191
+ /**
192
+ * Delete a single audit trail by ID.
193
+ *
194
+ * @param {string} id The audit trail ID
195
+ * @return {Promise<boolean>} True if deleted successfully
196
+ */
197
+ async deleteGlobalAuditTrail(id) {
198
+ this.globalAuditTrailsLoading = true
199
+ this.globalAuditTrailsError = null
200
+
201
+ try {
202
+ const url = buildGlobalUrl(this._options.baseUrl) + `/${id}`
203
+
204
+ const response = await fetch(url, {
205
+ method: 'DELETE',
206
+ headers: buildHeaders(),
207
+ })
208
+
209
+ if (!response.ok) {
210
+ this.globalAuditTrailsError = await parseResponseError(response, 'audit trail')
211
+ console.error(`Error deleting audit trail ${id}:`, this.globalAuditTrailsError)
212
+ return false
213
+ }
214
+
215
+ this.globalAuditTrails = {
216
+ ...this.globalAuditTrails,
217
+ results: this.globalAuditTrails.results.filter((item) => item.id !== id),
218
+ total: Math.max(0, this.globalAuditTrails.total - 1),
219
+ }
220
+
221
+ return true
222
+ } catch (error) {
223
+ this.globalAuditTrailsError = error.name === 'TypeError'
224
+ ? networkError(error)
225
+ : genericError(error)
226
+ console.error(`Error deleting audit trail ${id}:`, error)
227
+ return false
228
+ } finally {
229
+ this.globalAuditTrailsLoading = false
230
+ }
231
+ },
232
+
233
+ /**
234
+ * Delete multiple audit trails by IDs.
235
+ *
236
+ * @param {string[]} ids Array of audit trail IDs to delete
237
+ * @return {Promise<boolean>} True if deleted successfully
238
+ */
239
+ async deleteMultipleGlobalAuditTrails(ids) {
240
+ if (!ids?.length) return true
241
+
242
+ this.globalAuditTrailsLoading = true
243
+ this.globalAuditTrailsError = null
244
+
245
+ try {
246
+ const url = buildGlobalUrl(this._options.baseUrl)
247
+
248
+ const response = await fetch(url, {
249
+ method: 'DELETE',
250
+ headers: buildHeaders(),
251
+ body: JSON.stringify({ ids }),
252
+ })
253
+
254
+ if (!response.ok) {
255
+ this.globalAuditTrailsError = await parseResponseError(response, 'audit trails')
256
+ console.error('Error deleting audit trails:', this.globalAuditTrailsError)
257
+ return false
258
+ }
259
+
260
+ const idSet = new Set(ids)
261
+ const remaining = this.globalAuditTrails.results.filter((item) => !idSet.has(item.id))
262
+
263
+ this.globalAuditTrails = {
264
+ ...this.globalAuditTrails,
265
+ results: remaining,
266
+ total: Math.max(0, this.globalAuditTrails.total - (this.globalAuditTrails.results.length - remaining.length)),
267
+ }
268
+
269
+ return true
270
+ } catch (error) {
271
+ this.globalAuditTrailsError = error.name === 'TypeError'
272
+ ? networkError(error)
273
+ : genericError(error)
274
+ console.error('Error deleting audit trails:', error)
275
+ return false
276
+ } finally {
277
+ this.globalAuditTrailsLoading = false
278
+ }
279
+ },
280
+
281
+ /**
282
+ * Re-fetch global audit trails with current pagination state.
283
+ *
284
+ * @return {Promise<Array>} The fetched results
285
+ */
286
+ async refreshGlobalAuditTrails() {
287
+ return this.fetchGlobalAuditTrails({
288
+ _limit: this.globalAuditTrails.limit,
289
+ _page: this.globalAuditTrails.page,
290
+ })
291
+ },
292
+
293
+ /**
294
+ * Set the active audit trail item (for detail views).
295
+ *
296
+ * @param {object|null} item The audit trail item or null to clear
297
+ */
298
+ setAuditTrailItem(item) {
299
+ this.auditTrailItem = item || null
300
+ },
301
+
302
+ /**
303
+ * Set audit trail filters (merged with existing).
304
+ *
305
+ * @param {object} filters Filter key-value pairs
306
+ */
307
+ setAuditTrailFilters(filters) {
308
+ this.auditTrailFilters = { ...this.auditTrailFilters, ...filters }
309
+ },
310
+
311
+ /**
312
+ * Set the audit trail search term.
313
+ *
314
+ * @param {string} search The search term
315
+ */
316
+ setAuditTrailSearch(search) {
317
+ this.auditTrailSearch = search || ''
318
+ },
319
+
320
+ /**
321
+ * Clear all audit trail filters and search.
322
+ */
323
+ clearAuditTrailFilters() {
324
+ this.auditTrailFilters = {}
325
+ this.auditTrailSearch = ''
326
+ },
327
+
328
+ /**
329
+ * Clear all global audit trail state back to defaults.
330
+ */
331
+ clearGlobalAuditTrails() {
332
+ this.globalAuditTrails = emptyPaginated(globalLimit)
333
+ this.globalAuditTrailsLoading = false
334
+ this.globalAuditTrailsError = null
335
+ this.auditTrailStatistics = { ...EMPTY_STATISTICS }
336
+ this.auditTrailStatisticsLoading = false
337
+ this.auditTrailStatisticsError = null
338
+ this.auditTrailItem = null
339
+ this.auditTrailFilters = {}
340
+ this.auditTrailSearch = ''
341
+ },
342
+
343
+ /**
344
+ * Clear all audit trail state (both object-scoped and global).
345
+ * Overrides the base clearAuditTrails so clearAllSubResources() clears everything.
346
+ */
347
+ clearAuditTrails() {
348
+ // Base sub-resource clear
349
+ this.auditTrails = emptyPaginated(options.limit || 20)
350
+ this.auditTrailsLoading = false
351
+ this.auditTrailsError = null
352
+ // Global clear
353
+ this.clearGlobalAuditTrails()
354
+ },
355
+ },
356
+ }
357
+ }
@@ -42,7 +42,7 @@ export function lifecyclePlugin() {
42
42
  * @param {string} type The registered object type slug
43
43
  * @param {string} objectId The object ID
44
44
  * @param {string} action The lifecycle action endpoint (e.g. 'lock', 'publish')
45
- * @param {object} [body=null] Optional request body
45
+ * @param {object} [body] Optional request body
46
46
  * @return {Promise<object|null>} Response data or null on error
47
47
  */
48
48
  async _lifecycleAction(type, objectId, action, body = null) {
@@ -70,7 +70,7 @@ export function lifecyclePlugin() {
70
70
  const data = await response.json()
71
71
 
72
72
  if (this.objects[type] && data.id) {
73
- this.objects[type][data.id] = data
73
+ this.objects = { ...this.objects, [type]: { ...this.objects[type], [data.id]: data } }
74
74
  }
75
75
 
76
76
  return data
@@ -115,7 +115,7 @@ export function lifecyclePlugin() {
115
115
  *
116
116
  * @param {string} type The registered object type slug
117
117
  * @param {string} objectId The object ID
118
- * @param {object} [options={}] Publish options
118
+ * @param {object} [options] Publish options
119
119
  * @param {string} [options.date] Publish date (ISO 8601)
120
120
  * @return {Promise<object|null>} Updated object or null on error
121
121
  */
@@ -128,7 +128,7 @@ export function lifecyclePlugin() {
128
128
  *
129
129
  * @param {string} type The registered object type slug
130
130
  * @param {string} objectId The object ID
131
- * @param {object} [options={}] Depublish options
131
+ * @param {object} [options] Depublish options
132
132
  * @param {string} [options.date] Depublish date (ISO 8601)
133
133
  * @return {Promise<object|null>} Updated object or null on error
134
134
  */
@@ -1,4 +1,4 @@
1
- import { buildHeaders } from '../../utils/headers.js'
1
+ import { buildHeaders, prefixUrl } from '../../utils/headers.js'
2
2
 
3
3
  /**
4
4
  * Register mapping plugin for the object store.
@@ -28,7 +28,7 @@ export function registerMappingPlugin() {
28
28
  state: () => ({
29
29
  /** @type {Array} All available registers from OpenRegister */
30
30
  registers: [],
31
- /** @type {Object<string, Array>} Schemas keyed by register ID */
31
+ /** @type {{[key: string]: Array}} Schemas keyed by register ID */
32
32
  registerSchemas: {},
33
33
  /** @type {boolean} Whether registers are being fetched */
34
34
  registersLoading: false,
@@ -37,18 +37,28 @@ export function registerMappingPlugin() {
37
37
  }),
38
38
 
39
39
  getters: {
40
- /** @return {Array} Raw register list */
40
+ /**
41
+ * @param {object} state - Pinia state
42
+ * @return {Array} Raw register list
43
+ */
41
44
  getRegisters: (state) => state.registers,
42
45
 
43
- /** @return {boolean} Whether registers are loading */
46
+ /**
47
+ * @param {object} state - Pinia state
48
+ * @return {boolean} Whether registers are loading
49
+ */
44
50
  isRegistersLoading: (state) => state.registersLoading,
45
51
 
46
- /** @return {string|null} Last error */
52
+ /**
53
+ * @param {object} state - Pinia state
54
+ * @return {string|null} Last error
55
+ */
47
56
  getRegistersError: (state) => state.registersError,
48
57
 
49
58
  /**
50
59
  * Registers as NcSelect-compatible options.
51
60
  *
61
+ * @param {object} state - Pinia state
52
62
  * @return {Array<{label: string, value: string}>}
53
63
  */
54
64
  registerOptions: (state) => state.registers.map((r) => ({
@@ -75,7 +85,7 @@ export function registerMappingPlugin() {
75
85
  /**
76
86
  * Fetch all registers from OpenRegister with expanded schemas.
77
87
  *
78
- * @param {boolean} [withSchemas=true] Include schemas in response
88
+ * @param {boolean} [withSchemas] Include schemas in response
79
89
  * @return {Promise<Array>} Fetched registers
80
90
  */
81
91
  async fetchRegisters(withSchemas = true) {
@@ -83,7 +93,7 @@ export function registerMappingPlugin() {
83
93
  this.registersError = null
84
94
 
85
95
  try {
86
- let url = '/apps/openregister/api/registers'
96
+ let url = prefixUrl('/apps/openregister/api/registers')
87
97
  if (withSchemas) {
88
98
  url += '?_extend[]=schemas'
89
99
  }
@@ -155,7 +165,7 @@ export function registerMappingPlugin() {
155
165
  // Fetch from API as fallback
156
166
  try {
157
167
  const response = await fetch(
158
- `/apps/openregister/api/registers/${id}?_extend[]=schemas`,
168
+ prefixUrl(`/apps/openregister/api/registers/${id}?_extend[]=schemas`),
159
169
  { method: 'GET', headers: buildHeaders() },
160
170
  )
161
171
  if (!response.ok) return []
@@ -18,7 +18,7 @@ const usedBase = createSubResourcePlugin('used', 'used')
18
18
  /**
19
19
  * Combined relations plugin that registers contracts, uses, and used sub-resources.
20
20
  *
21
- * @param {object} [options={}] Plugin options
21
+ * @param {object} [options] Plugin options
22
22
  * @return {Function} Plugin factory
23
23
  *
24
24
  * @example
@@ -124,21 +124,21 @@ export function searchPlugin() {
124
124
  getters: {
125
125
  /**
126
126
  * The current search result objects.
127
- * @param {object} state
127
+ * @param {object} state Pinia state
128
128
  * @return {Array}
129
129
  */
130
130
  searchCollection: (state) => state._searchCollection,
131
131
 
132
132
  /**
133
133
  * Pagination state for the last search fetch.
134
- * @param {object} state
134
+ * @param {object} state Pinia state
135
135
  * @return {{ total: number, page: number, pages: number, limit: number }}
136
136
  */
137
137
  searchPagination: (state) => state._searchPagination,
138
138
 
139
139
  /**
140
140
  * True while a search fetch is in progress.
141
- * @param {object} state
141
+ * @param {object} state Pinia state
142
142
  * @return {boolean}
143
143
  */
144
144
  searchLoading: (state) => state._searchLoading,
@@ -146,7 +146,7 @@ export function searchPlugin() {
146
146
  /**
147
147
  * The schema object for the current search register/schema pair.
148
148
  * Populated automatically by `refetchSearchCollection`.
149
- * @param {object} state
149
+ * @param {object} state Pinia state
150
150
  * @return {object|null}
151
151
  */
152
152
  searchSchema: (state) => state._searchSchema,
@@ -154,7 +154,7 @@ export function searchPlugin() {
154
154
  /**
155
155
  * The register object for the current search register/schema pair.
156
156
  * Populated automatically by `refetchSearchCollection`.
157
- * @param {object} state
157
+ * @param {object} state Pinia state
158
158
  * @return {object|null}
159
159
  */
160
160
  searchRegister: (state) => state._searchRegister,
@@ -162,7 +162,7 @@ export function searchPlugin() {
162
162
  /**
163
163
  * Facet data from the last search fetch, in CnIndexSidebar-compatible format:
164
164
  * `{ fieldName: { values: [{ value, count }] } }`.
165
- * @param {object} state
165
+ * @param {object} state Pinia state
166
166
  * @return {object}
167
167
  */
168
168
  searchFacets: (state) => state._searchFacets,
@@ -236,7 +236,20 @@ export function searchPlugin() {
236
236
  * @return {Promise<Array>} The fetched collection (empty array on error)
237
237
  */
238
238
  async refetchSearchCollection() {
239
- const { register, schema, ...queryParams } = this.searchParams
239
+ const { register, schema, filters, ...queryParams } = this.searchParams
240
+
241
+ // Flatten the filters object into individual query params so the
242
+ // backend receives them as field-level filters (e.g. ?title=foo)
243
+ // instead of a single `filters=[object Object]` param.
244
+ if (filters && typeof filters === 'object') {
245
+ for (const [field, values] of Object.entries(filters)) {
246
+ if (Array.isArray(values) && values.length > 0) {
247
+ queryParams[field] = values
248
+ } else if (values && !Array.isArray(values)) {
249
+ queryParams[field] = values
250
+ }
251
+ }
252
+ }
240
253
 
241
254
  if (!register || !schema) {
242
255
  console.warn('[searchPlugin] refetchSearchCollection called without register/schema in searchParams')
@@ -263,7 +276,7 @@ export function searchPlugin() {
263
276
  }
264
277
 
265
278
  try {
266
- const baseUrl = this._options?.baseUrl || '/apps/openregister/api/objects'
279
+ const baseUrl = this._options?.baseUrl || prefixUrl('/apps/openregister/api/objects')
267
280
  const url = `${baseUrl}/${register}/${schema}` + buildQueryString(queryParams)
268
281
 
269
282
  const response = await fetch(url, {