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.
Files changed (79) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -15
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +141 -0
  10. package/build/runtime/modules/app-mounter.js +65 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/error-boundary.js +116 -0
  15. package/build/runtime/modules/fetch.js +31 -0
  16. package/build/runtime/modules/file-router.js +205 -0
  17. package/build/runtime/modules/forms.js +193 -0
  18. package/build/runtime/modules/i18n.js +167 -0
  19. package/build/runtime/modules/layouts.js +163 -0
  20. package/build/runtime/modules/link.js +141 -0
  21. package/build/runtime/modules/meta.js +117 -0
  22. package/build/runtime/modules/odoo-rpc.js +264 -0
  23. package/build/runtime/modules/pwa.js +262 -0
  24. package/build/runtime/modules/router.js +389 -0
  25. package/build/runtime/modules/seo.js +186 -0
  26. package/build/runtime/modules/store.js +196 -0
  27. package/build/runtime/modules/templates-manager.js +52 -0
  28. package/build/runtime/modules/test-utils.js +238 -0
  29. package/build/runtime/vite/plugin.js +183 -0
  30. package/eslint.js +29 -0
  31. package/package.json +29 -11
  32. package/CONTRIBUTING.md +0 -49
  33. package/bin/metaowl-build.js +0 -12
  34. package/bin/metaowl-dev.js +0 -12
  35. package/bin/metaowl-generate.js +0 -339
  36. package/bin/metaowl-lint.js +0 -71
  37. package/bin/utils.js +0 -82
  38. package/index.js +0 -328
  39. package/modules/app-mounter.js +0 -104
  40. package/modules/auto-import.js +0 -225
  41. package/modules/cache.js +0 -59
  42. package/modules/composables.js +0 -600
  43. package/modules/error-boundary.js +0 -228
  44. package/modules/fetch.js +0 -51
  45. package/modules/file-router.js +0 -478
  46. package/modules/forms.js +0 -353
  47. package/modules/i18n.js +0 -333
  48. package/modules/layouts.js +0 -431
  49. package/modules/link.js +0 -255
  50. package/modules/meta.js +0 -119
  51. package/modules/odoo-rpc.js +0 -511
  52. package/modules/pwa.js +0 -515
  53. package/modules/router.js +0 -769
  54. package/modules/seo.js +0 -501
  55. package/modules/store.js +0 -409
  56. package/modules/templates-manager.js +0 -89
  57. package/modules/test-utils.js +0 -532
  58. package/test/auto-import.test.js +0 -110
  59. package/test/cache.test.js +0 -55
  60. package/test/composables.test.js +0 -103
  61. package/test/dynamic-routes.test.js +0 -469
  62. package/test/error-boundary.test.js +0 -126
  63. package/test/fetch.test.js +0 -100
  64. package/test/file-router.test.js +0 -55
  65. package/test/forms.test.js +0 -203
  66. package/test/i18n.test.js +0 -188
  67. package/test/layouts.test.js +0 -395
  68. package/test/link.test.js +0 -189
  69. package/test/meta.test.js +0 -146
  70. package/test/odoo-rpc.test.js +0 -547
  71. package/test/pwa.test.js +0 -154
  72. package/test/router-guards.test.js +0 -229
  73. package/test/router.test.js +0 -77
  74. package/test/seo.test.js +0 -353
  75. package/test/store.test.js +0 -476
  76. package/test/templates-manager.test.js +0 -83
  77. package/test/test-utils.test.js +0 -314
  78. package/vite/plugin.js +0 -277
  79. package/vitest.config.js +0 -8
