-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
158 additions
and
92 deletions.
There are no files selected for viewing
92 changes: 1 addition & 91 deletions
92
apps/admin-x-design-system/src/global/form/URLTextField.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import isEmail from 'validator/es/lib/isEmail'; | ||
|
||
export const formatUrl = (value: string, baseUrl?: string, nullable?: boolean) => { | ||
if (nullable && !value) { | ||
return {save: null, display: ''}; | ||
} | ||
|
||
let url = value.trim(); | ||
|
||
if (!url) { | ||
if (baseUrl) { | ||
return {save: '/', display: baseUrl}; | ||
} | ||
return {save: '', display: ''}; | ||
} | ||
|
||
// if we have an email address, add the mailto: | ||
if (isEmail(url)) { | ||
return {save: `mailto:${url}`, display: `mailto:${url}`}; | ||
} | ||
|
||
const isAnchorLink = url.match(/^#/); | ||
if (isAnchorLink) { | ||
return {save: url, display: url}; | ||
} | ||
|
||
const isProtocolRelative = url.match(/^(\/\/)/); | ||
if (isProtocolRelative) { | ||
return {save: url, display: url}; | ||
} | ||
|
||
if (!baseUrl) { | ||
// Absolute URL with no base URL | ||
if (!url.startsWith('http')) { | ||
url = `https://${url}`; | ||
} | ||
} | ||
|
||
// If it doesn't look like a URL, leave it as is rather than assuming it's a pathname etc | ||
if (!url.match(/^[a-zA-Z0-9-]+:/) && !url.match(/^(\/|\?)/)) { | ||
return {save: url, display: url}; | ||
} | ||
|
||
let parsedUrl: URL; | ||
|
||
try { | ||
parsedUrl = new URL(url, baseUrl); | ||
} catch (e) { | ||
return {save: url, display: url}; | ||
} | ||
|
||
if (!baseUrl) { | ||
return {save: parsedUrl.toString(), display: parsedUrl.toString()}; | ||
} | ||
const parsedBaseUrl = new URL(baseUrl); | ||
|
||
let isRelativeToBasePath = parsedUrl.pathname && parsedUrl.pathname.indexOf(parsedBaseUrl.pathname) === 0; | ||
|
||
// if our path is only missing a trailing / mark it as relative | ||
if (`${parsedUrl.pathname}/` === parsedBaseUrl.pathname) { | ||
isRelativeToBasePath = true; | ||
} | ||
|
||
const isOnSameHost = parsedUrl.host === parsedBaseUrl.host; | ||
|
||
// if relative to baseUrl, remove the base url before sending to action | ||
if (isOnSameHost && isRelativeToBasePath) { | ||
url = url.replace(/^[a-zA-Z0-9-]+:/, ''); | ||
url = url.replace(/^\/\//, ''); | ||
url = url.replace(parsedBaseUrl.host, ''); | ||
url = url.replace(parsedBaseUrl.pathname, ''); | ||
|
||
// handle case where url path is same as baseUrl path but missing trailing slash | ||
if (parsedUrl.pathname.slice(-1) !== '/') { | ||
url = url.replace(parsedBaseUrl.pathname.slice(0, -1), ''); | ||
} | ||
|
||
if (!url.match(/^\//)) { | ||
url = `/${url}`; | ||
} | ||
|
||
if (!url.match(/\/$/) && !url.match(/[.#?]/)) { | ||
url = `${url}/`; | ||
} | ||
} | ||
|
||
// we update with the relative URL but then transform it back to absolute | ||
// for the input value. This avoids problems where the underlying relative | ||
// value hasn't changed even though the input value has | ||
return {save: url, display: new URL(url, baseUrl).toString()}; | ||
}; |
64 changes: 64 additions & 0 deletions
64
apps/admin-x-design-system/test/unit/utils/formatUrl.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import * as assert from 'assert/strict'; | ||
import {formatUrl} from '../../../src/utils/formatUrl'; | ||
|
||
describe.only('formatUrl', function () { | ||
it('displays empty string if the input is empty and nullable is true', function () { | ||
const formattedUrl = formatUrl('', undefined, true); | ||
assert.deepEqual(formattedUrl, {save: null, display: ''}); | ||
}); | ||
|
||
it('displays empty string value if the input has only whitespace', function () { | ||
const formattedUrl = formatUrl(''); | ||
assert.deepEqual(formattedUrl, {save: '', display: ''}); | ||
}); | ||
|
||
it('displays base value if the input has only whitespace and base url is available', function () { | ||
const formattedUrl = formatUrl('', 'http://example.com'); | ||
assert.deepEqual(formattedUrl, {save: '/', display: 'http://example.com'}); | ||
}); | ||
|
||
it('displays a mailto address for an email address', function () { | ||
const formattedUrl = formatUrl('test@example.com'); | ||
assert.deepEqual(formattedUrl, {save: 'mailto:test@example.com', display: 'mailto:test@example.com'}); | ||
}); | ||
|
||
it('displays an anchor link without formatting', function () { | ||
const formattedUrl = formatUrl('#section'); | ||
assert.deepEqual(formattedUrl, {save: '#section', display: '#section'}); | ||
}); | ||
|
||
it('adds https:// automatically', function () { | ||
const formattedUrl = formatUrl('example.com'); | ||
assert.deepEqual(formattedUrl, {save: 'https://example.com/', display: 'https://example.com/'}); | ||
}); | ||
|
||
it('adds a trailing slash / automatically', function () { | ||
const formattedUrl = formatUrl('https://example.com'); | ||
assert.deepEqual(formattedUrl, {save: 'https://example.com/', display: 'https://example.com/'}); | ||
}); | ||
|
||
it('saves a relative URL is the input is a pathname', function () { | ||
const formattedUrl = formatUrl('/path', 'http://example.com'); | ||
assert.deepEqual(formattedUrl, {save: '/path/', display: 'http://example.com/path/'}); | ||
}); | ||
|
||
it('saves a relative URL is the input is a pathname, even if the base url has an non-empty pathname', function () { | ||
const formattedUrl = formatUrl('/path', 'http://example.com/blog'); | ||
assert.deepEqual(formattedUrl, {save: '/path/', display: 'http://example.com/blog/path/'}); | ||
}); | ||
|
||
it('saves a relative URL is the input includes the base url', function () { | ||
const formattedUrl = formatUrl('http://example.com/path', 'http://example.com'); | ||
assert.deepEqual(formattedUrl, {save: '/path/', display: 'http://example.com/path/'}); | ||
}); | ||
|
||
it('saves an absolute URL is the input has a different pathname to the base url', function () { | ||
const formattedUrl = formatUrl('http://example.com/path', 'http://example.com/blog'); | ||
assert.deepEqual(formattedUrl, {save: 'http://example.com/path', display: 'http://example.com/path'}); | ||
}); | ||
|
||
it('saves an absolte URL if the input has a different hostname to the base url', function () { | ||
const formattedUrl = formatUrl('http://another.com/path', 'http://example.com'); | ||
assert.deepEqual(formattedUrl, {save: 'http://another.com/path', display: 'http://another.com/path'}); | ||
}); | ||
}); |