-
Notifications
You must be signed in to change notification settings - Fork 156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #129: handle relative paths on links and route calls #243
base: main
Are you sure you want to change the base?
Changes from 2 commits
f2c85be
0381a36
c763a39
36199b5
370ecc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,28 +23,51 @@ function setUrl(url, type='push') { | |
} | ||
|
||
|
||
function getCurrentLocation() { | ||
return (customHistory && customHistory.location) || | ||
(customHistory && customHistory.getCurrentLocation && customHistory.getCurrentLocation()) || | ||
(typeof location!=='undefined' ? location : EMPTY); | ||
} | ||
|
||
|
||
function getCurrentUrl() { | ||
let url; | ||
if (customHistory && customHistory.location) { | ||
url = customHistory.location; | ||
} | ||
else if (customHistory && customHistory.getCurrentLocation) { | ||
url = customHistory.getCurrentLocation(); | ||
} | ||
else { | ||
url = typeof location!=='undefined' ? location : EMPTY; | ||
} | ||
let url = getCurrentLocation(); | ||
return `${url.pathname || ''}${url.search || ''}`; | ||
} | ||
|
||
|
||
const a = typeof document!=='undefined' && document.createElement('a'); | ||
|
||
// Based on https://tools.ietf.org/html/rfc3986#appendix-B | ||
const uriRegex = new RegExp('^([^:/?#]+:)?(?://([^/?#]*))?([^?#]*)((?:\\?[^#]*)?)((?:#.*)?)'); | ||
|
||
/* Resolve URL relative to current location */ | ||
function resolve(url) { | ||
let current = getCurrentLocation(); | ||
if (a) { | ||
a.setAttribute('href', url); | ||
url = a.href; | ||
} | ||
let [,protocol,host,pathname,search,hash] = uriRegex.exec(url); | ||
if ( | ||
(current.protocol && protocol !== current.protocol) || | ||
(current.host && host !== current.host) | ||
) { | ||
return; | ||
} | ||
return `${pathname}${search}${hash}`; | ||
} | ||
|
||
|
||
function route(url, replace=false) { | ||
if (typeof url!=='string' && url.url) { | ||
replace = url.replace; | ||
url = url.url; | ||
} | ||
|
||
url = resolve(url); | ||
if (!url) return; | ||
|
||
// only push URL into history if we can handle it | ||
if (canRoute(url)) { | ||
setUrl(url, replace ? 'replace' : 'push'); | ||
|
@@ -79,17 +102,11 @@ function routeTo(url) { | |
|
||
|
||
function routeFromLink(node) { | ||
// only valid elements | ||
if (!node || !node.getAttribute) return; | ||
|
||
let href = node.getAttribute('href'), | ||
target = node.getAttribute('target'); | ||
|
||
// ignore links with targets and non-path URLs | ||
if (!href || !href.match(/^\//g) || (target && !target.match(/^_?self$/i))) return; | ||
// ignore invalid & external links: | ||
if (!node || (node.target && !node.target.match(/^_?self$/i))) return; | ||
|
||
// attempt to route, if no match simply cede control to browser | ||
return route(href); | ||
return route(node.pathname + node.search + node.hash); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's been a while since I wrote this, but think the reason I did this was for some sort of consistency with |
||
} | ||
|
||
|
||
|
@@ -117,12 +134,9 @@ function delegateLinkHandler(e) { | |
|
||
let t = e.target; | ||
do { | ||
if (String(t.nodeName).toUpperCase()==='A' && t.getAttribute('href') && isPreactElement(t)) { | ||
if (t.hasAttribute('native')) return; | ||
if (String(t.nodeName).toUpperCase()==='A' && t.pathname && isPreactElement(t) && !t.hasAttribute('native') && routeFromLink(t)) { | ||
// if link is handled by the router, prevent browser defaults | ||
if (routeFromLink(t)) { | ||
return prevent(e); | ||
} | ||
return prevent(e); | ||
} | ||
} while ((t=t.parentNode)); | ||
} | ||
|
@@ -131,13 +145,13 @@ function delegateLinkHandler(e) { | |
let eventListenersInitialized = false; | ||
|
||
function initEventListeners() { | ||
if (eventListenersInitialized){ | ||
return; | ||
} | ||
if (eventListenersInitialized) return; | ||
|
||
if (typeof addEventListener==='function') { | ||
if (!customHistory) { | ||
addEventListener('popstate', () => routeTo(getCurrentUrl())); | ||
addEventListener('popstate', () => { | ||
routeTo(getCurrentUrl()); | ||
}); | ||
} | ||
addEventListener('click', delegateLinkHandler); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we skip the parse entirely and just do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may have been worrying about server-side (nodejs) rendering where we don't have an
a
to do URL resolution: is that a valid concern?If not, I'll happily simplify.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can probably skip the SSR concern for now - route() should be a no-op there anyway since SSR is single-pass.