diff --git a/ghost/admin/app/components/gh-navitem-url-input.js b/ghost/admin/app/components/gh-navitem-url-input.js index 20130684db..2d061e105b 100644 --- a/ghost/admin/app/components/gh-navitem-url-input.js +++ b/ghost/admin/app/components/gh-navitem-url-input.js @@ -88,14 +88,13 @@ export default TextField.extend({ }, notifyUrlChanged() { - let value = this.get('value').trim(); + let url = this.get('value').trim(); let urlParts = document.createElement('a'); let baseUrl = this.get('baseUrl'); let baseUrlParts = document.createElement('a'); - let url = value; // ensure value property is trimmed - this.set('value', value); + this.set('value', url); // leverage the browser's native URI parsing urlParts.href = url; @@ -113,12 +112,28 @@ export default TextField.extend({ this.set('value', url); } - // remove the base url before sending to action - if (urlParts.host === baseUrlParts.host && !url.match(/^#/)) { + // get our baseUrl relativity checks in order + let isOnSameHost = urlParts.host === baseUrlParts.host; + let isAnchorLink = url.match(/^#/); + let isRelativeToBasePath = urlParts.pathname.indexOf(baseUrlParts.pathname) === 0; + + // if our pathname is only missing a trailing / mark it as relative + if (`${urlParts.pathname}/` === baseUrlParts.pathname) { + isRelativeToBasePath = true; + } + + // if relative to baseUrl, remove the base url before sending to action + if (!isAnchorLink && isOnSameHost && isRelativeToBasePath) { url = url.replace(/^[a-zA-Z0-9\-]+:/, ''); url = url.replace(/^\/\//, ''); url = url.replace(baseUrlParts.host, ''); url = url.replace(baseUrlParts.pathname, ''); + + // handle case where url path is same as baseUrl path but missing trailing slash + if (urlParts.pathname.slice(-1) !== '/') { + url = url.replace(baseUrlParts.pathname.slice(0, -1), ''); + } + if (!url.match(/^\//)) { url = `/${url}`; } diff --git a/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js b/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js index 1314a8cbad..edcfcdaa99 100644 --- a/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js +++ b/ghost/admin/tests/integration/components/gh-navitem-url-input-test.js @@ -320,35 +320,6 @@ describeComponent( testUrl('test/nested'); }); - it('handles a baseUrl with a path component', function () { - let expectedUrl = ''; - - this.set('baseUrl', `${currentUrl}blog/`); - - this.on('updateUrl', (url) => { - expect(url).to.equal(expectedUrl); - }); - - this.render(hbs ` - {{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} - `); - let $input = this.$('input'); - - let testUrl = (url) => { - expectedUrl = url; - run(() => { - $input.val(`${currentUrl}blog${url}`).trigger('input'); - }); - run(() => { - $input.trigger('blur'); - }); - }; - - testUrl('/about'); - testUrl('/about#contact'); - testUrl('/test/nested'); - }); - it('handles links to subdomains of blog domain', function () { let expectedUrl = ''; @@ -369,5 +340,72 @@ describeComponent( }); expect($input.val()).to.equal(expectedUrl); }); + + describe('with sub-folder baseUrl', function () { + beforeEach(function () { + this.set('baseUrl', `${currentUrl}blog/`); + }); + + it('handles URLs relative to base url', function () { + let expectedUrl = ''; + + this.on('updateUrl', (url) => { + expect(url).to.equal(expectedUrl); + }); + + this.render(hbs ` + {{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} + `); + let $input = this.$('input'); + + let testUrl = (url) => { + expectedUrl = url; + run(() => { + $input.val(`${currentUrl}blog${url}`).trigger('input'); + }); + run(() => { + $input.trigger('blur'); + }); + }; + + testUrl('/about'); + testUrl('/about#contact'); + testUrl('/test/nested'); + }); + + it('handles URLs relative to base host', function () { + let expectedUrl = ''; + + this.on('updateUrl', (url) => { + expect(url).to.equal(expectedUrl); + }); + + this.render(hbs ` + {{gh-navitem-url-input baseUrl=baseUrl url=url last=isLast change="updateUrl"}} + `); + let $input = this.$('input'); + + let testUrl = (url) => { + expectedUrl = url; + run(() => { + $input.val(url).trigger('input'); + }); + run(() => { + $input.trigger('blur'); + }); + }; + + testUrl(`http://${window.location.host}`); + testUrl(`https://${window.location.host}`); + testUrl(`http://${window.location.host}/`); + testUrl(`https://${window.location.host}/`); + testUrl(`http://${window.location.host}/test`); + testUrl(`https://${window.location.host}/test`); + testUrl(`http://${window.location.host}/#test`); + testUrl(`https://${window.location.host}/#test`); + testUrl(`http://${window.location.host}/another/folder`); + testUrl(`https://${window.location.host}/another/folder`); + }); + }); } );