router.js
4.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { RX_ENCODED_COMMA, RX_ENCODE_REVERSE, RX_PLUS, RX_QUERY_START } from '../constants/regex'
import { isTag } from './dom'
import { isArray, isNull, isPlainObject, isString, isUndefined } from './inspect'
import { keys } from './object'
import { safeVueInstance } from './safe-vue-instance'
import { toString } from './string'
const ANCHOR_TAG = 'a'
// Method to replace reserved chars
const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)
// Fixed encodeURIComponent which is more conformant to RFC3986:
// - escapes [!'()*]
// - preserve commas
const encode = str =>
encodeURIComponent(toString(str))
.replace(RX_ENCODE_REVERSE, encodeReserveReplacer)
.replace(RX_ENCODED_COMMA, ',')
const decode = decodeURIComponent
// Stringifies an object of query parameters
// See: https://github.com/vuejs/vue-router/blob/dev/src/util/query.js
export const stringifyQueryObj = obj => {
if (!isPlainObject(obj)) {
return ''
}
const query = keys(obj)
.map(key => {
const value = obj[key]
if (isUndefined(value)) {
return ''
} else if (isNull(value)) {
return encode(key)
} else if (isArray(value)) {
return value
.reduce((results, value2) => {
if (isNull(value2)) {
results.push(encode(key))
} else if (!isUndefined(value2)) {
// Faster than string interpolation
results.push(encode(key) + '=' + encode(value2))
}
return results
}, [])
.join('&')
}
// Faster than string interpolation
return encode(key) + '=' + encode(value)
})
/* must check for length, as we only want to filter empty strings, not things that look falsey! */
.filter(x => x.length > 0)
.join('&')
return query ? `?${query}` : ''
}
export const parseQuery = query => {
const parsed = {}
query = toString(query)
.trim()
.replace(RX_QUERY_START, '')
if (!query) {
return parsed
}
query.split('&').forEach(param => {
const parts = param.replace(RX_PLUS, ' ').split('=')
const key = decode(parts.shift())
const value = parts.length > 0 ? decode(parts.join('=')) : null
if (isUndefined(parsed[key])) {
parsed[key] = value
} else if (isArray(parsed[key])) {
parsed[key].push(value)
} else {
parsed[key] = [parsed[key], value]
}
})
return parsed
}
export const isLink = props => !!(props.href || props.to)
export const isRouterLink = tag => !!(tag && !isTag(tag, 'a'))
export const computeTag = ({ to, disabled, routerComponentName }, thisOrParent) => {
const hasRouter = !!safeVueInstance(thisOrParent).$router
const hasNuxt = !!safeVueInstance(thisOrParent).$nuxt
if (!hasRouter || (hasRouter && (disabled || !to))) {
return ANCHOR_TAG
}
// TODO:
// Check registered components for existence of user supplied router link component name
// We would need to check PascalCase, kebab-case, and camelCase versions of name:
// const name = routerComponentName
// const names = [name, PascalCase(name), KebabCase(name), CamelCase(name)]
// exists = names.some(name => !!thisOrParent.$options.components[name])
// And may want to cache the result for performance or we just let the render fail
// if the component is not registered
return routerComponentName || (hasNuxt ? 'nuxt-link' : 'router-link')
}
export const computeRel = ({ target, rel } = {}) =>
target === '_blank' && isNull(rel) ? 'noopener' : rel || null
export const computeHref = (
{ href, to } = {},
tag = ANCHOR_TAG,
fallback = '#',
toFallback = '/'
) => {
// Return `href` when explicitly provided
if (href) {
return href
}
// We've checked for `$router` in `computeTag()`, so `isRouterLink()` indicates a live router
// When deferring to Vue Router's `<router-link>`, don't use the `href` attribute at all
// We return `null`, and then remove `href` from the attributes passed to `<router-link>`
if (isRouterLink(tag)) {
return null
}
// Fallback to `to` prop (if `to` is a string)
if (isString(to)) {
return to || toFallback
}
// Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object)
if (isPlainObject(to) && (to.path || to.query || to.hash)) {
const path = toString(to.path)
const query = stringifyQueryObj(to.query)
let hash = toString(to.hash)
hash = !hash || hash.charAt(0) === '#' ? hash : `#${hash}`
return `${path}${query}${hash}` || toFallback
}
// If nothing is provided return the fallback
return fallback
}