package/modules/router.js DELETED
@@ -1,769 +0,0 @@
1
- /**
2
- * @module Router
3
- *
4
- * Enhanced router with navigation guards support.
5
- *
6
- * Navigation guards allow intercepting navigation and controlling access
7
- * to routes based on conditions like authentication, permissions, etc.
8
- *
9
- * Features:
10
- * - Global beforeEach guards
11
- * - Global afterEach hooks
12
- * - Per-route beforeEnter guards
13
- * - Route metadata support
14
- * - Navigation cancellation and redirection
15
- *
16
- * @example
17
- * import { router } from 'metaowl'
18
- *
19
- * // Global guard - runs before every navigation
20
- * router.beforeEach((to, from, next) => {
21
- * const auth = useAuthStore()
22
- * if (to.meta.requiresAuth && !auth.isLoggedIn) {
23
- * next('/login')
24
- * } else {
25
- * next()
26
- * }
27
- * })
28
- *
29
- * // Global after hook - runs after navigation
30
- * router.afterEach((to, from) => {
31
- * console.log(`Navigated from ${from.path} to ${to.path}`)
32
- * })
33
- *
34
- * // Per-route guard
35
- * export class AdminPage extends Component {
36
- * static route = {
37
- * path: '/admin',
38
- * meta: { requiresAuth: true, role: 'admin' },
39
- * beforeEnter: (to, from, next) => { ... }
40
- * }
41
- * }
42
- */
43
-
44
- import { Component } from '@odoo/owl'
45
-
46
- /**
47
- * Navigation guard function signature:
48
- * @typedef {Function} NavigationGuard
49
- * @param {Route} to - Target route
50
- * @param {Route} from - Current route (or null on initial)
51
- * @param {Function} next - Callback to resolve navigation
52
- * - next() - proceed to next guard
53
- * - next(false) - abort navigation
54
- * - next('/path') - redirect to path
55
- * - next({ path: '/path', replace: true }) - redirect with options
56
- * - next(error) - abort with error
57
- */
58
-
59
- /**
60
- * Current route object.
61
- * @type {Route|null}
62
- */
63
- let _currentRoute = null
64
-
65
- /**
66
- * Previous route object.
67
- * @type {Route|null}
68
- */
69
- let _previousRoute = null
70
-
71
- /**
72
- * Global beforeEach guards.
73
- * @type {NavigationGuard[]}
74
- */
75
- const _beforeEachGuards = []
76
-
77
- /**
78
- * Global afterEach hooks.
79
- * @type {Function[]}
80
- */
81
- const _afterEachHooks = []
82
-
83
- /**
84
- * Navigation in progress flag.
85
- * @type {boolean}
86
- */
87
- let _isNavigating = false
88
-
89
- /**
90
- * Navigation cancellation token.
91
- * @type {Function|null}
92
- */
93
- let _cancelNavigation = null
94
-
95
- /**
96
- * Router instance with guard support.
97
- */
98
- class Router {
99
- constructor(routes) {
100
- this.routes = routes
101
- this.routeMap = new Map()
102
-
103
- // Build route map for quick lookup
104
- for (const route of routes) {
105
- for (const path of route.path) {
106
- this.routeMap.set(path, route)
107
- }
108
- }
109
- }
110
-
111
- /**
112
- * Resolve current URL against route table.
113
- *
114
- * @param {string} [path] - Optional path to resolve (for testing)
115
- * @returns {Route|null}
116
- */
117
- resolve(path) {
118
- const currentPath = path || document.location.pathname
119
-
120
- // Try exact match first
121
- if (this.routeMap.has(currentPath)) {
122
- return this.routeMap.get(currentPath)
123
- }
124
-
125
- // Try matching dynamic routes
126
- for (const route of this.routes) {
127
- if (this.matchRoute(route, currentPath)) {
128
- return route
129
- }
130
- }
131
-
132
- return null
133
- }
134
-
135
- /**
136
- * Match a route against a path.
137
- *
138
- * @param {object} route - Route definition
139
- * @param {string} path - Current path
140
- * @returns {boolean}
141
- */
142
- matchRoute(route, path) {
143
- for (const routePath of route.path) {
144
- if (this.pathMatches(routePath, path)) {
145
- return true
146
- }
147
- }
148
- return false
149
- }
150
-
151
- /**
152
- * Check if a route path matches the current path.
153
- *
154
- * Supports:
155
- * - Exact matches: /about
156
- * - Dynamic segments: /user/:id
157
- * - Optional segments: /user/:id?
158
- * - Wildcards: /user/*
159
- *
160
- * @param {string} routePath - Route path pattern
161
- * @param {string} currentPath - Current URL path
162
- * @returns {boolean}
163
- */
164
- pathMatches(routePath, currentPath) {
165
- // Simple exact match for static routes
166
- if (!routePath.includes(':') && !routePath.includes('*')) {
167
- const normalizedRoute = routePath.replace(/\/$/, '') || '/'
168
- const normalizedCurrent = currentPath.replace(/\/$/, '') || '/'
169
- return normalizedRoute === normalizedCurrent
170
- }
171
-
172
- // Build regex without pre-escaping the whole string (which would break : and * handling)
173
- let pattern = routePath
174
- // Escape forward slashes
175
- .replace(/\//g, '\\/')
176
- // Replace catch-all :name(.*) params - must come before required-param replacement
177
- .replace(/:([^/(]+)\(\.\*\)/g, '(.*)')
178
- // Replace optional params /:name?
179
- .replace(/\/:([^/(]+)\?/g, '(?:/([^/]+))?')
180
- // Replace required params :name
181
- .replace(/:([^/(?\s]+)/g, '([^/]+)')
182
- // Replace bare wildcards *
183
- .replace(/\*/g, '(.*)')
184
-
185
- pattern = '^' + pattern + '$'
186
- const regex = new RegExp(pattern)
187
-
188
- return regex.test(currentPath)
189
- }
190
-
191
- /**
192
- * Extract parameters from a matched route.
193
- *
194
- * @param {object} route - Route definition
195
- * @param {string} path - Current path
196
- * @returns {object} Parameter key-value pairs
197
- */
198
- extractParams(route, path) {
199
- const params = {}
200
-
201
- for (const routePath of route.path) {
202
- const match = this.matchAndExtract(routePath, path)
203
- if (match) {
204
- Object.assign(params, match)
205
- }
206
- }
207
-
208
- return params
209
- }
210
-
211
- /**
212
- * Match a path and extract parameters.
213
- *
214
- * @param {string} routePath - Route pattern
215
- * @param {string} currentPath - Current URL
216
- * @returns {object|null}
217
- */
218
- matchAndExtract(routePath, currentPath) {
219
- if (!routePath.includes(':')) {
220
- return null
221
- }
222
-
223
- // Extract parameter names in the correct order
224
- const paramNames = []
225
-
226
- // Build pattern: handle catch-all :name(.*) first, then optional, then required
227
- let pattern = routePath
228
- .replace(/:([^/(]+)\(\.\*\)/g, (match, name) => {
229
- paramNames.push(name)
230
- return '(.*)'
231
- })
232
- .replace(/\/:([^/(]+)\?/g, (match, name) => {
233
- paramNames.push(name)
234
- return '(?:/([^/]+))?'
235
- })
236
- .replace(/:([^/(?\s]+)/g, (match, name) => {
237
- paramNames.push(name)
238
- return '([^/]+)'
239
- })
240
-
241
- const regex = new RegExp('^' + pattern + '$')
242
- const matches = currentPath.match(regex)
243
-
244
- if (!matches) return null
245
-
246
- const params = {}
247
- for (let i = 0; i < paramNames.length; i++) {
248
- if (matches[i + 1] !== undefined) {
249
- params[paramNames[i]] = matches[i + 1]
250
- }
251
- }
252
-
253
- return params
254
- }
255
- }
256
-
257
- /**
258
- * Route object passed to guards.
259
- * @typedef {object} Route
260
- * @property {string} name - Route name
261
- * @property {string[]} path - URL paths
262
- * @property {Function} component - Component class
263
- * @property {object} [meta] - Route metadata
264
- * @property {NavigationGuard} [beforeEnter] - Per-route guard
265
- * @property {object} [params] - Dynamic parameters
266
- */
267
-
268
- /**
269
- * Process routes with guards.
270
- *
271
- * @param {object[]} routes - Route table
272
- * @param {string} [customPath] - Optional custom path for testing
273
- * @returns {Promise<object[]>} Resolved route or throws error
274
- * @throws {NavigationError} If navigation is aborted
275
- */
276
- // Tracks which routes arrays have already been augmented with SSG path variants.
277
- // Using a WeakSet means each distinct array is injected exactly once, even
278
- // across multiple processRoutes calls with the same array.
279
- const _injectedRouteSets = new WeakSet()
280
-
281
- export async function processRoutes(routes, customPath) {
282
- // Use custom path for testing if provided
283
- const targetPath = customPath || document.location.pathname
284
-
285
- // Inject SSG-compatible path variants ONCE per routes array.
286
- // injectSystemRoutes mutates route.path in-place. Calling it on every
287
- // navigation causes the arrays to grow on every call (each injected path
288
- // becomes a base for further injections), making route matching
289
- // exponentially slower with every navigation.
290
- if (!_injectedRouteSets.has(routes)) {
291
- _injectedRouteSets.add(routes)
292
- for (const route of routes) {
293
- const originalPaths = [...route.path]
294
- for (const path of originalPaths) {
295
- if (typeof path === 'string') {
296
- injectSystemRoutes(route, path)
297
- }
298
- }
299
- }
300
- }
301
-
302
- const router = new Router(routes)
303
- const toRoute = router.resolve(targetPath)
304
-
305
- if (!toRoute) {
306
- throw new Error(`No route found for "${targetPath}".`)
307
- }
308
-
309
- // Build route object
310
- const to = buildRouteObject(toRoute, router)
311
- const from = _currentRoute
312
-
313
- // Run navigation guards
314
- try {
315
- await runGuards(to, from, router)
316
-
317
- // Update current route
318
- _previousRoute = _currentRoute
319
- _currentRoute = to
320
-
321
- // Run afterEach hooks
322
- for (const hook of _afterEachHooks) {
323
- hook(to, from)
324
- }
325
-
326
- return [toRoute]
327
- } catch (error) {
328
- if (error.name === 'NavigationRedirect') {
329
- // Redirect to new location
330
- if (error.path) {
331
- window.location.href = error.path
332
- return null
333
- }
334
- }
335
- throw error
336
- }
337
- }
338
-
339
- /**
340
- * Build a route object for guards.
341
- *
342
- * @param {object} routeDef - Raw route definition
343
- * @param {Router} router - Router instance
344
- * @returns {Route}
345
- */
346
- function buildRouteObject(routeDef, router) {
347
- const currentPath = document.location.pathname
348
- const params = router.extractParams(routeDef, currentPath)
349
-
350
- return {
351
- name: routeDef.name,
352
- path: routeDef.path,
353
- fullPath: currentPath,
354
- component: routeDef.component,
355
- meta: routeDef.meta || {},
356
- beforeEnter: routeDef.beforeEnter,
357
- params,
358
- query: parseQuery(document.location.search)
359
- }
360
- }
361
-
362
- /**
363
- * Parse query string into object.
364
- *
365
- * @param {string} search - Query string (e.g., '?foo=bar&baz=qux')
366
- * @returns {object}
367
- */
368
- function parseQuery(search) {
369
- const query = {}
370
- if (!search || search === '?') return query
371
-
372
- const params = new URLSearchParams(search.substring(1))
373
- for (const [key, value] of params) {
374
- if (query[key]) {
375
- if (Array.isArray(query[key])) {
376
- query[key].push(value)
377
- } else {
378
- query[key] = [query[key], value]
379
- }
380
- } else {
381
- query[key] = value
382
- }
383
- }
384
-
385
- return query
386
- }
387
-
388
- /**
389
- * Run all navigation guards.
390
- *
391
- * @param {Route} to - Target route
392
- * @param {Route} from - Current route
393
- * @param {Router} router - Router instance
394
- */
395
- async function runGuards(to, from, router) {
396
- _isNavigating = true
397
-
398
- // Create navigation controller
399
- let cancelled = false
400
- _cancelNavigation = () => { cancelled = true }
401
-
402
- try {
403
- // Run global beforeEach guards
404
- for (const guard of _beforeEachGuards) {
405
- if (cancelled) break
406
-
407
- const result = await runGuard(guard, to, from)
408
-
409
- if (result === false) {
410
- throw new NavigationCancelled()
411
- }
412
-
413
- if (typeof result === 'string') {
414
- throw new NavigationRedirect(result)
415
- }
416
-
417
- if (result && typeof result === 'object' && result.path) {
418
- throw new NavigationRedirect(result.path)
419
- }
420
- }
421
-
422
- // Run per-route beforeEnter
423
- if (to.beforeEnter && !cancelled) {
424
- const result = await runGuard(to.beforeEnter, to, from)
425
-
426
- if (result === false) {
427
- throw new NavigationCancelled()
428
- }
429
-
430
- if (typeof result === 'string') {
431
- throw new NavigationRedirect(result)
432
- }
433
- }
434
- } finally {
435
- _isNavigating = false
436
- _cancelNavigation = null
437
- }
438
- }
439
-
440
- /**
441
- * Run a single guard with next callback support.
442
- *
443
- * @param {Function} guard - Guard function
444
- * @param {Route} to - Target route
445
- * @param {Route} from - Current route
446
- * @returns {Promise<*>}
447
- */
448
- async function runGuard(guard, to, from) {
449
- return new Promise((resolve, reject) => {
450
- const next = (result) => {
451
- if (result instanceof Error) {
452
- reject(result)
453
- } else {
454
- resolve(result)
455
- }
456
- }
457
-
458
- try {
459
- const guardResult = guard(to, from, next)
460
-
461
- // Handle both sync and async guards
462
- if (guardResult && typeof guardResult.then === 'function') {
463
- guardResult.then(resolve).catch(reject)
464
- } else if (guardResult !== undefined) {
465
- // Sync guard returned a value
466
- resolve(guardResult)
467
- }
468
- // If undefined, guard called next() callback
469
- } catch (error) {
470
- reject(error)
471
- }
472
- })
473
- }
474
-
475
- /**
476
- * Reset router state (for testing purposes).
477
- */
478
- export function resetRouter() {
479
- _beforeEachGuards.length = 0
480
- _afterEachHooks.length = 0
481
- _isNavigating = false
482
- _cancelNavigation = null
483
- _currentRoute = null
484
- _previousRoute = null
485
- }
486
-
487
- /**
488
- * Navigation cancelled error.
489
- */
490
- class NavigationCancelled extends Error {
491
- constructor() {
492
- super('Navigation cancelled')
493
- this.name = 'NavigationCancelled'
494
- }
495
- }
496
-
497
- /**
498
- * Navigation redirect error.
499
- */
500
- class NavigationRedirect extends Error {
501
- constructor(path) {
502
- super('Navigation redirect')
503
- this.name = 'NavigationRedirect'
504
- this.path = path
505
- }
506
- }
507
-
508
- // --- Public Router API ---
509
-
510
- /**
511
- * Register a global guard to be called before every navigation.
512
- *
513
- * @param {NavigationGuard} guard - Guard function
514
- * @returns {Function} Remove guard function
515
- */
516
- export function beforeEach(guard) {
517
- _beforeEachGuards.push(guard)
518
- return () => {
519
- const index = _beforeEachGuards.indexOf(guard)
520
- if (index > -1) _beforeEachGuards.splice(index, 1)
521
- }
522
- }
523
-
524
- /**
525
- * Register a hook to be called after every navigation.
526
- *
527
- * @param {Function} hook - Hook function (to, from) => void
528
- * @returns {Function} Remove hook function
529
- */
530
- export function afterEach(hook) {
531
- _afterEachHooks.push(hook)
532
- return () => {
533
- const index = _afterEachHooks.indexOf(hook)
534
- if (index > -1) _afterEachHooks.splice(index, 1)
535
- }
536
- }
537
-
538
- /**
539
- * Get the current route.
540
- *
541
- * @returns {Route|null}
542
- */
543
- export function getCurrentRoute() {
544
- return _currentRoute
545
- }
546
-
547
- /**
548
- * Get the previous route.
549
- *
550
- * @returns {Route|null}
551
- */
552
- export function getPreviousRoute() {
553
- return _previousRoute
554
- }
555
-
556
- /**
557
- * Check if navigation is in progress.
558
- *
559
- * @returns {boolean}
560
- */
561
- export function isNavigating() {
562
- return _isNavigating
563
- }
564
-
565
- /**
566
- * Cancel current navigation (if in progress).
567
- */
568
- export function cancelNavigation() {
569
- if (_cancelNavigation) {
570
- _cancelNavigation()
571
- }
572
- }
573
-
574
- /**
575
- * Callback for SPA navigation.
576
- * Set when the app is initialized.
577
- * @type {Function|null}
578
- */
579
- let _spaNavigationCallback = null
580
-
581
- /**
582
- * Sets the SPA navigation callback.
583
- * Called internally by boot().
584
- *
585
- * @param {Function} callback - Function called during SPA navigation
586
- * @internal
587
- */
588
- export function _setSpaNavigationCallback(callback) {
589
- _spaNavigationCallback = callback
590
- }
591
-
592
- /**
593
- * Flag indicating if SPA navigation is enabled.
594
- * @type {boolean}
595
- */
596
- let _spaEnabled = false
597
-
598
- /**
599
- * Enables or disables SPA navigation.
600
- *
601
- * @param {boolean} enabled - True to enable SPA navigation
602
- */
603
- export function setSpaMode(enabled) {
604
- _spaEnabled = enabled
605
- }
606
-
607
- /**
608
- * Checks if SPA navigation is enabled.
609
- *
610
- * @returns {boolean}
611
- */
612
- export function isSpaMode() {
613
- return _spaEnabled
614
- }
615
-
616
- /**
617
- * Navigate to a path with SPA navigation (no page reload).
618
- * Updates URL via history.pushState and renders the new route.
619
- *
620
- * @param {string} path - Target path (e.g., "/about")
621
- * @param {object} [options] - Navigation options
622
- * @param {boolean} [options.replace=false] - Replace current history entry instead of creating new one
623
- * @returns {Promise<boolean>} True if navigation successful
624
- *
625
- * @example
626
- * // Normal navigation
627
- * await navigateTo('/about')
628
- *
629
- * // Replace current entry (no back possible)
630
- * await navigateTo('/login', { replace: true })
631
- */
632
- export async function navigateTo(path, options = {}) {
633
- const { replace = false } = options
634
-
635
- if (!_spaEnabled || !_spaNavigationCallback) {
636
- // Fallback: Normal browser navigation
637
- if (replace) {
638
- window.location.replace(path)
639
- } else {
640
- window.location.href = path
641
- }
642
- return false
643
- }
644
-
645
- try {
646
- // Update URL without page reload
647
- if (replace) {
648
- window.history.replaceState({ path }, '', path)
649
- } else {
650
- window.history.pushState({ path }, '', path)
651
- }
652
-
653
- // Perform SPA navigation
654
- await _spaNavigationCallback(path)
655
- return true
656
- } catch (error) {
657
- console.error('[metaowl] SPA navigation failed:', error)
658
- // Fallback to normal navigation on error
659
- window.location.href = path
660
- return false
661
- }
662
- }
663
-
664
- /**
665
- * Programmatically navigate to a path.
666
- *
667
- * @param {string} path - Target path
668
- * @param {object} [options] - Navigation options
669
- * @param {boolean} [options.replace=false] - Replace current history entry
670
- * @param {boolean} [options.reload=true] - Reload the page
671
- * @deprecated Use navigateTo() for SPA navigation
672
- */
673
- export function navigate(path, options = {}) {
674
- const { replace = false, reload = true } = options
675
-
676
- if (reload || !_spaEnabled) {
677
- // Traditional navigation with page reload
678
- if (replace) {
679
- window.location.replace(path)
680
- } else {
681
- window.location.href = path
682
- }
683
- } else {
684
- // SPA navigation
685
- navigateTo(path, { replace })
686
- }
687
- }
688
-
689
- /**
690
- * Push a new history entry.
691
- *
692
- * @param {string} path - Target path
693
- */
694
- export function push(path) {
695
- navigateTo(path, { replace: false })
696
- }
697
-
698
- /**
699
- * Replace current history entry.
700
- *
701
- * @param {string} path - Target path
702
- */
703
- export function replace(path) {
704
- navigateTo(path, { replace: true })
705
- }
706
-
707
- /**
708
- * Go back in history.
709
- */
710
- export function back() {
711
- window.history.back()
712
- }
713
-
714
- /**
715
- * Go forward in history.
716
- */
717
- export function forward() {
718
- window.history.forward()
719
- }
720
-
721
- /**
722
- * Go n steps in history.
723
- *
724
- * @param {number} n - Steps to go (negative for back)
725
- */
726
- export function go(n) {
727
- window.history.go(n)
728
- }
729
-
730
- /**
731
- * Router singleton with guard methods.
732
- */
733
- export const router = {
734
- beforeEach,
735
- afterEach,
736
- get currentRoute() { return getCurrentRoute() },
737
- get previousRoute() { return getPreviousRoute() },
738
- get isNavigating() { return isNavigating() },
739
- cancel: cancelNavigation,
740
- push,
741
- replace,
742
- back,
743
- forward,
744
- go,
745
- navigate,
746
- navigateTo,
747
- setSpaMode,
748
- isSpaMode
749
- }
750
-
751
- /**
752
- * Injects SSG-compatible path variants for a route:
753
- * trailing slash, .html suffix, /index.html suffix.
754
- *
755
- * @param {object} route
756
- * @param {string} path
757
- * @returns {object}
758
- */
759
- function injectSystemRoutes(route, path) {
760
- if (path === '/') {
761
- if (!route.path.includes('/index.html')) route.path.push('/index.html')
762
- } else {
763
- if (!route.path.includes(`${path}.html`)) route.path.push(`${path}.html`)
764
- if (!route.path.includes(`${path}/`)) route.path.push(`${path}/`)
765
- if (!route.path.includes(`${path}/index.html`)) route.path.push(`${path}/index.html`)
766
- }
767
-
768
- return route
769
- }