forked from daren.hsu/line_push
update
This commit is contained in:
+30
-3
@@ -11,6 +11,10 @@ const eventTypes: Array<Function> = [String, Array]
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
let warnedCustomSlot
|
||||
let warnedTagProp
|
||||
let warnedEventProp
|
||||
|
||||
export default {
|
||||
name: 'RouterLink',
|
||||
props: {
|
||||
@@ -22,7 +26,9 @@ export default {
|
||||
type: String,
|
||||
default: 'a'
|
||||
},
|
||||
custom: Boolean,
|
||||
exact: Boolean,
|
||||
exactPath: Boolean,
|
||||
append: Boolean,
|
||||
replace: Boolean,
|
||||
activeClass: String,
|
||||
@@ -66,8 +72,8 @@ export default {
|
||||
? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
|
||||
: route
|
||||
|
||||
classes[exactActiveClass] = isSameRoute(current, compareTarget)
|
||||
classes[activeClass] = this.exact
|
||||
classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath)
|
||||
classes[activeClass] = this.exact || this.exactPath
|
||||
? classes[exactActiveClass]
|
||||
: isIncludedRoute(current, compareTarget)
|
||||
|
||||
@@ -106,13 +112,17 @@ export default {
|
||||
})
|
||||
|
||||
if (scopedSlot) {
|
||||
if (process.env.NODE_ENV !== 'production' && !this.custom) {
|
||||
!warnedCustomSlot && warn(false, 'In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link v-slot="{ navigate, href }" custom></router-link>\n')
|
||||
warnedCustomSlot = true
|
||||
}
|
||||
if (scopedSlot.length === 1) {
|
||||
return scopedSlot[0]
|
||||
} else if (scopedSlot.length > 1 || !scopedSlot.length) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(
|
||||
false,
|
||||
`RouterLink with to="${
|
||||
`<router-link> with to="${
|
||||
this.to
|
||||
}" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
|
||||
)
|
||||
@@ -121,6 +131,23 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if ('tag' in this.$options.propsData && !warnedTagProp) {
|
||||
warn(
|
||||
false,
|
||||
`<router-link>'s tag prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
|
||||
)
|
||||
warnedTagProp = true
|
||||
}
|
||||
if ('event' in this.$options.propsData && !warnedEventProp) {
|
||||
warn(
|
||||
false,
|
||||
`<router-link>'s event prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
|
||||
)
|
||||
warnedEventProp = true
|
||||
}
|
||||
}
|
||||
|
||||
if (this.tag === 'a') {
|
||||
data.on = on
|
||||
data.attrs = { href, 'aria-current': ariaCurrentValue }
|
||||
|
||||
+7
-1
@@ -1,5 +1,6 @@
|
||||
import { warn } from '../util/warn'
|
||||
import { extend } from '../util/misc'
|
||||
import { handleRouteEntered } from '../util/route'
|
||||
|
||||
export default {
|
||||
name: 'RouterView',
|
||||
@@ -94,10 +95,15 @@ export default {
|
||||
) {
|
||||
matched.instances[name] = vnode.componentInstance
|
||||
}
|
||||
|
||||
// if the route transition has already been confirmed then we weren't
|
||||
// able to call the cbs during confirmation as the component was not
|
||||
// registered yet, so we call it here.
|
||||
handleRouteEntered(route)
|
||||
}
|
||||
|
||||
const configProps = matched.props && matched.props[name]
|
||||
// save route and configProps in cachce
|
||||
// save route and configProps in cache
|
||||
if (configProps) {
|
||||
extend(cache[name], {
|
||||
route,
|
||||
|
||||
+28
-2
@@ -7,10 +7,13 @@ import { createRoute } from './util/route'
|
||||
import { fillParams } from './util/params'
|
||||
import { createRouteMap } from './create-route-map'
|
||||
import { normalizeLocation } from './util/location'
|
||||
import { decode } from './util/query'
|
||||
|
||||
export type Matcher = {
|
||||
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
|
||||
addRoutes: (routes: Array<RouteConfig>) => void;
|
||||
addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
|
||||
getRoutes: () => Array<RouteRecord>;
|
||||
};
|
||||
|
||||
export function createMatcher (
|
||||
@@ -23,6 +26,28 @@ export function createMatcher (
|
||||
createRouteMap(routes, pathList, pathMap, nameMap)
|
||||
}
|
||||
|
||||
function addRoute (parentOrRoute, route) {
|
||||
const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
|
||||
// $flow-disable-line
|
||||
createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
|
||||
|
||||
// add aliases of parent
|
||||
if (parent && parent.alias.length) {
|
||||
createRouteMap(
|
||||
// $flow-disable-line route is defined if parent is
|
||||
parent.alias.map(alias => ({ path: alias, children: [route] })),
|
||||
pathList,
|
||||
pathMap,
|
||||
nameMap,
|
||||
parent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function getRoutes () {
|
||||
return pathList.map(path => pathMap[path])
|
||||
}
|
||||
|
||||
function match (
|
||||
raw: RawLocation,
|
||||
currentRoute?: Route,
|
||||
@@ -166,6 +191,8 @@ export function createMatcher (
|
||||
|
||||
return {
|
||||
match,
|
||||
addRoute,
|
||||
getRoutes,
|
||||
addRoutes
|
||||
}
|
||||
}
|
||||
@@ -185,10 +212,9 @@ function matchRoute (
|
||||
|
||||
for (let i = 1, len = m.length; i < len; ++i) {
|
||||
const key = regex.keys[i - 1]
|
||||
const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
|
||||
if (key) {
|
||||
// Fix #1994: using * with props: true generates a param named 0
|
||||
params[key.name || 'pathMatch'] = val
|
||||
params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -8,7 +8,8 @@ export function createRouteMap (
|
||||
routes: Array<RouteConfig>,
|
||||
oldPathList?: Array<string>,
|
||||
oldPathMap?: Dictionary<RouteRecord>,
|
||||
oldNameMap?: Dictionary<RouteRecord>
|
||||
oldNameMap?: Dictionary<RouteRecord>,
|
||||
parentRoute?: RouteRecord
|
||||
): {
|
||||
pathList: Array<string>,
|
||||
pathMap: Dictionary<RouteRecord>,
|
||||
@@ -22,7 +23,7 @@ export function createRouteMap (
|
||||
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
|
||||
|
||||
routes.forEach(route => {
|
||||
addRouteRecord(pathList, pathMap, nameMap, route)
|
||||
addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
|
||||
})
|
||||
|
||||
// ensure wildcard routes are always at the end
|
||||
@@ -70,6 +71,14 @@ function addRouteRecord (
|
||||
path || name
|
||||
)} cannot be a ` + `string id. Use an actual component instead.`
|
||||
)
|
||||
|
||||
warn(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
!/[^\u0000-\u007F]+/.test(path),
|
||||
`Route with path "${path}" contains unencoded characters, make sure ` +
|
||||
`your path is correctly encoded before passing it to the router. Use ` +
|
||||
`encodeURI to encode static segments of your path.`
|
||||
)
|
||||
}
|
||||
|
||||
const pathToRegexpOptions: PathToRegexpOptions =
|
||||
@@ -84,7 +93,13 @@ function addRouteRecord (
|
||||
path: normalizedPath,
|
||||
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
|
||||
components: route.components || { default: route.component },
|
||||
alias: route.alias
|
||||
? typeof route.alias === 'string'
|
||||
? [route.alias]
|
||||
: route.alias
|
||||
: [],
|
||||
instances: {},
|
||||
enteredCbs: {},
|
||||
name,
|
||||
parent,
|
||||
matchAs,
|
||||
@@ -114,7 +129,7 @@ function addRouteRecord (
|
||||
`Named Route '${route.name}' has a default child route. ` +
|
||||
`When navigating to this named route (:to="{name: '${
|
||||
route.name
|
||||
}'"), ` +
|
||||
}'}"), ` +
|
||||
`the default child route will not be rendered. Remove the name from ` +
|
||||
`this route and use the name of the default child route for named ` +
|
||||
`links instead.`
|
||||
|
||||
+6
-3
@@ -2,8 +2,7 @@
|
||||
|
||||
import type Router from '../index'
|
||||
import { History } from './base'
|
||||
import { isRouterError } from '../util/warn'
|
||||
import { NavigationFailureType } from './errors'
|
||||
import { NavigationFailureType, isNavigationFailure } from '../util/errors'
|
||||
|
||||
export class AbstractHistory extends History {
|
||||
index: number
|
||||
@@ -47,11 +46,15 @@ export class AbstractHistory extends History {
|
||||
this.confirmTransition(
|
||||
route,
|
||||
() => {
|
||||
const prev = this.current
|
||||
this.index = targetIndex
|
||||
this.updateRoute(route)
|
||||
this.router.afterHooks.forEach(hook => {
|
||||
hook && hook(route, prev)
|
||||
})
|
||||
},
|
||||
err => {
|
||||
if (isRouterError(err, NavigationFailureType.duplicated)) {
|
||||
if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
|
||||
this.index = targetIndex
|
||||
}
|
||||
}
|
||||
|
||||
+53
-56
@@ -4,8 +4,8 @@ import { _Vue } from '../install'
|
||||
import type Router from '../index'
|
||||
import { inBrowser } from '../util/dom'
|
||||
import { runQueue } from '../util/async'
|
||||
import { warn, isError, isRouterError } from '../util/warn'
|
||||
import { START, isSameRoute } from '../util/route'
|
||||
import { warn } from '../util/warn'
|
||||
import { START, isSameRoute, handleRouteEntered } from '../util/route'
|
||||
import {
|
||||
flatten,
|
||||
flatMapComponents,
|
||||
@@ -16,8 +16,11 @@ import {
|
||||
createNavigationCancelledError,
|
||||
createNavigationRedirectedError,
|
||||
createNavigationAbortedError,
|
||||
isError,
|
||||
isNavigationFailure,
|
||||
NavigationFailureType
|
||||
} from './errors'
|
||||
} from '../util/errors'
|
||||
import { handleScroll } from '../util/scroll'
|
||||
|
||||
export class History {
|
||||
router: Router
|
||||
@@ -35,7 +38,11 @@ export class History {
|
||||
// implemented by sub-classes
|
||||
+go: (n: number) => void
|
||||
+push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
|
||||
+replace: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
|
||||
+replace: (
|
||||
loc: RawLocation,
|
||||
onComplete?: Function,
|
||||
onAbort?: Function
|
||||
) => void
|
||||
+ensureURL: (push?: boolean) => void
|
||||
+getCurrentLocation: () => string
|
||||
+setupListeners: Function
|
||||
@@ -77,11 +84,21 @@ export class History {
|
||||
onComplete?: Function,
|
||||
onAbort?: Function
|
||||
) {
|
||||
const route = this.router.match(location, this.current)
|
||||
let route
|
||||
// catch redirect option https://github.com/vuejs/vue-router/issues/3201
|
||||
try {
|
||||
route = this.router.match(location, this.current)
|
||||
} catch (e) {
|
||||
this.errorCbs.forEach(cb => {
|
||||
cb(e)
|
||||
})
|
||||
// Exception should still be thrown
|
||||
throw e
|
||||
}
|
||||
const prev = this.current
|
||||
this.confirmTransition(
|
||||
route,
|
||||
() => {
|
||||
const prev = this.current
|
||||
this.updateRoute(route)
|
||||
onComplete && onComplete(route)
|
||||
this.ensureURL()
|
||||
@@ -102,17 +119,15 @@ export class History {
|
||||
onAbort(err)
|
||||
}
|
||||
if (err && !this.ready) {
|
||||
this.ready = true
|
||||
// Initial redirection should still trigger the onReady onSuccess
|
||||
// Initial redirection should not mark the history as ready yet
|
||||
// because it's triggered by the redirection instead
|
||||
// https://github.com/vuejs/vue-router/issues/3225
|
||||
if (!isRouterError(err, NavigationFailureType.redirected)) {
|
||||
// https://github.com/vuejs/vue-router/issues/3331
|
||||
if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
|
||||
this.ready = true
|
||||
this.readyErrorCbs.forEach(cb => {
|
||||
cb(err)
|
||||
})
|
||||
} else {
|
||||
this.readyCbs.forEach(cb => {
|
||||
cb(route)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,17 +136,20 @@ export class History {
|
||||
|
||||
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
|
||||
const current = this.current
|
||||
this.pending = route
|
||||
const abort = err => {
|
||||
// changed after adding errors with
|
||||
// https://github.com/vuejs/vue-router/pull/3047 before that change,
|
||||
// redirect and aborted navigation would produce an err == null
|
||||
if (!isRouterError(err) && isError(err)) {
|
||||
if (!isNavigationFailure(err) && isError(err)) {
|
||||
if (this.errorCbs.length) {
|
||||
this.errorCbs.forEach(cb => {
|
||||
cb(err)
|
||||
})
|
||||
} else {
|
||||
warn(false, 'uncaught error during route navigation:')
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(false, 'uncaught error during route navigation:')
|
||||
}
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
@@ -146,6 +164,9 @@ export class History {
|
||||
route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
|
||||
) {
|
||||
this.ensureURL()
|
||||
if (route.hash) {
|
||||
handleScroll(this.router, current, route, false)
|
||||
}
|
||||
return abort(createNavigationDuplicatedError(current, route))
|
||||
}
|
||||
|
||||
@@ -167,7 +188,6 @@ export class History {
|
||||
resolveAsyncComponents(activated)
|
||||
)
|
||||
|
||||
this.pending = route
|
||||
const iterator = (hook: NavigationGuard, next) => {
|
||||
if (this.pending !== route) {
|
||||
return abort(createNavigationCancelledError(current, route))
|
||||
@@ -204,11 +224,9 @@ export class History {
|
||||
}
|
||||
|
||||
runQueue(queue, iterator, () => {
|
||||
const postEnterCbs = []
|
||||
const isValid = () => this.current === route
|
||||
// wait until async components are resolved before
|
||||
// extracting in-component enter guards
|
||||
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
|
||||
const enterGuards = extractEnterGuards(activated)
|
||||
const queue = enterGuards.concat(this.router.resolveHooks)
|
||||
runQueue(queue, iterator, () => {
|
||||
if (this.pending !== route) {
|
||||
@@ -218,9 +236,7 @@ export class History {
|
||||
onComplete(route)
|
||||
if (this.router.app) {
|
||||
this.router.app.$nextTick(() => {
|
||||
postEnterCbs.forEach(cb => {
|
||||
cb()
|
||||
})
|
||||
handleRouteEntered(route)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -236,11 +252,18 @@ export class History {
|
||||
// Default implementation is empty
|
||||
}
|
||||
|
||||
teardownListeners () {
|
||||
teardown () {
|
||||
// clean up event listeners
|
||||
// https://github.com/vuejs/vue-router/issues/2341
|
||||
this.listeners.forEach(cleanupListener => {
|
||||
cleanupListener()
|
||||
})
|
||||
this.listeners = []
|
||||
|
||||
// reset current history route
|
||||
// https://github.com/vuejs/vue-router/issues/3294
|
||||
this.current = START
|
||||
this.pending = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,15 +354,13 @@ function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
|
||||
}
|
||||
|
||||
function extractEnterGuards (
|
||||
activated: Array<RouteRecord>,
|
||||
cbs: Array<Function>,
|
||||
isValid: () => boolean
|
||||
activated: Array<RouteRecord>
|
||||
): Array<?Function> {
|
||||
return extractGuards(
|
||||
activated,
|
||||
'beforeRouteEnter',
|
||||
(guard, _, match, key) => {
|
||||
return bindEnterGuard(guard, match, key, cbs, isValid)
|
||||
return bindEnterGuard(guard, match, key)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -347,41 +368,17 @@ function extractEnterGuards (
|
||||
function bindEnterGuard (
|
||||
guard: NavigationGuard,
|
||||
match: RouteRecord,
|
||||
key: string,
|
||||
cbs: Array<Function>,
|
||||
isValid: () => boolean
|
||||
key: string
|
||||
): NavigationGuard {
|
||||
return function routeEnterGuard (to, from, next) {
|
||||
return guard(to, from, cb => {
|
||||
if (typeof cb === 'function') {
|
||||
cbs.push(() => {
|
||||
// #750
|
||||
// if a router-view is wrapped with an out-in transition,
|
||||
// the instance may not have been registered at this time.
|
||||
// we will need to poll for registration until current route
|
||||
// is no longer valid.
|
||||
poll(cb, match.instances, key, isValid)
|
||||
})
|
||||
if (!match.enteredCbs[key]) {
|
||||
match.enteredCbs[key] = []
|
||||
}
|
||||
match.enteredCbs[key].push(cb)
|
||||
}
|
||||
next(cb)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function poll (
|
||||
cb: any, // somehow flow cannot infer this is a function
|
||||
instances: Object,
|
||||
key: string,
|
||||
isValid: () => boolean
|
||||
) {
|
||||
if (
|
||||
instances[key] &&
|
||||
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance
|
||||
) {
|
||||
cb(instances[key])
|
||||
} else if (isValid()) {
|
||||
setTimeout(() => {
|
||||
poll(cb, instances, key, isValid)
|
||||
}, 16)
|
||||
}
|
||||
}
|
||||
|
||||
-12
@@ -124,18 +124,6 @@ export function getHash (): string {
|
||||
if (index < 0) return ''
|
||||
|
||||
href = href.slice(index + 1)
|
||||
// decode the hash but not the search or hash
|
||||
// as search(query) is already decoded
|
||||
// https://github.com/vuejs/vue-router/issues/2708
|
||||
const searchIndex = href.indexOf('?')
|
||||
if (searchIndex < 0) {
|
||||
const hashIndex = href.indexOf('#')
|
||||
if (hashIndex > -1) {
|
||||
href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex)
|
||||
} else href = decodeURI(href)
|
||||
} else {
|
||||
href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex)
|
||||
}
|
||||
|
||||
return href
|
||||
}
|
||||
|
||||
+8
-2
@@ -86,8 +86,14 @@ export class HTML5History extends History {
|
||||
}
|
||||
|
||||
export function getLocation (base: string): string {
|
||||
let path = decodeURI(window.location.pathname)
|
||||
if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
|
||||
let path = window.location.pathname
|
||||
const pathLowerCase = path.toLowerCase()
|
||||
const baseLowerCase = base.toLowerCase()
|
||||
// base="/a" shouldn't turn path="/app" into "/a/pp"
|
||||
// https://github.com/vuejs/vue-router/issues/3555
|
||||
// so we ensure the trailing slash in the base
|
||||
if (base && ((pathLowerCase === baseLowerCase) ||
|
||||
(pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
|
||||
path = path.slice(base.length)
|
||||
}
|
||||
return (path || '/') + window.location.search + window.location.hash
|
||||
|
||||
+78
-46
@@ -2,12 +2,13 @@
|
||||
|
||||
import { install } from './install'
|
||||
import { START } from './util/route'
|
||||
import { assert } from './util/warn'
|
||||
import { assert, warn } from './util/warn'
|
||||
import { inBrowser } from './util/dom'
|
||||
import { cleanPath } from './util/path'
|
||||
import { createMatcher } from './create-matcher'
|
||||
import { normalizeLocation } from './util/location'
|
||||
import { supportsPushState } from './util/push-state'
|
||||
import { handleScroll } from './util/scroll'
|
||||
|
||||
import { HashHistory } from './history/hash'
|
||||
import { HTML5History } from './history/html5'
|
||||
@@ -15,24 +16,32 @@ import { AbstractHistory } from './history/abstract'
|
||||
|
||||
import type { Matcher } from './create-matcher'
|
||||
|
||||
export default class VueRouter {
|
||||
static install: () => void;
|
||||
static version: string;
|
||||
import { isNavigationFailure, NavigationFailureType } from './util/errors'
|
||||
|
||||
app: any;
|
||||
apps: Array<any>;
|
||||
ready: boolean;
|
||||
readyCbs: Array<Function>;
|
||||
options: RouterOptions;
|
||||
mode: string;
|
||||
history: HashHistory | HTML5History | AbstractHistory;
|
||||
matcher: Matcher;
|
||||
fallback: boolean;
|
||||
beforeHooks: Array<?NavigationGuard>;
|
||||
resolveHooks: Array<?NavigationGuard>;
|
||||
afterHooks: Array<?AfterNavigationHook>;
|
||||
export default class VueRouter {
|
||||
static install: () => void
|
||||
static version: string
|
||||
static isNavigationFailure: Function
|
||||
static NavigationFailureType: any
|
||||
static START_LOCATION: Route
|
||||
|
||||
app: any
|
||||
apps: Array<any>
|
||||
ready: boolean
|
||||
readyCbs: Array<Function>
|
||||
options: RouterOptions
|
||||
mode: string
|
||||
history: HashHistory | HTML5History | AbstractHistory
|
||||
matcher: Matcher
|
||||
fallback: boolean
|
||||
beforeHooks: Array<?NavigationGuard>
|
||||
resolveHooks: Array<?NavigationGuard>
|
||||
afterHooks: Array<?AfterNavigationHook>
|
||||
|
||||
constructor (options: RouterOptions = {}) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(this instanceof VueRouter, `Router must be called with the new operator.`)
|
||||
}
|
||||
this.app = null
|
||||
this.apps = []
|
||||
this.options = options
|
||||
@@ -42,7 +51,8 @@ export default class VueRouter {
|
||||
this.matcher = createMatcher(options.routes || [], this)
|
||||
|
||||
let mode = options.mode || 'hash'
|
||||
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
|
||||
this.fallback =
|
||||
mode === 'history' && !supportsPushState && options.fallback !== false
|
||||
if (this.fallback) {
|
||||
mode = 'hash'
|
||||
}
|
||||
@@ -68,11 +78,7 @@ export default class VueRouter {
|
||||
}
|
||||
}
|
||||
|
||||
match (
|
||||
raw: RawLocation,
|
||||
current?: Route,
|
||||
redirectedFrom?: Location
|
||||
): Route {
|
||||
match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
|
||||
return this.matcher.match(raw, current, redirectedFrom)
|
||||
}
|
||||
|
||||
@@ -81,11 +87,12 @@ export default class VueRouter {
|
||||
}
|
||||
|
||||
init (app: any /* Vue component instance */) {
|
||||
process.env.NODE_ENV !== 'production' && assert(
|
||||
install.installed,
|
||||
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
|
||||
`before creating root instance.`
|
||||
)
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
assert(
|
||||
install.installed,
|
||||
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
|
||||
`before creating root instance.`
|
||||
)
|
||||
|
||||
this.apps.push(app)
|
||||
|
||||
@@ -99,11 +106,7 @@ export default class VueRouter {
|
||||
// we do not release the router so it can be reused
|
||||
if (this.app === app) this.app = this.apps[0] || null
|
||||
|
||||
if (!this.app) {
|
||||
// clean up event listeners
|
||||
// https://github.com/vuejs/vue-router/issues/2341
|
||||
this.history.teardownListeners()
|
||||
}
|
||||
if (!this.app) this.history.teardown()
|
||||
})
|
||||
|
||||
// main app previously initialized
|
||||
@@ -117,14 +120,28 @@ export default class VueRouter {
|
||||
const history = this.history
|
||||
|
||||
if (history instanceof HTML5History || history instanceof HashHistory) {
|
||||
const setupListeners = () => {
|
||||
history.setupListeners()
|
||||
const handleInitialScroll = routeOrError => {
|
||||
const from = history.current
|
||||
const expectScroll = this.options.scrollBehavior
|
||||
const supportsScroll = supportsPushState && expectScroll
|
||||
|
||||
if (supportsScroll && 'fullPath' in routeOrError) {
|
||||
handleScroll(this, routeOrError, from, false)
|
||||
}
|
||||
}
|
||||
history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners)
|
||||
const setupListeners = routeOrError => {
|
||||
history.setupListeners()
|
||||
handleInitialScroll(routeOrError)
|
||||
}
|
||||
history.transitionTo(
|
||||
history.getCurrentLocation(),
|
||||
setupListeners,
|
||||
setupListeners
|
||||
)
|
||||
}
|
||||
|
||||
history.listen(route => {
|
||||
this.apps.forEach((app) => {
|
||||
this.apps.forEach(app => {
|
||||
app._route = route
|
||||
})
|
||||
})
|
||||
@@ -193,11 +210,14 @@ export default class VueRouter {
|
||||
if (!route) {
|
||||
return []
|
||||
}
|
||||
return [].concat.apply([], route.matched.map(m => {
|
||||
return Object.keys(m.components).map(key => {
|
||||
return m.components[key]
|
||||
return [].concat.apply(
|
||||
[],
|
||||
route.matched.map(m => {
|
||||
return Object.keys(m.components).map(key => {
|
||||
return m.components[key]
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
resolve (
|
||||
@@ -213,12 +233,7 @@ export default class VueRouter {
|
||||
resolved: Route
|
||||
} {
|
||||
current = current || this.history.current
|
||||
const location = normalizeLocation(
|
||||
to,
|
||||
current,
|
||||
append,
|
||||
this
|
||||
)
|
||||
const location = normalizeLocation(to, current, append, this)
|
||||
const route = this.match(location, current)
|
||||
const fullPath = route.redirectedFrom || route.fullPath
|
||||
const base = this.history.base
|
||||
@@ -233,7 +248,21 @@ export default class VueRouter {
|
||||
}
|
||||
}
|
||||
|
||||
getRoutes () {
|
||||
return this.matcher.getRoutes()
|
||||
}
|
||||
|
||||
addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
|
||||
this.matcher.addRoute(parentOrRoute, route)
|
||||
if (this.history.current !== START) {
|
||||
this.history.transitionTo(this.history.getCurrentLocation())
|
||||
}
|
||||
}
|
||||
|
||||
addRoutes (routes: Array<RouteConfig>) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')
|
||||
}
|
||||
this.matcher.addRoutes(routes)
|
||||
if (this.history.current !== START) {
|
||||
this.history.transitionTo(this.history.getCurrentLocation())
|
||||
@@ -256,6 +285,9 @@ function createHref (base: string, fullPath: string, mode) {
|
||||
|
||||
VueRouter.install = install
|
||||
VueRouter.version = '__VERSION__'
|
||||
VueRouter.isNavigationFailure = isNavigationFailure
|
||||
VueRouter.NavigationFailureType = NavigationFailureType
|
||||
VueRouter.START_LOCATION = START
|
||||
|
||||
if (inBrowser && window.Vue) {
|
||||
window.Vue.use(VueRouter)
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// When changing thing, also edit router.d.ts
|
||||
export const NavigationFailureType = {
|
||||
redirected: 2,
|
||||
aborted: 4,
|
||||
cancelled: 8,
|
||||
duplicated: 16
|
||||
}
|
||||
|
||||
export function createNavigationRedirectedError (from, to) {
|
||||
return createRouterError(
|
||||
from,
|
||||
to,
|
||||
NavigationFailureType.redirected,
|
||||
`Redirected when going from "${from.fullPath}" to "${stringifyRoute(
|
||||
to
|
||||
)}" via a navigation guard.`
|
||||
)
|
||||
}
|
||||
|
||||
export function createNavigationDuplicatedError (from, to) {
|
||||
const error = createRouterError(
|
||||
from,
|
||||
to,
|
||||
NavigationFailureType.duplicated,
|
||||
`Avoided redundant navigation to current location: "${from.fullPath}".`
|
||||
)
|
||||
// backwards compatible with the first introduction of Errors
|
||||
error.name = 'NavigationDuplicated'
|
||||
return error
|
||||
}
|
||||
|
||||
export function createNavigationCancelledError (from, to) {
|
||||
return createRouterError(
|
||||
from,
|
||||
to,
|
||||
NavigationFailureType.cancelled,
|
||||
`Navigation cancelled from "${from.fullPath}" to "${
|
||||
to.fullPath
|
||||
}" with a new navigation.`
|
||||
)
|
||||
}
|
||||
|
||||
export function createNavigationAbortedError (from, to) {
|
||||
return createRouterError(
|
||||
from,
|
||||
to,
|
||||
NavigationFailureType.aborted,
|
||||
`Navigation aborted from "${from.fullPath}" to "${
|
||||
to.fullPath
|
||||
}" via a navigation guard.`
|
||||
)
|
||||
}
|
||||
|
||||
function createRouterError (from, to, type, message) {
|
||||
const error = new Error(message)
|
||||
error._isRouter = true
|
||||
error.from = from
|
||||
error.to = to
|
||||
error.type = type
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
const propertiesToLog = ['params', 'query', 'hash']
|
||||
|
||||
function stringifyRoute (to) {
|
||||
if (typeof to === 'string') return to
|
||||
if ('path' in to) return to.path
|
||||
const location = {}
|
||||
propertiesToLog.forEach(key => {
|
||||
if (key in to) location[key] = to[key]
|
||||
})
|
||||
return JSON.stringify(location, null, 2)
|
||||
}
|
||||
|
||||
export function isError (err) {
|
||||
return Object.prototype.toString.call(err).indexOf('Error') > -1
|
||||
}
|
||||
|
||||
export function isNavigationFailure (err, errorType) {
|
||||
return (
|
||||
isError(err) &&
|
||||
err._isRouter &&
|
||||
(errorType == null || err.type === errorType)
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -70,5 +70,5 @@ export function parsePath (path: string): {
|
||||
}
|
||||
|
||||
export function cleanPath (path: string): string {
|
||||
return path.replace(/\/\//g, '/')
|
||||
return path.replace(/\/(?:\s*\/)+/g, '/')
|
||||
}
|
||||
|
||||
+50
-32
@@ -9,11 +9,21 @@ const commaRE = /%2C/g
|
||||
// fixed encodeURIComponent which is more conformant to RFC3986:
|
||||
// - escapes [!'()*]
|
||||
// - preserve commas
|
||||
const encode = str => encodeURIComponent(str)
|
||||
.replace(encodeReserveRE, encodeReserveReplacer)
|
||||
.replace(commaRE, ',')
|
||||
const encode = str =>
|
||||
encodeURIComponent(str)
|
||||
.replace(encodeReserveRE, encodeReserveReplacer)
|
||||
.replace(commaRE, ',')
|
||||
|
||||
const decode = decodeURIComponent
|
||||
export function decode (str: string) {
|
||||
try {
|
||||
return decodeURIComponent(str)
|
||||
} catch (err) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(false, `Error decoding "${str}". Leaving it intact.`)
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
export function resolveQuery (
|
||||
query: ?string,
|
||||
@@ -29,11 +39,16 @@ export function resolveQuery (
|
||||
parsedQuery = {}
|
||||
}
|
||||
for (const key in extraQuery) {
|
||||
parsedQuery[key] = extraQuery[key]
|
||||
const value = extraQuery[key]
|
||||
parsedQuery[key] = Array.isArray(value)
|
||||
? value.map(castQueryParamValue)
|
||||
: castQueryParamValue(value)
|
||||
}
|
||||
return parsedQuery
|
||||
}
|
||||
|
||||
const castQueryParamValue = value => (value == null || typeof value === 'object' ? value : String(value))
|
||||
|
||||
function parseQuery (query: string): Dictionary<string> {
|
||||
const res = {}
|
||||
|
||||
@@ -46,9 +61,7 @@ function parseQuery (query: string): Dictionary<string> {
|
||||
query.split('&').forEach(param => {
|
||||
const parts = param.replace(/\+/g, ' ').split('=')
|
||||
const key = decode(parts.shift())
|
||||
const val = parts.length > 0
|
||||
? decode(parts.join('='))
|
||||
: null
|
||||
const val = parts.length > 0 ? decode(parts.join('=')) : null
|
||||
|
||||
if (res[key] === undefined) {
|
||||
res[key] = val
|
||||
@@ -63,33 +76,38 @@ function parseQuery (query: string): Dictionary<string> {
|
||||
}
|
||||
|
||||
export function stringifyQuery (obj: Dictionary<string>): string {
|
||||
const res = obj ? Object.keys(obj).map(key => {
|
||||
const val = obj[key]
|
||||
const res = obj
|
||||
? Object.keys(obj)
|
||||
.map(key => {
|
||||
const val = obj[key]
|
||||
|
||||
if (val === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (val === null) {
|
||||
return encode(key)
|
||||
}
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
const result = []
|
||||
val.forEach(val2 => {
|
||||
if (val2 === undefined) {
|
||||
return
|
||||
if (val === undefined) {
|
||||
return ''
|
||||
}
|
||||
if (val2 === null) {
|
||||
result.push(encode(key))
|
||||
} else {
|
||||
result.push(encode(key) + '=' + encode(val2))
|
||||
|
||||
if (val === null) {
|
||||
return encode(key)
|
||||
}
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
const result = []
|
||||
val.forEach(val2 => {
|
||||
if (val2 === undefined) {
|
||||
return
|
||||
}
|
||||
if (val2 === null) {
|
||||
result.push(encode(key))
|
||||
} else {
|
||||
result.push(encode(key) + '=' + encode(val2))
|
||||
}
|
||||
})
|
||||
return result.join('&')
|
||||
}
|
||||
|
||||
return encode(key) + '=' + encode(val)
|
||||
})
|
||||
return result.join('&')
|
||||
}
|
||||
|
||||
return encode(key) + '=' + encode(val)
|
||||
}).filter(x => x.length > 0).join('&') : null
|
||||
.filter(x => x.length > 0)
|
||||
.join('&')
|
||||
: null
|
||||
return res ? `?${res}` : ''
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import { _Vue } from '../install'
|
||||
import { warn, isError } from './warn'
|
||||
import { warn } from './warn'
|
||||
import { isError } from '../util/errors'
|
||||
|
||||
export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
|
||||
return (to, from, next) => {
|
||||
|
||||
+29
-10
@@ -70,23 +70,23 @@ function getFullPath (
|
||||
return (path || '/') + stringify(query) + hash
|
||||
}
|
||||
|
||||
export function isSameRoute (a: Route, b: ?Route): boolean {
|
||||
export function isSameRoute (a: Route, b: ?Route, onlyPath: ?boolean): boolean {
|
||||
if (b === START) {
|
||||
return a === b
|
||||
} else if (!b) {
|
||||
return false
|
||||
} else if (a.path && b.path) {
|
||||
return (
|
||||
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
|
||||
return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
|
||||
a.hash === b.hash &&
|
||||
isObjectEqual(a.query, b.query)
|
||||
)
|
||||
isObjectEqual(a.query, b.query))
|
||||
} else if (a.name && b.name) {
|
||||
return (
|
||||
a.name === b.name &&
|
||||
a.hash === b.hash &&
|
||||
(onlyPath || (
|
||||
a.hash === b.hash &&
|
||||
isObjectEqual(a.query, b.query) &&
|
||||
isObjectEqual(a.params, b.params)
|
||||
isObjectEqual(a.params, b.params))
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return false
|
||||
@@ -96,14 +96,18 @@ export function isSameRoute (a: Route, b: ?Route): boolean {
|
||||
function isObjectEqual (a = {}, b = {}): boolean {
|
||||
// handle null value #1566
|
||||
if (!a || !b) return a === b
|
||||
const aKeys = Object.keys(a)
|
||||
const bKeys = Object.keys(b)
|
||||
const aKeys = Object.keys(a).sort()
|
||||
const bKeys = Object.keys(b).sort()
|
||||
if (aKeys.length !== bKeys.length) {
|
||||
return false
|
||||
}
|
||||
return aKeys.every(key => {
|
||||
return aKeys.every((key, i) => {
|
||||
const aVal = a[key]
|
||||
const bKey = bKeys[i]
|
||||
if (bKey !== key) return false
|
||||
const bVal = b[key]
|
||||
// query values can be null and undefined
|
||||
if (aVal == null || bVal == null) return aVal === bVal
|
||||
// check nested equality
|
||||
if (typeof aVal === 'object' && typeof bVal === 'object') {
|
||||
return isObjectEqual(aVal, bVal)
|
||||
@@ -130,3 +134,18 @@ function queryIncludes (current: Dictionary<string>, target: Dictionary<string>)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function handleRouteEntered (route: Route) {
|
||||
for (let i = 0; i < route.matched.length; i++) {
|
||||
const record = route.matched[i]
|
||||
for (const name in record.instances) {
|
||||
const instance = record.instances[name]
|
||||
const cbs = record.enteredCbs[name]
|
||||
if (!instance || !cbs) continue
|
||||
delete record.enteredCbs[name]
|
||||
for (let i = 0; i < cbs.length; i++) {
|
||||
if (!instance._isBeingDestroyed) cbs[i](instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -160,6 +160,16 @@ function scrollToPosition (shouldScroll, position) {
|
||||
}
|
||||
|
||||
if (position) {
|
||||
window.scrollTo(position.x, position.y)
|
||||
// $flow-disable-line
|
||||
if ('scrollBehavior' in document.documentElement.style) {
|
||||
window.scrollTo({
|
||||
left: position.x,
|
||||
top: position.y,
|
||||
// $flow-disable-line
|
||||
behavior: shouldScroll.behavior
|
||||
})
|
||||
} else {
|
||||
window.scrollTo(position.x, position.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -7,15 +7,8 @@ export function assert (condition: any, message: string) {
|
||||
}
|
||||
|
||||
export function warn (condition: any, message: string) {
|
||||
if (process.env.NODE_ENV !== 'production' && !condition) {
|
||||
if (!condition) {
|
||||
typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function isError (err: any): boolean {
|
||||
return Object.prototype.toString.call(err).indexOf('Error') > -1
|
||||
}
|
||||
|
||||
export function isRouterError (err: any, errorType: ?string): boolean {
|
||||
return isError(err) && err._isRouter && (errorType == null || err.type === errorType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user