metaowl 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +13 -15
- package/build/runtime/bin/metaowl-build.js +10 -0
- package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
- package/build/runtime/bin/metaowl-dev.js +10 -0
- package/build/runtime/bin/metaowl-generate.js +231 -0
- package/build/runtime/bin/metaowl-lint.js +58 -0
- package/build/runtime/bin/utils.js +68 -0
- package/build/runtime/index.js +141 -0
- package/build/runtime/modules/app-mounter.js +65 -0
- package/build/runtime/modules/auto-import.js +140 -0
- package/build/runtime/modules/cache.js +49 -0
- package/build/runtime/modules/composables.js +353 -0
- package/build/runtime/modules/error-boundary.js +116 -0
- package/build/runtime/modules/fetch.js +31 -0
- package/build/runtime/modules/file-router.js +205 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +167 -0
- package/build/runtime/modules/layouts.js +163 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +264 -0
- package/build/runtime/modules/pwa.js +262 -0
- package/build/runtime/modules/router.js +389 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +196 -0
- package/build/runtime/modules/templates-manager.js +52 -0
- package/build/runtime/modules/test-utils.js +238 -0
- package/build/runtime/vite/plugin.js +183 -0
- package/eslint.js +29 -0
- package/package.json +29 -11
- package/CONTRIBUTING.md +0 -49
- package/bin/metaowl-build.js +0 -12
- package/bin/metaowl-dev.js +0 -12
- package/bin/metaowl-generate.js +0 -339
- package/bin/metaowl-lint.js +0 -71
- package/bin/utils.js +0 -82
- package/index.js +0 -328
- package/modules/app-mounter.js +0 -104
- package/modules/auto-import.js +0 -225
- package/modules/cache.js +0 -59
- package/modules/composables.js +0 -600
- package/modules/error-boundary.js +0 -228
- package/modules/fetch.js +0 -51
- package/modules/file-router.js +0 -478
- package/modules/forms.js +0 -353
- package/modules/i18n.js +0 -333
- package/modules/layouts.js +0 -431
- package/modules/link.js +0 -255
- package/modules/meta.js +0 -119
- package/modules/odoo-rpc.js +0 -511
- package/modules/pwa.js +0 -515
- package/modules/router.js +0 -769
- package/modules/seo.js +0 -501
- package/modules/store.js +0 -409
- package/modules/templates-manager.js +0 -89
- package/modules/test-utils.js +0 -532
- package/test/auto-import.test.js +0 -110
- package/test/cache.test.js +0 -55
- package/test/composables.test.js +0 -103
- package/test/dynamic-routes.test.js +0 -469
- package/test/error-boundary.test.js +0 -126
- package/test/fetch.test.js +0 -100
- package/test/file-router.test.js +0 -55
- package/test/forms.test.js +0 -203
- package/test/i18n.test.js +0 -188
- package/test/layouts.test.js +0 -395
- package/test/link.test.js +0 -189
- package/test/meta.test.js +0 -146
- package/test/odoo-rpc.test.js +0 -547
- package/test/pwa.test.js +0 -154
- package/test/router-guards.test.js +0 -229
- package/test/router.test.js +0 -77
- package/test/seo.test.js +0 -353
- package/test/store.test.js +0 -476
- package/test/templates-manager.test.js +0 -83
- package/test/test-utils.test.js +0 -314
- package/vite/plugin.js +0 -277
- package/vitest.config.js +0 -8
package/modules/store.js
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module Store
|
|
3
|
-
*
|
|
4
|
-
* Lightweight state management for OWL applications, inspired by Pinia/Vuex.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Reactive state with OWL's reactivity system
|
|
8
|
-
* - Synchronous mutations for state changes
|
|
9
|
-
* - Asynchronous actions with context access
|
|
10
|
-
* - Getter support for computed values
|
|
11
|
-
* - Module composition via plugins
|
|
12
|
-
* - DevTools integration support
|
|
13
|
-
* - Persistence plugin for localStorage/sessionStorage
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* import { Store } from 'metaowl'
|
|
17
|
-
*
|
|
18
|
-
* const useUserStore = Store.define('user', {
|
|
19
|
-
* state: () => ({ name: '', loggedIn: false }),
|
|
20
|
-
* getters: {
|
|
21
|
-
* displayName: (state) => state.name || 'Guest'
|
|
22
|
-
* },
|
|
23
|
-
* mutations: {
|
|
24
|
-
* setName: (state, name) => { state.name = name },
|
|
25
|
-
* setLoggedIn: (state, value) => { state.loggedIn = value }
|
|
26
|
-
* },
|
|
27
|
-
* actions: {
|
|
28
|
-
* async login({ commit, state }, credentials) {
|
|
29
|
-
* const result = await Fetch.url('/api/login', 'POST', credentials)
|
|
30
|
-
* commit('setName', result.name)
|
|
31
|
-
* commit('setLoggedIn', true)
|
|
32
|
-
* return result
|
|
33
|
-
* },
|
|
34
|
-
* logout({ commit }) {
|
|
35
|
-
* commit('setName', '')
|
|
36
|
-
* commit('setLoggedIn', false)
|
|
37
|
-
* }
|
|
38
|
-
* }
|
|
39
|
-
* })
|
|
40
|
-
*
|
|
41
|
-
* // In a component:
|
|
42
|
-
* const store = useUserStore()
|
|
43
|
-
* console.log(store.state.name)
|
|
44
|
-
* console.log(store.getters.displayName)
|
|
45
|
-
* store.commit('setName', 'John')
|
|
46
|
-
* await store.dispatch('login', { email, password })
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import { reactive } from '@odoo/owl'
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Registry of all defined stores by their ID.
|
|
53
|
-
* @type {Map<string, Store>}
|
|
54
|
-
*/
|
|
55
|
-
const _stores = new Map()
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Global plugins applied to all stores.
|
|
59
|
-
* @type {Function[]}
|
|
60
|
-
*/
|
|
61
|
-
const _plugins = []
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Store class implementing the state management pattern.
|
|
65
|
-
*/
|
|
66
|
-
export class Store {
|
|
67
|
-
/**
|
|
68
|
-
* Creates a new Store instance.
|
|
69
|
-
*
|
|
70
|
-
* @param {string} id - Unique identifier for the store
|
|
71
|
-
* @param {object} config - Store configuration
|
|
72
|
-
* @param {Function} config.state - Factory function returning initial state object
|
|
73
|
-
* @param {Object.<string, Function>} [config.getters] - Computed property functions
|
|
74
|
-
* @param {Object.<string, Function>} [config.mutations] - Synchronous state mutation functions
|
|
75
|
-
* @param {Object.<string, Function>} [config.actions] - Asynchronous action functions
|
|
76
|
-
*/
|
|
77
|
-
constructor(id, config) {
|
|
78
|
-
this._id = id
|
|
79
|
-
this._config = config
|
|
80
|
-
this._state = reactive(config.state ? config.state() : {})
|
|
81
|
-
this._getters = {}
|
|
82
|
-
this._mutations = config.mutations || {}
|
|
83
|
-
this._actions = config.actions || {}
|
|
84
|
-
this._subscribers = []
|
|
85
|
-
this._actionSubscribers = []
|
|
86
|
-
|
|
87
|
-
// Initialize getters as getter functions
|
|
88
|
-
if (config.getters) {
|
|
89
|
-
for (const [name, fn] of Object.entries(config.getters)) {
|
|
90
|
-
Object.defineProperty(this._getters, name, {
|
|
91
|
-
get: () => fn(this._state, this._getters),
|
|
92
|
-
enumerable: true,
|
|
93
|
-
configurable: true
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Apply global plugins
|
|
99
|
-
for (const plugin of _plugins) {
|
|
100
|
-
plugin(this)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The store's unique identifier.
|
|
106
|
-
* @returns {string}
|
|
107
|
-
*/
|
|
108
|
-
get id() {
|
|
109
|
-
return this._id
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Reactive state object. Direct mutation is discouraged; use commit() instead.
|
|
114
|
-
* @returns {object}
|
|
115
|
-
*/
|
|
116
|
-
get state() {
|
|
117
|
-
return this._state
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Computed getters object.
|
|
122
|
-
* @returns {object}
|
|
123
|
-
*/
|
|
124
|
-
get getters() {
|
|
125
|
-
return this._getters
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Commit a synchronous mutation to modify state.
|
|
130
|
-
*
|
|
131
|
-
* @param {string} type - Mutation name
|
|
132
|
-
* @param {*} payload - Data passed to mutation handler
|
|
133
|
-
* @returns {*} Return value from mutation
|
|
134
|
-
* @throws {Error} If mutation is not defined
|
|
135
|
-
*/
|
|
136
|
-
commit(type, payload) {
|
|
137
|
-
const mutation = this._mutations[type]
|
|
138
|
-
if (!mutation) {
|
|
139
|
-
throw new Error(`[metaowl] Mutation "${type}" not found in store "${this._id}"`)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const prevState = JSON.parse(JSON.stringify(this._state))
|
|
143
|
-
const result = mutation(this._state, payload)
|
|
144
|
-
|
|
145
|
-
// Notify subscribers
|
|
146
|
-
for (const subscriber of this._subscribers) {
|
|
147
|
-
subscriber({ type, payload }, this._state, prevState)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return result
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Dispatch an asynchronous action.
|
|
155
|
-
*
|
|
156
|
-
* @param {string} type - Action name
|
|
157
|
-
* @param {*} payload - Data passed to action handler
|
|
158
|
-
* @returns {Promise<*>} Return value from action
|
|
159
|
-
* @throws {Error} If action is not defined
|
|
160
|
-
*/
|
|
161
|
-
async dispatch(type, payload) {
|
|
162
|
-
const action = this._actions[type]
|
|
163
|
-
if (!action) {
|
|
164
|
-
throw new Error(`[metaowl] Action "${type}" not found in store "${this._id}"`)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const context = {
|
|
168
|
-
state: this._state,
|
|
169
|
-
getters: this._getters,
|
|
170
|
-
commit: this.commit.bind(this),
|
|
171
|
-
dispatch: this.dispatch.bind(this)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Notify action subscribers (before)
|
|
175
|
-
for (const subscriber of this._actionSubscribers) {
|
|
176
|
-
subscriber({ type, payload }, 'before')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const result = await action(context, payload)
|
|
181
|
-
|
|
182
|
-
// Notify action subscribers (after)
|
|
183
|
-
for (const subscriber of this._actionSubscribers) {
|
|
184
|
-
subscriber({ type, payload }, 'after', result)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return result
|
|
188
|
-
} catch (error) {
|
|
189
|
-
// Notify action subscribers (error)
|
|
190
|
-
for (const subscriber of this._actionSubscribers) {
|
|
191
|
-
subscriber({ type, payload }, 'error', error)
|
|
192
|
-
}
|
|
193
|
-
throw error
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Subscribe to state changes. Callback receives mutation info and state snapshots.
|
|
199
|
-
*
|
|
200
|
-
* @param {Function} callback - (mutation, state, prevState) => void
|
|
201
|
-
* @returns {Function} Unsubscribe function
|
|
202
|
-
*/
|
|
203
|
-
subscribe(callback) {
|
|
204
|
-
this._subscribers.push(callback)
|
|
205
|
-
return () => {
|
|
206
|
-
const index = this._subscribers.indexOf(callback)
|
|
207
|
-
if (index > -1) this._subscribers.splice(index, 1)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Subscribe to action dispatches.
|
|
213
|
-
*
|
|
214
|
-
* @param {Function} callback - (action, status, result/error) => void
|
|
215
|
-
* @returns {Function} Unsubscribe function
|
|
216
|
-
*/
|
|
217
|
-
subscribeAction(callback) {
|
|
218
|
-
this._actionSubscribers.push(callback)
|
|
219
|
-
return () => {
|
|
220
|
-
const index = this._actionSubscribers.indexOf(callback)
|
|
221
|
-
if (index > -1) this._actionSubscribers.splice(index, 1)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Reset store state to initial values.
|
|
227
|
-
*/
|
|
228
|
-
reset() {
|
|
229
|
-
if (this._config.state) {
|
|
230
|
-
const initialState = this._config.state()
|
|
231
|
-
Object.keys(this._state).forEach(key => {
|
|
232
|
-
delete this._state[key]
|
|
233
|
-
})
|
|
234
|
-
Object.assign(this._state, initialState)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Define a new store or retrieve existing one.
|
|
240
|
-
*
|
|
241
|
-
* @param {string} id - Unique store identifier
|
|
242
|
-
* @param {object} config - Store configuration
|
|
243
|
-
* @returns {Function} Hook function that returns store instance
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* const useCounterStore = Store.define('counter', {
|
|
247
|
-
* state: () => ({ count: 0 }),
|
|
248
|
-
* getters: { double: (state) => state.count * 2 },
|
|
249
|
-
* mutations: { increment: (state) => state.count++ }
|
|
250
|
-
* })
|
|
251
|
-
*
|
|
252
|
-
* const store = useCounterStore()
|
|
253
|
-
* store.commit('increment')
|
|
254
|
-
* console.log(store.getters.double.value) // 2
|
|
255
|
-
*/
|
|
256
|
-
static define(id, config) {
|
|
257
|
-
return function useStore() {
|
|
258
|
-
if (!_stores.has(id)) {
|
|
259
|
-
_stores.set(id, new Store(id, config))
|
|
260
|
-
}
|
|
261
|
-
return _stores.get(id)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get a store by ID (for advanced use cases).
|
|
267
|
-
*
|
|
268
|
-
* @param {string} id - Store identifier
|
|
269
|
-
* @returns {Store|undefined}
|
|
270
|
-
*/
|
|
271
|
-
static get(id) {
|
|
272
|
-
return _stores.get(id)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Check if a store exists.
|
|
277
|
-
*
|
|
278
|
-
* @param {string} id - Store identifier
|
|
279
|
-
* @returns {boolean}
|
|
280
|
-
*/
|
|
281
|
-
static has(id) {
|
|
282
|
-
return _stores.has(id)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Remove a store from the registry.
|
|
287
|
-
*
|
|
288
|
-
* @param {string} id - Store identifier
|
|
289
|
-
* @returns {boolean} True if store was removed
|
|
290
|
-
*/
|
|
291
|
-
static remove(id) {
|
|
292
|
-
const store = _stores.get(id)
|
|
293
|
-
if (store) {
|
|
294
|
-
store.reset()
|
|
295
|
-
return _stores.delete(id)
|
|
296
|
-
}
|
|
297
|
-
return false
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Clear all stores (useful for testing).
|
|
302
|
-
*/
|
|
303
|
-
static clear() {
|
|
304
|
-
_stores.clear()
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Get all registered store IDs.
|
|
309
|
-
*
|
|
310
|
-
* @returns {string[]}
|
|
311
|
-
*/
|
|
312
|
-
static storeIds() {
|
|
313
|
-
return Array.from(_stores.keys())
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Register a global plugin applied to all new stores.
|
|
318
|
-
*
|
|
319
|
-
* @param {Function} plugin - (store) => void
|
|
320
|
-
*/
|
|
321
|
-
static use(plugin) {
|
|
322
|
-
_plugins.push(plugin)
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Plugin for persisting store state to storage (localStorage/sessionStorage).
|
|
328
|
-
*
|
|
329
|
-
* @param {object} options
|
|
330
|
-
* @param {Storage} [options.storage=localStorage] - Storage implementation
|
|
331
|
-
* @param {string} [options.key] - Custom storage key (defaults to store id)
|
|
332
|
-
* @param {string[]} [options.paths] - Specific state paths to persist (default: all)
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* Store.use(createPersistencePlugin({
|
|
336
|
-
* storage: localStorage,
|
|
337
|
-
* paths: ['user', 'preferences']
|
|
338
|
-
* }))
|
|
339
|
-
*/
|
|
340
|
-
export function createPersistencePlugin(options = {}) {
|
|
341
|
-
const { storage = localStorage, key, paths } = options
|
|
342
|
-
|
|
343
|
-
return function persistencePlugin(store) {
|
|
344
|
-
const storageKey = key || `metaowl:store:${store.id}`
|
|
345
|
-
|
|
346
|
-
// Restore state from storage on init
|
|
347
|
-
try {
|
|
348
|
-
const saved = storage.getItem(storageKey)
|
|
349
|
-
if (saved) {
|
|
350
|
-
const persisted = JSON.parse(saved)
|
|
351
|
-
if (paths) {
|
|
352
|
-
// Only restore specified paths
|
|
353
|
-
for (const path of paths) {
|
|
354
|
-
if (path in persisted && path in store.state) {
|
|
355
|
-
store.state[path] = persisted[path]
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
// Restore all
|
|
360
|
-
Object.assign(store.state, persisted)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} catch (e) {
|
|
364
|
-
console.warn('[metaowl] Failed to restore store from storage:', e)
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Subscribe to changes and persist
|
|
368
|
-
store.subscribe((mutation, state) => {
|
|
369
|
-
try {
|
|
370
|
-
const toPersist = paths
|
|
371
|
-
? Object.fromEntries(paths.map(p => [p, state[p]]))
|
|
372
|
-
: state
|
|
373
|
-
storage.setItem(storageKey, JSON.stringify(toPersist))
|
|
374
|
-
} catch (e) {
|
|
375
|
-
console.warn('[metaowl] Failed to persist store:', e)
|
|
376
|
-
}
|
|
377
|
-
})
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Simple store factory for basic use cases without full Store class overhead.
|
|
383
|
-
*
|
|
384
|
-
* @param {object} initialState - Initial state object
|
|
385
|
-
* @returns {object} Reactive state object with $patch method
|
|
386
|
-
*
|
|
387
|
-
* @example
|
|
388
|
-
* const counter = createStore({ count: 0 })
|
|
389
|
-
* counter.count++ // reactive
|
|
390
|
-
* counter.$patch({ count: 10 }) // batch update
|
|
391
|
-
*/
|
|
392
|
-
export function createStore(initialState = {}) {
|
|
393
|
-
const state = reactive({ ...initialState })
|
|
394
|
-
|
|
395
|
-
state.$patch = (partialState) => {
|
|
396
|
-
Object.assign(state, partialState)
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
state.$reset = () => {
|
|
400
|
-
Object.keys(state).forEach(key => {
|
|
401
|
-
if (!key.startsWith('$')) {
|
|
402
|
-
delete state[key]
|
|
403
|
-
}
|
|
404
|
-
})
|
|
405
|
-
Object.assign(state, initialState)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return state
|
|
409
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module TemplatesManager
|
|
3
|
-
*
|
|
4
|
-
* Template loading and merging utilities for OWL applications.
|
|
5
|
-
*/
|
|
6
|
-
import { loadFile } from '@odoo/owl'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Link component template.
|
|
10
|
-
* Automatically added to all templates.
|
|
11
|
-
* @type {string}
|
|
12
|
-
*/
|
|
13
|
-
const LINK_COMPONENT_TEMPLATE = /* xml */ `
|
|
14
|
-
<t t-name="Link">
|
|
15
|
-
<a
|
|
16
|
-
t-att="forwardedAttrs"
|
|
17
|
-
t-att-href="props.to"
|
|
18
|
-
t-att-class="linkClasses"
|
|
19
|
-
t-att-target="props.target"
|
|
20
|
-
t-att-rel="linkRel"
|
|
21
|
-
t-att-title="props.title"
|
|
22
|
-
t-att-download="props.download"
|
|
23
|
-
t-on-click="onClick"
|
|
24
|
-
>
|
|
25
|
-
<t t-slot="default"/>
|
|
26
|
-
</a>
|
|
27
|
-
</t>
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Internal templates that are automatically added.
|
|
32
|
-
* @type {string[]}
|
|
33
|
-
*/
|
|
34
|
-
const INTERNAL_TEMPLATES = [
|
|
35
|
-
LINK_COMPONENT_TEMPLATE
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Loads OWL XML template(s) into a string ready to be passed to OWL's mount() options.
|
|
40
|
-
*
|
|
41
|
-
* If a single file is provided that already contains <templates> wrapper (merged file),
|
|
42
|
-
* it's returned as-is. Otherwise, the content is wrapped in <templates>.
|
|
43
|
-
*
|
|
44
|
-
* @param {string|string[]} files - Array of URL-style XML paths or single path
|
|
45
|
-
* @returns {Promise<string>}
|
|
46
|
-
*/
|
|
47
|
-
export async function mergeTemplates(files) {
|
|
48
|
-
// Normalize to array
|
|
49
|
-
const fileArray = Array.isArray(files) ? files : [files]
|
|
50
|
-
|
|
51
|
-
// If there's only one file, check if it's already wrapped
|
|
52
|
-
if (fileArray.length === 1) {
|
|
53
|
-
try {
|
|
54
|
-
const content = await loadFile(fileArray[0])
|
|
55
|
-
// If already wrapped (merged templates.xml), return as-is with internal templates
|
|
56
|
-
if (content.trim().startsWith('<templates>')) {
|
|
57
|
-
return content.replace('</templates>', INTERNAL_TEMPLATES.join('') + '</templates>')
|
|
58
|
-
}
|
|
59
|
-
// Otherwise wrap it with internal templates
|
|
60
|
-
return '<templates>' + content + INTERNAL_TEMPLATES.join('') + '</templates>'
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.error(`[metaowl] Failed to load template: ${fileArray[0]}`, e)
|
|
63
|
-
return '<templates>' + INTERNAL_TEMPLATES.join('') + '</templates>'
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Multiple files: load each and wrap in <templates>
|
|
68
|
-
const results = await Promise.all(
|
|
69
|
-
fileArray.map(async (file) => {
|
|
70
|
-
try {
|
|
71
|
-
return await loadFile(file)
|
|
72
|
-
} catch (e) {
|
|
73
|
-
console.error(`[metaowl] Failed to load template: ${file}`, e)
|
|
74
|
-
return ''
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
)
|
|
78
|
-
return '<templates>' + results.join('') + INTERNAL_TEMPLATES.join('') + '</templates>'
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Gibt die internen Templates zurück.
|
|
83
|
-
* Nützlich für Testing oder manuelle Template-Registrierung.
|
|
84
|
-
*
|
|
85
|
-
* @returns {string[]}
|
|
86
|
-
*/
|
|
87
|
-
export function getInternalTemplates() {
|
|
88
|
-
return [...INTERNAL_TEMPLATES]
|
|
89
|
-
}
|