-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.coffee
117 lines (91 loc) · 3.43 KB
/
index.coffee
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
# Deps
import URL from 'url-parse'
# Default settings
settings =
addBlankToExternal: false
addTrailingSlashToInternal: false
internalUrls: []
sameWindowUrls: []
internalHosts: []
externalPaths: []
# Override the settings
mergeSettings = (choices) -> settings = {...settings, ...choices }
# Add listeners to anchors
bind = (el, binding, vnode) ->
# Get the router instance
router = vnode.context.$router
# Handle self
handleAnchor el, router if el.tagName.toLowerCase() == 'a'
# Handle child anchors that have an href
handleAnchor anchor, router for anchor in el.querySelectorAll 'a'
# Check an anchor tag
export handleAnchor = (anchor, router) ->
# If an explicit target attribute is set, then abort. Assuming the author
# of the content knew what they were doing.
return if anchor.getAttribute 'target'
if url = anchor.getAttribute 'href'
if isInternal url
then handleInternal anchor, url, router
else handleExternal anchor, url
# Test if an anchor is an internal link
export isInternal = (url) ->
urlObj = makeUrlObj url
# Does it match externalPaths
for pathRegex in settings.externalPaths
return false if urlObj.pathname.match pathRegex
# Does it begin with a / and not an // ?
return true if urlObj.href.match /^\/(?!\/)/
# Does it begin with a hash, meaning a link to down page?
return true if urlObj.href.match /^#/
# Does the host match internal URLs?
for urlRegex in settings.internalUrls
return true if urlObj.href.match urlRegex
# Does the host match internal hosts?
return true if urlObj.host in settings.internalHosts
# Make a URL instance from url strings
makeUrlObj = (url) ->
# Already a URL object
return url unless typeof url == 'string'
# Return URL object. Passing an empty object to 2nd param so functions
# the same during SSR as client side
return new URL url, {}
# Add click bindings to internal links that resolve. Thus, if the Vue doesn't
# know about a route, it will not be handled by vue-router. Though it won't
# open in a new window.
handleInternal = (anchor, url, router) ->
path = makeRouterPath url, { router }
if router.resolve({ path }).route.matched.length
anchor.addEventListener 'click', (e) ->
e.preventDefault()
router.push { path }
# Make routeable path
export makeRouterPath = (url, { router } = {}) ->
urlObj = makeUrlObj url
# Remove the router.base from the path, if it exists
path = urlObj.pathname
if (base = router?.options?.base) and path.indexOf(base) == 0
then path = '/' + path.slice base.length
# Create path with query and hash
"#{path}#{urlObj.query}#{urlObj.hash}"
# Add target blank to external links
handleExternal = (anchor, url) ->
if shouldOpenInNewWindow(url) and not anchor.hasAttribute('target')
anchor.setAttribute 'target', '_blank'
# Should external link open in a new window
export shouldOpenInNewWindow = (url) ->
return false unless settings.addBlankToExternal
urlObj = makeUrlObj url
return false if urlObj.href.match /^javascript:/ # A javascript:func() link
for urlRegex in settings.sameWindowUrls
return false if urlObj.href.match urlRegex
return true
# Add trailing slash if one is missing, returning a string
export addTrailingSlash = (url) ->
urlObj = makeUrlObj url
urlObj.pathname += '/' if urlObj.pathname.match /\/$/
urlObj.toString()
# Directive definition with settings method for overriding the default settings.
# I'm relying on Browser garbage collection to cleanup listeners.
export default
bind: bind
settings: mergeSettings