@conduction/nextcloud-vue 0.1.0-beta.10 → 0.1.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nextcloud-vue.cjs.js +64663 -63467
- package/dist/nextcloud-vue.cjs.js.map +1 -1
- package/dist/nextcloud-vue.css +443 -444
- package/dist/nextcloud-vue.esm.js +64637 -63443
- package/dist/nextcloud-vue.esm.js.map +1 -1
- package/l10n/en.json +164 -0
- package/l10n/nl.json +164 -0
- package/package.json +19 -3
- package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +8 -7
- package/src/components/CnAdvancedFormDialog/CnDataTab.vue +2 -2
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +5 -5
- package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +2 -2
- package/src/components/CnCardGrid/CnCardGrid.vue +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +29 -1
- package/src/components/CnCopyDialog/CnCopyDialog.vue +15 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +5 -4
- package/src/components/CnDetailGrid/CnDetailGrid.vue +3 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +5 -4
- package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -2
- package/src/components/CnFilterBar/CnFilterBar.vue +3 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +122 -9
- package/src/components/CnIndexPage/CnIndexPage.vue +1 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +8 -7
- package/src/components/CnJsonViewer/CnJsonViewer.vue +33 -4
- package/src/components/CnMassActionBar/CnMassActionBar.vue +1 -1
- package/src/components/CnMassImportDialog/CnMassImportDialog.vue +2 -2
- package/src/components/CnNotesCard/CnNotesCard.vue +7 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +11 -10
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +3 -2
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +8 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -5
- package/src/components/CnObjectSidebar/CnNotesTab.vue +8 -7
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +6 -5
- package/src/components/CnObjectSidebar/CnTagsTab.vue +3 -2
- package/src/components/CnObjectSidebar/CnTasksTab.vue +11 -10
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +14 -13
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +15 -14
- package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +4 -4
- package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +10 -10
- package/src/components/CnSettingsSection/CnSettingsSection.vue +5 -4
- package/src/components/CnStatsBlock/CnStatsBlock.vue +5 -4
- package/src/components/CnStatsPanel/CnStatsPanel.vue +3 -2
- package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +9 -8
- package/src/components/CnTableWidget/CnTableWidget.vue +3 -2
- package/src/components/CnTasksCard/CnTasksCard.vue +5 -4
- package/src/components/CnTimelineStages/CnTimelineStages.vue +3 -1
- package/src/components/CnUserActionMenu/CnUserActionMenu.vue +7 -6
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -1
- package/src/index.js +4 -0
- package/src/l10n/index.js +12 -0
- package/src/store/createCrudStore.d.ts +350 -0
- package/src/store/createCrudStore.js +58 -5
- package/src/store/pluginMerge.js +55 -0
- package/src/store/plugins/index.js +1 -0
- package/src/store/plugins/logs.d.ts +22 -0
- package/src/store/plugins/logs.js +172 -0
- package/src/store/useObjectStore.js +19 -49
- package/src/types/index.d.ts +32 -0
- package/src/utils/schema.js +3 -2
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
</template>
|
|
71
71
|
|
|
72
72
|
<script>
|
|
73
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
73
74
|
import { CnSettingsSection } from '../CnSettingsSection/index.js'
|
|
74
75
|
import { NcLoadingIcon, NcButton } from '@nextcloud/vue'
|
|
75
76
|
import Check from 'vue-material-design-icons/Check.vue'
|
|
@@ -122,12 +123,12 @@ export default {
|
|
|
122
123
|
/** Section title */
|
|
123
124
|
title: {
|
|
124
125
|
type: String,
|
|
125
|
-
default: 'Version
|
|
126
|
+
default: () => t('nextcloud-vue', 'Version information'),
|
|
126
127
|
},
|
|
127
128
|
/** Section description */
|
|
128
129
|
description: {
|
|
129
130
|
type: String,
|
|
130
|
-
default: 'Information about the current application installation',
|
|
131
|
+
default: () => t('nextcloud-vue', 'Information about the current application installation'),
|
|
131
132
|
},
|
|
132
133
|
/** Documentation URL (shows info icon next to title) */
|
|
133
134
|
docUrl: {
|
|
@@ -137,7 +138,7 @@ export default {
|
|
|
137
138
|
/** Card heading text */
|
|
138
139
|
cardTitle: {
|
|
139
140
|
type: String,
|
|
140
|
-
default: 'Application
|
|
141
|
+
default: () => t('nextcloud-vue', 'Application information'),
|
|
141
142
|
},
|
|
142
143
|
/** Application name to display */
|
|
143
144
|
appName: {
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
</template>
|
|
56
56
|
|
|
57
57
|
<script>
|
|
58
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
59
|
+
|
|
58
60
|
/**
|
|
59
61
|
* CnWidgetWrapper — Widget container with header, content, and footer.
|
|
60
62
|
*
|
|
@@ -79,7 +81,7 @@ export default {
|
|
|
79
81
|
/** Widget title */
|
|
80
82
|
title: {
|
|
81
83
|
type: String,
|
|
82
|
-
default: 'Widget',
|
|
84
|
+
default: () => t('nextcloud-vue', 'Widget'),
|
|
83
85
|
},
|
|
84
86
|
/** Whether to show the header with title */
|
|
85
87
|
showTitle: {
|
package/src/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export {
|
|
|
74
74
|
relationsPlugin,
|
|
75
75
|
filesPlugin,
|
|
76
76
|
lifecyclePlugin,
|
|
77
|
+
logsPlugin,
|
|
77
78
|
registerMappingPlugin,
|
|
78
79
|
selectionPlugin,
|
|
79
80
|
searchPlugin,
|
|
@@ -85,6 +86,9 @@ export {
|
|
|
85
86
|
// Composables
|
|
86
87
|
export { useListView, useDetailView, useSubResource, useDashboardView, useContextMenu } from './composables/index.js'
|
|
87
88
|
|
|
89
|
+
// Localization
|
|
90
|
+
export { registerTranslations } from './l10n/index.js'
|
|
91
|
+
|
|
88
92
|
// Utilities
|
|
89
93
|
export { buildHeaders, buildQueryString, parseResponseError, networkError, genericError } from './utils/index.js'
|
|
90
94
|
export { columnsFromSchema, formatValue, filtersFromSchema, fieldsFromSchema } from './utils/index.js'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getLanguage, register } from '@nextcloud/l10n'
|
|
2
|
+
import en from '../../l10n/en.json'
|
|
3
|
+
import nl from '../../l10n/nl.json'
|
|
4
|
+
|
|
5
|
+
const BUNDLES = { en, nl }
|
|
6
|
+
const APP_NAME = 'nextcloud-vue'
|
|
7
|
+
|
|
8
|
+
export function registerTranslations() {
|
|
9
|
+
const lang = (getLanguage() || 'en').split(/[-_]/)[0]
|
|
10
|
+
const bundle = BUNDLES[lang] ?? BUNDLES.en
|
|
11
|
+
register(APP_NAME, bundle.translations)
|
|
12
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written type definitions for `createCrudStore` (implementation is in
|
|
3
|
+
* `createCrudStore.js`). The library ships as JavaScript, but this file gives
|
|
4
|
+
* TypeScript consumers full entity inference, feature-flag gating, and
|
|
5
|
+
* `extend` merging with correct `this` typing.
|
|
6
|
+
*
|
|
7
|
+
* Design notes:
|
|
8
|
+
* - `const Id extends string` / `const F extends Features` preserves literal
|
|
9
|
+
* types so `features: { loading: true }` flows `true` through the
|
|
10
|
+
* conditional types without requiring `as const` at the call site.
|
|
11
|
+
* Requires TypeScript 5.0+.
|
|
12
|
+
* - Entity inference: if `config.entity` is a class constructor, `T` is the
|
|
13
|
+
* instance type. Otherwise `T` falls back to `unknown` unless the caller
|
|
14
|
+
* passes it explicitly via the second overload.
|
|
15
|
+
* - `ThisType<...>` inside `extend.actions` / `extend.getters` makes `this`
|
|
16
|
+
* resolve to the fully-merged store (state + getters + base actions +
|
|
17
|
+
* extended actions), matching Pinia's own runtime semantics.
|
|
18
|
+
* - `Omit<BaseActions<T>, keyof ExtActions>` implements override precedence:
|
|
19
|
+
* an extend action with the same name as a base action replaces it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { StoreDefinition, _GettersTree } from 'pinia'
|
|
23
|
+
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
// Utility types
|
|
26
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Forces TypeScript to eagerly evaluate an intersection of types into a
|
|
30
|
+
* single flat object shape. Purely a hover/tooltip ergonomics helper —
|
|
31
|
+
* semantically identical to the input.
|
|
32
|
+
*
|
|
33
|
+
* Without this, VSCode's quick-info tooltip shows `StoreDefinition<...,
|
|
34
|
+
* FullState<Source, ...> & ..., ..., MergedActions<...>>` with the aliases
|
|
35
|
+
* left unexpanded, making it hard to see which properties the store has.
|
|
36
|
+
* With this, hover shows `{ item: Source | null; list: Source[]; ... }`
|
|
37
|
+
* directly.
|
|
38
|
+
*/
|
|
39
|
+
export type Prettify<T> = { [K in keyof T]: T[K] } & {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A class constructor accepting raw data (e.g. `new Source(data)`).
|
|
43
|
+
*/
|
|
44
|
+
export type EntityClass<T> = new (data: any) => T
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract the instance type from an entity-class config field.
|
|
48
|
+
* Falls back to `unknown` when no class is provided.
|
|
49
|
+
*/
|
|
50
|
+
export type InferEntity<E> =
|
|
51
|
+
E extends EntityClass<infer T> ? T :
|
|
52
|
+
E extends null | undefined ? unknown :
|
|
53
|
+
unknown
|
|
54
|
+
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Feature flags
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Optional behavioral toggles. When a flag is set, the factory adds the
|
|
61
|
+
* corresponding state fields, getters, and actions to the store.
|
|
62
|
+
*/
|
|
63
|
+
export interface Features {
|
|
64
|
+
/** Add `loading` / `error` state plus the `isLoading` / `getError` getters. */
|
|
65
|
+
loading?: boolean
|
|
66
|
+
/** Add `viewMode` state, the `getViewMode` getter, and a `setViewMode(mode)` action. */
|
|
67
|
+
viewMode?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type LoadingState<F> = F extends { loading: true } ? { loading: boolean; error: string | null } : {}
|
|
71
|
+
export type ViewModeState<F> = F extends { viewMode: true } ? { viewMode: string } : {}
|
|
72
|
+
|
|
73
|
+
// Getter trees are declared as `(state) => value` and Pinia exposes them on
|
|
74
|
+
// the store as `.name: value`. We declare them as getter functions so the
|
|
75
|
+
// `StoreDefinition<..., Getters, ...>` return-type mapping kicks in.
|
|
76
|
+
export type LoadingGetters<F> = F extends { loading: true }
|
|
77
|
+
? {
|
|
78
|
+
isLoading: (state: { loading: boolean }) => boolean
|
|
79
|
+
getError: (state: { error: string | null }) => string | null
|
|
80
|
+
}
|
|
81
|
+
: {}
|
|
82
|
+
export type ViewModeGetters<F> = F extends { viewMode: true }
|
|
83
|
+
? { getViewMode: (state: { viewMode: string }) => string }
|
|
84
|
+
: {}
|
|
85
|
+
|
|
86
|
+
export type ViewModeActions<F> = F extends { viewMode: true } ? { setViewMode(mode: string): void } : {}
|
|
87
|
+
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Base state, getters and actions
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* State shape every store receives by default, independent of features and
|
|
94
|
+
* extensions. Feature-specific fields (`loading`, `error`, `viewMode`) and
|
|
95
|
+
* anything contributed by `extend.state` / plugins are added on top.
|
|
96
|
+
*/
|
|
97
|
+
export interface BaseState<T> {
|
|
98
|
+
/** The currently active/selected item, or `null` when nothing is selected. */
|
|
99
|
+
item: T | null
|
|
100
|
+
/** The full list of items as last returned by `refreshList`. */
|
|
101
|
+
list: T[]
|
|
102
|
+
/** Active filter criteria (merged via `setFilters`). */
|
|
103
|
+
filters: Record<string, unknown>
|
|
104
|
+
/** Current pagination position. `limit` defaults to 20. */
|
|
105
|
+
pagination: { page: number; limit: number }
|
|
106
|
+
/**
|
|
107
|
+
* Internal, runtime-resolved configuration exposed so extend actions and
|
|
108
|
+
* plugins can build URLs, clean fields, or instantiate the entity class
|
|
109
|
+
* without re-reading user config.
|
|
110
|
+
*/
|
|
111
|
+
_options: {
|
|
112
|
+
/** The raw endpoint segment supplied to the factory (e.g. `'sources'`). */
|
|
113
|
+
endpoint: string
|
|
114
|
+
/** Fields stripped by `cleanForSave` before POST/PUT. */
|
|
115
|
+
cleanFields: readonly string[]
|
|
116
|
+
/** Fully-qualified base URL for this store's REST endpoint, already `prefixUrl`-normalized. */
|
|
117
|
+
baseApiUrl: string
|
|
118
|
+
/** Entity class constructor, or `null` when the store returns raw data. */
|
|
119
|
+
entity: EntityClass<T> | null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Actions available on every store regardless of configuration. Extend
|
|
125
|
+
* actions with the same name replace these (see `MergedActions`).
|
|
126
|
+
*/
|
|
127
|
+
export interface BaseActions<T> {
|
|
128
|
+
/** Set the active item. Wraps in the configured Entity class when present; pass `null` to clear. */
|
|
129
|
+
setItem(data: T | Partial<T> | null): void
|
|
130
|
+
/** Replace the item list. Maps each entry through the Entity class when configured. */
|
|
131
|
+
setList(data: Array<T | Partial<T>>): void
|
|
132
|
+
/** Set `pagination.page` and `pagination.limit`. `limit` defaults to 20. */
|
|
133
|
+
setPagination(page: number, limit?: number): void
|
|
134
|
+
/** Merge filter key/value pairs into the current `filters` object. */
|
|
135
|
+
setFilters(filters: Record<string, unknown>): void
|
|
136
|
+
/** GET the list endpoint. Optional `search` is appended as `?_search=`; `soft=true` skips the loading toggle. */
|
|
137
|
+
refreshList(search?: string | null, soft?: boolean): Promise<{ response: Response; data: T[] }>
|
|
138
|
+
/** GET `/:id`, set it as the active item, and return the raw response data. */
|
|
139
|
+
getOne(id: string | number): Promise<T>
|
|
140
|
+
/** DELETE `/:id`, refresh the list, and clear the active item. */
|
|
141
|
+
deleteOne(idOrItem: string | number | { id: string | number }): Promise<{ response: Response }>
|
|
142
|
+
/** POST or PUT the item depending on whether it has an `id`, then refresh the list. */
|
|
143
|
+
save(item: Partial<T>): Promise<{ response: Response; data: T }>
|
|
144
|
+
/** Return a copy of `item` with `cleanFields` stripped, suitable for POST/PUT bodies. */
|
|
145
|
+
cleanForSave(item: T | Partial<T>): Partial<T>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Action merge rule:
|
|
150
|
+
* - Base actions not overridden by the extend block are preserved.
|
|
151
|
+
* - Actions declared in `extend.actions` replace base actions of the same name.
|
|
152
|
+
* - `viewMode` feature action is appended when the flag is set.
|
|
153
|
+
* - Plugin-contributed actions are reachable via the loose index signature
|
|
154
|
+
* on `PluginActionContribution` below (typed as `any`-returning callables).
|
|
155
|
+
*/
|
|
156
|
+
export type PluginActionContribution = { [key: string]: (...args: any[]) => any }
|
|
157
|
+
|
|
158
|
+
export type MergedActions<T, ExtActions, F> =
|
|
159
|
+
Omit<BaseActions<T>, keyof ExtActions> & ExtActions & ViewModeActions<F> & PluginActionContribution
|
|
160
|
+
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
// Full store shape (state + getters + actions) used inside ThisType<...>
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
// Plugins contribute arbitrary state, getters, and actions at runtime. We spread
|
|
166
|
+
// a loose index signature into the resolved store shape so plugin-contributed
|
|
167
|
+
// members are reachable via dot access without `as any`. The index signature
|
|
168
|
+
// returns `any` (not `unknown`) so callable plugin actions (`store.refreshLogs()`)
|
|
169
|
+
// type-check without an intermediate cast — trade-off is that typos on
|
|
170
|
+
// plugin-contributed members are not caught. Plugins needing strict typing
|
|
171
|
+
// should ship their own `.d.ts` augmenting the specific store.
|
|
172
|
+
export type PluginContribution = { [key: string]: any }
|
|
173
|
+
|
|
174
|
+
export type FullState<T, ExtState, F> =
|
|
175
|
+
BaseState<T> & ExtState & LoadingState<F> & ViewModeState<F> & PluginContribution
|
|
176
|
+
|
|
177
|
+
export type FullGetters<ExtGetters, F> =
|
|
178
|
+
ExtGetters & LoadingGetters<F> & ViewModeGetters<F>
|
|
179
|
+
|
|
180
|
+
export type StoreThis<T, ExtState, ExtGetters, ExtActions, F> =
|
|
181
|
+
FullState<T, ExtState, F> &
|
|
182
|
+
// Getters are callable-free on the Pinia store instance — accessed as properties.
|
|
183
|
+
// We narrow each getter declaration to its return type so `this.myGetter` is typed.
|
|
184
|
+
{ [K in keyof ExtGetters]: ExtGetters[K] extends (state: any) => infer R ? R : never } &
|
|
185
|
+
LoadingGetters<F> & ViewModeGetters<F> &
|
|
186
|
+
MergedActions<T, ExtActions, F>
|
|
187
|
+
|
|
188
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
189
|
+
// Plugins
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A plugin definition. Merged into the store at creation time:
|
|
194
|
+
* state is spread into store state, getters into getters, actions into actions.
|
|
195
|
+
*
|
|
196
|
+
* Merge precedence: base actions → plugin actions → extend.actions.
|
|
197
|
+
*
|
|
198
|
+
* Plugin-contributed properties are loosely typed on the resulting store
|
|
199
|
+
* (they appear as unknown state / any-returning actions). Consumers needing
|
|
200
|
+
* strict types on plugin output should augment the store's types at the
|
|
201
|
+
* call site, or use a plugin that ships a dedicated `.d.ts` with the shape.
|
|
202
|
+
*
|
|
203
|
+
* If `setup` is provided, it is invoked once per store instance the first
|
|
204
|
+
* time `useStore()` resolves it. Use this to register
|
|
205
|
+
* `store.$onAction` / `store.$subscribe` observers that react to base or
|
|
206
|
+
* other plugin actions without having to override them.
|
|
207
|
+
*/
|
|
208
|
+
export interface CrudPlugin {
|
|
209
|
+
/** Unique plugin identifier; used by `clearAllSubResources` and for debugging. */
|
|
210
|
+
name: string
|
|
211
|
+
/** State factory returning the state fields this plugin contributes. */
|
|
212
|
+
state?: () => Record<string, unknown>
|
|
213
|
+
/** Getters this plugin contributes — each receives the store state. */
|
|
214
|
+
getters?: Record<string, (state: any) => unknown>
|
|
215
|
+
/** Actions this plugin contributes. May override a base action with the same name. */
|
|
216
|
+
actions?: Record<string, (...args: any[]) => any>
|
|
217
|
+
/**
|
|
218
|
+
* Optional lifecycle hook run once per store instance, the first time
|
|
219
|
+
* `useStore()` resolves it. Typically used to register
|
|
220
|
+
* `store.$onAction` / `store.$subscribe` subscribers so the plugin can
|
|
221
|
+
* react to base or other-plugin actions without overriding them.
|
|
222
|
+
*/
|
|
223
|
+
setup?: (store: any) => void
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface PluginContrib {
|
|
227
|
+
[key: string]: unknown
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
231
|
+
// Config & extend shapes
|
|
232
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Domain-specific additions layered on top of the base store. Merged last,
|
|
236
|
+
* so `extend` can still override anything a plugin contributed.
|
|
237
|
+
*/
|
|
238
|
+
export interface ExtendConfig<
|
|
239
|
+
T,
|
|
240
|
+
ExtState extends Record<string, unknown>,
|
|
241
|
+
ExtGetters extends _GettersTree<BaseState<T> & ExtState>,
|
|
242
|
+
ExtActions extends Record<string, (...args: any[]) => any>,
|
|
243
|
+
F extends Features,
|
|
244
|
+
> {
|
|
245
|
+
/** State factory returning extra state properties merged into the store. */
|
|
246
|
+
state?: () => ExtState
|
|
247
|
+
/** Extra getters, or overrides of base/plugin getters with the same name. */
|
|
248
|
+
getters?: ExtGetters & ThisType<StoreThis<T, ExtState, ExtGetters, ExtActions, F>>
|
|
249
|
+
/** Extra actions, or overrides of base/plugin actions with the same name. */
|
|
250
|
+
actions?: ExtActions & ThisType<StoreThis<T, ExtState, ExtGetters, ExtActions, F>>
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Full configuration object accepted by `createCrudStore`. Mirrors the
|
|
255
|
+
* runtime options documented in `createCrudStore.js`.
|
|
256
|
+
*/
|
|
257
|
+
export interface CrudConfig<
|
|
258
|
+
T,
|
|
259
|
+
ExtState extends Record<string, unknown>,
|
|
260
|
+
ExtGetters extends _GettersTree<BaseState<T> & ExtState>,
|
|
261
|
+
ExtActions extends Record<string, (...args: any[]) => any>,
|
|
262
|
+
F extends Features,
|
|
263
|
+
Entity = EntityClass<T> | null | undefined,
|
|
264
|
+
> {
|
|
265
|
+
/** API resource path segment appended to `baseUrl` (e.g. `'sources'`). Required. */
|
|
266
|
+
endpoint: string
|
|
267
|
+
/** API base URL before the endpoint. Defaults to `'/apps/openregister/api'`. */
|
|
268
|
+
baseUrl?: string
|
|
269
|
+
/** Entity class constructor used to wrap raw API data; pass `null` for raw data. */
|
|
270
|
+
entity?: Entity
|
|
271
|
+
/** Fields stripped from items before POST/PUT. Defaults to `['id','uuid','created','updated']`. */
|
|
272
|
+
cleanFields?: readonly string[]
|
|
273
|
+
/** Feature flags enabling optional state/getters/actions. See `Features`. */
|
|
274
|
+
features?: F
|
|
275
|
+
/**
|
|
276
|
+
* Custom parser for `refreshList`'s JSON response body. Called with the
|
|
277
|
+
* store as `this`, so it can also perform side-effects (e.g. storing
|
|
278
|
+
* pagination info). Must return an array of items. Default:
|
|
279
|
+
* `(json) => json.results`.
|
|
280
|
+
*/
|
|
281
|
+
parseListResponse?: (this: StoreThis<T, ExtState, ExtGetters, ExtActions, F>, json: unknown) => T[] | Array<Partial<T>>
|
|
282
|
+
/**
|
|
283
|
+
* Plugin definitions merged into the store. Same shape as object-store
|
|
284
|
+
* plugins (`{ name, state?, getters?, actions?, setup? }`). Merge order
|
|
285
|
+
* is base → plugins → extend.
|
|
286
|
+
*/
|
|
287
|
+
plugins?: readonly CrudPlugin[]
|
|
288
|
+
/** Extra state/getters/actions merged on top of base and plugin contributions. */
|
|
289
|
+
extend?: ExtendConfig<T, ExtState, ExtGetters, ExtActions, F>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
293
|
+
// The factory — overloaded
|
|
294
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Overload 1 — entity inference.
|
|
298
|
+
*
|
|
299
|
+
* Provide `config.entity` as a class constructor; `T` is inferred as the
|
|
300
|
+
* instance type:
|
|
301
|
+
*
|
|
302
|
+
* ```ts
|
|
303
|
+
* const useSourceStore = createCrudStore('source', {
|
|
304
|
+
* endpoint: 'sources',
|
|
305
|
+
* entity: Source, // T = Source
|
|
306
|
+
* features: { loading: true },
|
|
307
|
+
* })
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
export function createCrudStore<
|
|
311
|
+
Entity extends EntityClass<any>,
|
|
312
|
+
const Id extends string = string,
|
|
313
|
+
const F extends Features = {},
|
|
314
|
+
ExtState extends Record<string, unknown> = {},
|
|
315
|
+
ExtGetters extends _GettersTree<BaseState<InferEntity<Entity>> & ExtState> = {},
|
|
316
|
+
ExtActions extends Record<string, (...args: any[]) => any> = {},
|
|
317
|
+
>(
|
|
318
|
+
name: Id,
|
|
319
|
+
config: CrudConfig<InferEntity<Entity>, ExtState, ExtGetters, ExtActions, F, Entity>,
|
|
320
|
+
): StoreDefinition<
|
|
321
|
+
Id,
|
|
322
|
+
Prettify<FullState<InferEntity<Entity>, ExtState, F>>,
|
|
323
|
+
Prettify<FullGetters<ExtGetters, F>>,
|
|
324
|
+
Prettify<MergedActions<InferEntity<Entity>, ExtActions, F>>
|
|
325
|
+
>
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Overload 2 — explicit `T` for raw-data stores (no entity class).
|
|
329
|
+
*
|
|
330
|
+
* ```ts
|
|
331
|
+
* interface LogShape { id: number; message: string }
|
|
332
|
+
* const useLogStore = createCrudStore<'log', LogShape>('log', { endpoint: 'logs' })
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
export function createCrudStore<
|
|
336
|
+
const Id extends string,
|
|
337
|
+
T,
|
|
338
|
+
const F extends Features = {},
|
|
339
|
+
ExtState extends Record<string, unknown> = {},
|
|
340
|
+
ExtGetters extends _GettersTree<BaseState<T> & ExtState> = {},
|
|
341
|
+
ExtActions extends Record<string, (...args: any[]) => any> = {},
|
|
342
|
+
>(
|
|
343
|
+
name: Id,
|
|
344
|
+
config: CrudConfig<T, ExtState, ExtGetters, ExtActions, F, null | undefined>,
|
|
345
|
+
): StoreDefinition<
|
|
346
|
+
Id,
|
|
347
|
+
FullState<T, ExtState, F>,
|
|
348
|
+
FullGetters<ExtGetters, F>,
|
|
349
|
+
MergedActions<T, ExtActions, F>
|
|
350
|
+
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { buildHeaders, prefixUrl } from '../utils/headers.js'
|
|
3
3
|
import { parseResponseError } from '../utils/errors.js'
|
|
4
|
+
import { mergePluginState, mergePluginGetters, mergePluginActions } from './pluginMerge.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Default fields stripped from items before POST/PUT.
|
|
@@ -76,10 +77,14 @@ function defaultParseListResponse(json) {
|
|
|
76
77
|
* @param {Function} [config.parseListResponse] Custom response parser for refreshList.
|
|
77
78
|
* Receives the parsed JSON body with the store instance as `this`.
|
|
78
79
|
* Must return an array of items. Default: `(json) => json.results`
|
|
80
|
+
* @param {Array} [config.plugins] Array of plugin definitions to merge into the store.
|
|
81
|
+
* Each plugin is `{ name, state?, getters?, actions? }` — same shape as object-store
|
|
82
|
+
* plugins. Merge order is base → plugins → extend, so `extend` can still override
|
|
83
|
+
* anything a plugin provides.
|
|
79
84
|
* @param {object} [config.extend] Extra state/getters/actions to merge into the store
|
|
80
85
|
* @param {Function} [config.extend.state] State factory returning extra state properties
|
|
81
86
|
* @param {object} [config.extend.getters] Extra getters (or overrides of base getters)
|
|
82
|
-
* @param {object} [config.extend.actions] Extra actions (or overrides of base actions)
|
|
87
|
+
* @param {object} [config.extend.actions] Extra actions (or overrides of base/plugin actions)
|
|
83
88
|
* @return {Function} Pinia store composable (useXxxStore)
|
|
84
89
|
*/
|
|
85
90
|
export function createCrudStore(name, config = {}) {
|
|
@@ -90,6 +95,7 @@ export function createCrudStore(name, config = {}) {
|
|
|
90
95
|
cleanFields = DEFAULT_CLEAN_FIELDS,
|
|
91
96
|
features = {},
|
|
92
97
|
parseListResponse = defaultParseListResponse,
|
|
98
|
+
plugins = [],
|
|
93
99
|
extend = {},
|
|
94
100
|
} = config
|
|
95
101
|
|
|
@@ -99,7 +105,18 @@ export function createCrudStore(name, config = {}) {
|
|
|
99
105
|
|
|
100
106
|
const baseApiUrl = prefixUrl(`${baseUrl}/${endpoint}`)
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
const pluginState = mergePluginState(plugins)
|
|
109
|
+
const pluginGetters = mergePluginGetters(plugins)
|
|
110
|
+
const pluginActions = mergePluginActions(plugins)
|
|
111
|
+
const setupPlugins = plugins.filter((p) => typeof p.setup === 'function')
|
|
112
|
+
// Track which store instances have already been set up so plugin setup
|
|
113
|
+
// hooks run exactly once per instance, even if useStore() is called many
|
|
114
|
+
// times. WeakSet lets garbage collection reclaim entries when a Pinia
|
|
115
|
+
// instance (and therefore its stores) are discarded — e.g. between tests
|
|
116
|
+
// that call createPinia() afresh.
|
|
117
|
+
const initialized = new WeakSet()
|
|
118
|
+
|
|
119
|
+
const useStore = defineStore(name, {
|
|
103
120
|
state: () => ({
|
|
104
121
|
// ── Core state ──
|
|
105
122
|
item: null,
|
|
@@ -111,8 +128,11 @@ export function createCrudStore(name, config = {}) {
|
|
|
111
128
|
...(features.loading ? { loading: false, error: null } : {}),
|
|
112
129
|
...(features.viewMode ? { viewMode: 'cards' } : {}),
|
|
113
130
|
|
|
114
|
-
// ──
|
|
115
|
-
|
|
131
|
+
// ── Plugin state ──
|
|
132
|
+
...pluginState,
|
|
133
|
+
|
|
134
|
+
// ── Internal config (available to extend actions and plugins) ──
|
|
135
|
+
_options: { endpoint, cleanFields, baseApiUrl, entity: Entity },
|
|
116
136
|
|
|
117
137
|
// ── Domain-specific state ──
|
|
118
138
|
...(typeof extend.state === 'function' ? extend.state() : {}),
|
|
@@ -128,6 +148,9 @@ export function createCrudStore(name, config = {}) {
|
|
|
128
148
|
}
|
|
129
149
|
: {}),
|
|
130
150
|
|
|
151
|
+
// ── Plugin getters ──
|
|
152
|
+
...pluginGetters,
|
|
153
|
+
|
|
131
154
|
// ── Domain-specific getters ──
|
|
132
155
|
...(extend.getters ?? {}),
|
|
133
156
|
},
|
|
@@ -353,8 +376,38 @@ export function createCrudStore(name, config = {}) {
|
|
|
353
376
|
}
|
|
354
377
|
},
|
|
355
378
|
|
|
356
|
-
// ──
|
|
379
|
+
// ── Plugin actions (may override base actions) ──
|
|
380
|
+
...pluginActions,
|
|
381
|
+
|
|
382
|
+
// ── Domain-specific actions (may override base/plugin actions) ──
|
|
357
383
|
...(extend.actions ?? {}),
|
|
358
384
|
},
|
|
359
385
|
})
|
|
386
|
+
|
|
387
|
+
// When no plugin declares a setup hook, return Pinia's composable
|
|
388
|
+
// directly — zero runtime overhead for the common case.
|
|
389
|
+
if (setupPlugins.length === 0) {
|
|
390
|
+
return useStore
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Wrapped composable: resolves the Pinia store, then runs each plugin's
|
|
395
|
+
* `setup(store)` exactly once per instance. Plugins typically use the
|
|
396
|
+
* setup hook to register `store.$onAction` / `store.$subscribe`
|
|
397
|
+
* subscriptions that observe base or other plugin actions without
|
|
398
|
+
* overriding them.
|
|
399
|
+
*
|
|
400
|
+
* @param {import('pinia').Pinia} [pinia] Optional Pinia instance override
|
|
401
|
+
* @return {object} The Pinia store instance with all plugin setups applied
|
|
402
|
+
*/
|
|
403
|
+
return function useCrudStore(pinia) {
|
|
404
|
+
const store = useStore(pinia)
|
|
405
|
+
if (!initialized.has(store)) {
|
|
406
|
+
initialized.add(store)
|
|
407
|
+
for (const plugin of setupPlugins) {
|
|
408
|
+
plugin.setup(store)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return store
|
|
412
|
+
}
|
|
360
413
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for merging Pinia store plugins into a store definition.
|
|
3
|
+
*
|
|
4
|
+
* Used by both `createObjectStore` (via `useObjectStore.js`) and
|
|
5
|
+
* `createCrudStore`, so plugin authors get the same shape everywhere:
|
|
6
|
+
* `{ name, state?, getters?, actions? }`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merge plugin state factories into a single state object.
|
|
11
|
+
*
|
|
12
|
+
* @param {Array} plugins Array of plugin definitions
|
|
13
|
+
* @return {object} Merged state object
|
|
14
|
+
*/
|
|
15
|
+
export function mergePluginState(plugins) {
|
|
16
|
+
const merged = {}
|
|
17
|
+
for (const plugin of plugins) {
|
|
18
|
+
if (plugin.state) {
|
|
19
|
+
Object.assign(merged, plugin.state())
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return merged
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Merge plugin getters into a single getters object.
|
|
27
|
+
*
|
|
28
|
+
* @param {Array} plugins Array of plugin definitions
|
|
29
|
+
* @return {object} Merged getters object
|
|
30
|
+
*/
|
|
31
|
+
export function mergePluginGetters(plugins) {
|
|
32
|
+
const merged = {}
|
|
33
|
+
for (const plugin of plugins) {
|
|
34
|
+
if (plugin.getters) {
|
|
35
|
+
Object.assign(merged, plugin.getters)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return merged
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Merge plugin actions into a single actions object.
|
|
43
|
+
*
|
|
44
|
+
* @param {Array} plugins Array of plugin definitions
|
|
45
|
+
* @return {object} Merged actions object
|
|
46
|
+
*/
|
|
47
|
+
export function mergePluginActions(plugins) {
|
|
48
|
+
const merged = {}
|
|
49
|
+
for (const plugin of plugins) {
|
|
50
|
+
if (plugin.actions) {
|
|
51
|
+
Object.assign(merged, plugin.actions)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return merged
|
|
55
|
+
}
|
|
@@ -2,6 +2,7 @@ export { auditTrailsPlugin } from './auditTrails.js'
|
|
|
2
2
|
export { relationsPlugin } from './relations.js'
|
|
3
3
|
export { filesPlugin } from './files.js'
|
|
4
4
|
export { lifecyclePlugin } from './lifecycle.js'
|
|
5
|
+
export { logsPlugin } from './logs.js'
|
|
5
6
|
export { registerMappingPlugin } from './registerMapping.js'
|
|
6
7
|
export { selectionPlugin } from './selection.js'
|
|
7
8
|
export { searchPlugin, SEARCH_TYPE, getRegisterApiUrl, getSchemaApiUrl } from './search.js'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CrudPlugin } from '../createCrudStore'
|
|
2
|
+
|
|
3
|
+
export interface LogsPluginOptions {
|
|
4
|
+
/** Required. Query-param name carrying the active item's id (e.g. 'source_id'). */
|
|
5
|
+
parentIdParam: string
|
|
6
|
+
/** Path segment appended to the store's base API URL. Default: 'logs'. */
|
|
7
|
+
path?: string
|
|
8
|
+
/** Default query params merged before caller-supplied filters. Default: `{ '_sort[created]': 'desc' }`. */
|
|
9
|
+
defaultSort?: Record<string, string>
|
|
10
|
+
/**
|
|
11
|
+
* When true, the plugin's `setup` hook registers a `store.$onAction`
|
|
12
|
+
* subscriber that auto-fires `refreshLogs()` after every `setItem` with
|
|
13
|
+
* an id (or `clearLogs()` when the item is cleared). Composes with other
|
|
14
|
+
* plugins that observe `setItem`.
|
|
15
|
+
*/
|
|
16
|
+
autoRefreshOnItemChange?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a logs sub-resource plugin for a `createCrudStore`.
|
|
21
|
+
*/
|
|
22
|
+
export function logsPlugin(options: LogsPluginOptions): CrudPlugin
|