metaowl 0.1.3 → 0.2.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/README.md +852 -3
- package/bin/metaowl-create.js +425 -0
- package/index.js +155 -1
- package/modules/app-mounter.js +7 -0
- package/modules/auto-import.js +225 -0
- package/modules/cache.js +2 -0
- package/modules/composables.js +600 -0
- package/modules/error-boundary.js +228 -0
- package/modules/fetch.js +7 -0
- package/modules/file-router.js +425 -19
- package/modules/forms.js +353 -0
- package/modules/i18n.js +333 -0
- package/modules/layouts.js +433 -0
- package/modules/odoo-rpc.js +511 -0
- package/modules/pwa.js +515 -0
- package/modules/router.js +593 -29
- package/modules/seo.js +501 -0
- package/modules/store.js +409 -0
- package/modules/templates-manager.js +5 -0
- package/modules/test-utils.js +532 -0
- package/package.json +1 -1
- package/test/auto-import.test.js +110 -0
- package/test/composables.test.js +103 -0
- package/test/dynamic-routes.test.js +520 -0
- package/test/error-boundary.test.js +126 -0
- package/test/forms.test.js +203 -0
- package/test/i18n.test.js +188 -0
- package/test/layouts.test.js +395 -0
- package/test/odoo-rpc.test.js +547 -0
- package/test/pwa.test.js +154 -0
- package/test/router-guards.test.js +617 -0
- package/test/seo.test.js +353 -0
- package/test/store.test.js +476 -0
- package/test/test-utils.test.js +314 -0
- package/vite/plugin.js +43 -5
package/modules/router.js
CHANGED
|
@@ -1,26 +1,610 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.
|
|
4
97
|
*/
|
|
5
98
|
class Router {
|
|
6
99
|
constructor(routes) {
|
|
7
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
|
+
}
|
|
8
109
|
}
|
|
9
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Resolve current URL against route table.
|
|
113
|
+
*
|
|
114
|
+
* @returns {Route|null}
|
|
115
|
+
*/
|
|
10
116
|
resolve() {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
117
|
+
const currentPath = document.location.pathname
|
|
118
|
+
|
|
119
|
+
// Try exact match first
|
|
120
|
+
if (this.routeMap.has(currentPath)) {
|
|
121
|
+
return this.routeMap.get(currentPath)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try matching dynamic routes
|
|
125
|
+
for (const route of this.routes) {
|
|
126
|
+
if (this.matchRoute(route, currentPath)) {
|
|
127
|
+
return route
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Match a route against a path.
|
|
136
|
+
*
|
|
137
|
+
* @param {object} route - Route definition
|
|
138
|
+
* @param {string} path - Current path
|
|
139
|
+
* @returns {boolean}
|
|
140
|
+
*/
|
|
141
|
+
matchRoute(route, path) {
|
|
142
|
+
for (const routePath of route.path) {
|
|
143
|
+
if (this.pathMatches(routePath, path)) {
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
15
149
|
|
|
16
|
-
|
|
17
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Check if a route path matches the current path.
|
|
152
|
+
*
|
|
153
|
+
* Supports:
|
|
154
|
+
* - Exact matches: /about
|
|
155
|
+
* - Dynamic segments: /user/:id
|
|
156
|
+
* - Optional segments: /user/:id?
|
|
157
|
+
* - Wildcards: /user/*
|
|
158
|
+
*
|
|
159
|
+
* @param {string} routePath - Route path pattern
|
|
160
|
+
* @param {string} currentPath - Current URL path
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
pathMatches(routePath, currentPath) {
|
|
164
|
+
// Convert route pattern to regex
|
|
165
|
+
if (!routePath.includes(':') && !routePath.includes('*')) {
|
|
166
|
+
// Simple exact match
|
|
167
|
+
const normalizedRoute = routePath.replace(/\/$/, '') || '/'
|
|
168
|
+
const normalizedCurrent = currentPath.replace(/\/$/, '') || '/'
|
|
169
|
+
return normalizedRoute === normalizedCurrent
|
|
18
170
|
}
|
|
19
171
|
|
|
20
|
-
|
|
172
|
+
// Dynamic route matching
|
|
173
|
+
let pattern = routePath
|
|
174
|
+
// Escape special regex characters except pattern markers
|
|
175
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
176
|
+
// Replace optional params :param?
|
|
177
|
+
.replace(/\\:([^/]+)\\?/g, '(?:\\/([^/]+))?')
|
|
178
|
+
// Replace required params :param
|
|
179
|
+
.replace(/\\:([^/]+)/g, '([^/]+)')
|
|
180
|
+
// Replace wildcards
|
|
181
|
+
.replace(/\\\*/g, '(.*)')
|
|
182
|
+
|
|
183
|
+
pattern = '^' + pattern + '$'
|
|
184
|
+
const regex = new RegExp(pattern)
|
|
185
|
+
|
|
186
|
+
return regex.test(currentPath)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extract parameters from a matched route.
|
|
191
|
+
*
|
|
192
|
+
* @param {object} route - Route definition
|
|
193
|
+
* @param {string} path - Current path
|
|
194
|
+
* @returns {object} Parameter key-value pairs
|
|
195
|
+
*/
|
|
196
|
+
extractParams(route, path) {
|
|
197
|
+
const params = {}
|
|
198
|
+
|
|
199
|
+
for (const routePath of route.path) {
|
|
200
|
+
const match = this.matchAndExtract(routePath, path)
|
|
201
|
+
if (match) {
|
|
202
|
+
Object.assign(params, match)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return params
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Match a path and extract parameters.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} routePath - Route pattern
|
|
213
|
+
* @param {string} currentPath - Current URL
|
|
214
|
+
* @returns {object|null}
|
|
215
|
+
*/
|
|
216
|
+
matchAndExtract(routePath, currentPath) {
|
|
217
|
+
if (!routePath.includes(':')) {
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Extract parameter names
|
|
222
|
+
const paramNames = []
|
|
223
|
+
const pattern = routePath.replace(/:([^/?]+)\??/g, (match, name) => {
|
|
224
|
+
paramNames.push(name)
|
|
225
|
+
return '([^/]+)'
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const regex = new RegExp('^' + pattern + '$')
|
|
229
|
+
const matches = currentPath.match(regex)
|
|
230
|
+
|
|
231
|
+
if (!matches) return null
|
|
232
|
+
|
|
233
|
+
const params = {}
|
|
234
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
235
|
+
params[paramNames[i]] = matches[i + 1]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return params
|
|
21
239
|
}
|
|
22
240
|
}
|
|
23
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Route object passed to guards.
|
|
244
|
+
* @typedef {object} Route
|
|
245
|
+
* @property {string} name - Route name
|
|
246
|
+
* @property {string[]} path - URL paths
|
|
247
|
+
* @property {Function} component - Component class
|
|
248
|
+
* @property {object} [meta] - Route metadata
|
|
249
|
+
* @property {NavigationGuard} [beforeEnter] - Per-route guard
|
|
250
|
+
* @property {object} [params] - Dynamic parameters
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Process routes with guards.
|
|
255
|
+
*
|
|
256
|
+
* @param {object[]} routes - Route table
|
|
257
|
+
* @returns {Promise<object[]>} Resolved route or throws error
|
|
258
|
+
* @throws {NavigationError} If navigation is aborted
|
|
259
|
+
*/
|
|
260
|
+
export async function processRoutes(routes) {
|
|
261
|
+
// Inject SSG-compatible path variants
|
|
262
|
+
for (const route of routes) {
|
|
263
|
+
const originalPaths = [...route.path]
|
|
264
|
+
for (const path of originalPaths) {
|
|
265
|
+
if (typeof path === 'string') {
|
|
266
|
+
injectSystemRoutes(route, path)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const router = new Router(routes)
|
|
272
|
+
const toRoute = router.resolve()
|
|
273
|
+
|
|
274
|
+
if (!toRoute) {
|
|
275
|
+
throw new Error(`No route found for "${document.location.pathname}".`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Build route object
|
|
279
|
+
const to = buildRouteObject(toRoute, router)
|
|
280
|
+
const from = _currentRoute
|
|
281
|
+
|
|
282
|
+
// Run navigation guards
|
|
283
|
+
try {
|
|
284
|
+
await runGuards(to, from, router)
|
|
285
|
+
|
|
286
|
+
// Update current route
|
|
287
|
+
_previousRoute = _currentRoute
|
|
288
|
+
_currentRoute = to
|
|
289
|
+
|
|
290
|
+
// Run afterEach hooks
|
|
291
|
+
for (const hook of _afterEachHooks) {
|
|
292
|
+
hook(to, from)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return [toRoute]
|
|
296
|
+
} catch (error) {
|
|
297
|
+
if (error.name === 'NavigationRedirect') {
|
|
298
|
+
// Redirect to new location
|
|
299
|
+
if (error.path) {
|
|
300
|
+
window.location.href = error.path
|
|
301
|
+
return null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw error
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build a route object for guards.
|
|
310
|
+
*
|
|
311
|
+
* @param {object} routeDef - Raw route definition
|
|
312
|
+
* @param {Router} router - Router instance
|
|
313
|
+
* @returns {Route}
|
|
314
|
+
*/
|
|
315
|
+
function buildRouteObject(routeDef, router) {
|
|
316
|
+
const currentPath = document.location.pathname
|
|
317
|
+
const params = router.extractParams(routeDef, currentPath)
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
name: routeDef.name,
|
|
321
|
+
path: routeDef.path,
|
|
322
|
+
fullPath: currentPath,
|
|
323
|
+
component: routeDef.component,
|
|
324
|
+
meta: routeDef.meta || {},
|
|
325
|
+
beforeEnter: routeDef.beforeEnter,
|
|
326
|
+
params,
|
|
327
|
+
query: parseQuery(document.location.search)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Parse query string into object.
|
|
333
|
+
*
|
|
334
|
+
* @param {string} search - Query string (e.g., '?foo=bar&baz=qux')
|
|
335
|
+
* @returns {object}
|
|
336
|
+
*/
|
|
337
|
+
function parseQuery(search) {
|
|
338
|
+
const query = {}
|
|
339
|
+
if (!search || search === '?') return query
|
|
340
|
+
|
|
341
|
+
const params = new URLSearchParams(search.substring(1))
|
|
342
|
+
for (const [key, value] of params) {
|
|
343
|
+
if (query[key]) {
|
|
344
|
+
if (Array.isArray(query[key])) {
|
|
345
|
+
query[key].push(value)
|
|
346
|
+
} else {
|
|
347
|
+
query[key] = [query[key], value]
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
query[key] = value
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return query
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Run all navigation guards.
|
|
359
|
+
*
|
|
360
|
+
* @param {Route} to - Target route
|
|
361
|
+
* @param {Route} from - Current route
|
|
362
|
+
* @param {Router} router - Router instance
|
|
363
|
+
*/
|
|
364
|
+
async function runGuards(to, from, router) {
|
|
365
|
+
_isNavigating = true
|
|
366
|
+
|
|
367
|
+
// Create navigation controller
|
|
368
|
+
let cancelled = false
|
|
369
|
+
_cancelNavigation = () => { cancelled = true }
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Run global beforeEach guards
|
|
373
|
+
for (const guard of _beforeEachGuards) {
|
|
374
|
+
if (cancelled) break
|
|
375
|
+
|
|
376
|
+
const result = await runGuard(guard, to, from)
|
|
377
|
+
|
|
378
|
+
if (result === false) {
|
|
379
|
+
throw new NavigationCancelled()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (typeof result === 'string') {
|
|
383
|
+
throw new NavigationRedirect(result)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (result && typeof result === 'object' && result.path) {
|
|
387
|
+
throw new NavigationRedirect(result.path)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Run per-route beforeEnter
|
|
392
|
+
if (to.beforeEnter && !cancelled) {
|
|
393
|
+
const result = await runGuard(to.beforeEnter, to, from)
|
|
394
|
+
|
|
395
|
+
if (result === false) {
|
|
396
|
+
throw new NavigationCancelled()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (typeof result === 'string') {
|
|
400
|
+
throw new NavigationRedirect(result)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} finally {
|
|
404
|
+
_isNavigating = false
|
|
405
|
+
_cancelNavigation = null
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Run a single guard with next callback support.
|
|
411
|
+
*
|
|
412
|
+
* @param {Function} guard - Guard function
|
|
413
|
+
* @param {Route} to - Target route
|
|
414
|
+
* @param {Route} from - Current route
|
|
415
|
+
* @returns {Promise<*>}
|
|
416
|
+
*/
|
|
417
|
+
async function runGuard(guard, to, from) {
|
|
418
|
+
return new Promise((resolve, reject) => {
|
|
419
|
+
const next = (result) => {
|
|
420
|
+
if (result instanceof Error) {
|
|
421
|
+
reject(result)
|
|
422
|
+
} else {
|
|
423
|
+
resolve(result)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const guardResult = guard(to, from, next)
|
|
429
|
+
|
|
430
|
+
// Handle both sync and async guards
|
|
431
|
+
if (guardResult && typeof guardResult.then === 'function') {
|
|
432
|
+
guardResult.then(resolve).catch(reject)
|
|
433
|
+
} else if (guardResult !== undefined) {
|
|
434
|
+
// Sync guard returned a value
|
|
435
|
+
resolve(guardResult)
|
|
436
|
+
}
|
|
437
|
+
// If undefined, guard called next() callback
|
|
438
|
+
} catch (error) {
|
|
439
|
+
reject(error)
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Navigation cancelled error.
|
|
446
|
+
*/
|
|
447
|
+
class NavigationCancelled extends Error {
|
|
448
|
+
constructor() {
|
|
449
|
+
super('Navigation cancelled')
|
|
450
|
+
this.name = 'NavigationCancelled'
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Navigation redirect error.
|
|
456
|
+
*/
|
|
457
|
+
class NavigationRedirect extends Error {
|
|
458
|
+
constructor(path) {
|
|
459
|
+
super('Navigation redirect')
|
|
460
|
+
this.name = 'NavigationRedirect'
|
|
461
|
+
this.path = path
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// --- Public Router API ---
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Register a global guard to be called before every navigation.
|
|
469
|
+
*
|
|
470
|
+
* @param {NavigationGuard} guard - Guard function
|
|
471
|
+
* @returns {Function} Remove guard function
|
|
472
|
+
*/
|
|
473
|
+
export function beforeEach(guard) {
|
|
474
|
+
_beforeEachGuards.push(guard)
|
|
475
|
+
return () => {
|
|
476
|
+
const index = _beforeEachGuards.indexOf(guard)
|
|
477
|
+
if (index > -1) _beforeEachGuards.splice(index, 1)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Register a hook to be called after every navigation.
|
|
483
|
+
*
|
|
484
|
+
* @param {Function} hook - Hook function (to, from) => void
|
|
485
|
+
* @returns {Function} Remove hook function
|
|
486
|
+
*/
|
|
487
|
+
export function afterEach(hook) {
|
|
488
|
+
_afterEachHooks.push(hook)
|
|
489
|
+
return () => {
|
|
490
|
+
const index = _afterEachHooks.indexOf(hook)
|
|
491
|
+
if (index > -1) _afterEachHooks.splice(index, 1)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get the current route.
|
|
497
|
+
*
|
|
498
|
+
* @returns {Route|null}
|
|
499
|
+
*/
|
|
500
|
+
export function getCurrentRoute() {
|
|
501
|
+
return _currentRoute
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get the previous route.
|
|
506
|
+
*
|
|
507
|
+
* @returns {Route|null}
|
|
508
|
+
*/
|
|
509
|
+
export function getPreviousRoute() {
|
|
510
|
+
return _previousRoute
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Check if navigation is in progress.
|
|
515
|
+
*
|
|
516
|
+
* @returns {boolean}
|
|
517
|
+
*/
|
|
518
|
+
export function isNavigating() {
|
|
519
|
+
return _isNavigating
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Cancel current navigation (if in progress).
|
|
524
|
+
*/
|
|
525
|
+
export function cancelNavigation() {
|
|
526
|
+
if (_cancelNavigation) {
|
|
527
|
+
_cancelNavigation()
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Programmatically navigate to a path.
|
|
533
|
+
*
|
|
534
|
+
* @param {string} path - Target path
|
|
535
|
+
* @param {object} [options] - Navigation options
|
|
536
|
+
* @param {boolean} [options.replace=false] - Replace current history entry
|
|
537
|
+
* @param {boolean} [options.reload=true] - Reload the page
|
|
538
|
+
*/
|
|
539
|
+
export function navigate(path, options = {}) {
|
|
540
|
+
const { replace = false, reload = true } = options
|
|
541
|
+
|
|
542
|
+
if (replace) {
|
|
543
|
+
window.location.replace(path)
|
|
544
|
+
} else {
|
|
545
|
+
window.location.href = path
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Push a new history entry.
|
|
551
|
+
*
|
|
552
|
+
* @param {string} path - Target path
|
|
553
|
+
*/
|
|
554
|
+
export function push(path) {
|
|
555
|
+
navigate(path, { replace: false })
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Replace current history entry.
|
|
560
|
+
*
|
|
561
|
+
* @param {string} path - Target path
|
|
562
|
+
*/
|
|
563
|
+
export function replace(path) {
|
|
564
|
+
navigate(path, { replace: true })
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Go back in history.
|
|
569
|
+
*/
|
|
570
|
+
export function back() {
|
|
571
|
+
window.history.back()
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Go forward in history.
|
|
576
|
+
*/
|
|
577
|
+
export function forward() {
|
|
578
|
+
window.history.forward()
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Go n steps in history.
|
|
583
|
+
*
|
|
584
|
+
* @param {number} n - Steps to go (negative for back)
|
|
585
|
+
*/
|
|
586
|
+
export function go(n) {
|
|
587
|
+
window.history.go(n)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Router singleton with guard methods.
|
|
592
|
+
*/
|
|
593
|
+
export const router = {
|
|
594
|
+
beforeEach,
|
|
595
|
+
afterEach,
|
|
596
|
+
get currentRoute() { return getCurrentRoute() },
|
|
597
|
+
get previousRoute() { return getPreviousRoute() },
|
|
598
|
+
get isNavigating() { return isNavigating() },
|
|
599
|
+
cancel: cancelNavigation,
|
|
600
|
+
push,
|
|
601
|
+
replace,
|
|
602
|
+
back,
|
|
603
|
+
forward,
|
|
604
|
+
go,
|
|
605
|
+
navigate
|
|
606
|
+
}
|
|
607
|
+
|
|
24
608
|
/**
|
|
25
609
|
* Injects SSG-compatible path variants for a route:
|
|
26
610
|
* trailing slash, .html suffix, /index.html suffix.
|
|
@@ -40,23 +624,3 @@ function injectSystemRoutes(route, path) {
|
|
|
40
624
|
|
|
41
625
|
return route
|
|
42
626
|
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Expands all routes with SSG path variants, then resolves the current URL.
|
|
46
|
-
*
|
|
47
|
-
* @param {object[]} routes
|
|
48
|
-
* @returns {Promise<object[]>}
|
|
49
|
-
*/
|
|
50
|
-
export async function processRoutes(routes) {
|
|
51
|
-
for (const route of routes) {
|
|
52
|
-
const originalPaths = [...route.path]
|
|
53
|
-
for (const path of originalPaths) {
|
|
54
|
-
if (typeof path === 'string') {
|
|
55
|
-
injectSystemRoutes(route, path)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return new Router(routes).resolve()
|
|
61
|
-
}
|
|
62
|
-
|