apps/admin-x-design-system/src/utils/formatUrl.ts
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, '');
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: displayFromBase(url, baseUrl)};
};
const displayFromBase = (url: string, baseUrl: string) => {
// Ensure base url has a trailing slash
if (!baseUrl.endsWith('/')) {
baseUrl += '/';
}
// Remove leading slash from url
if (url.startsWith('/')) {
url = url.substring(1);
}
return new URL(url, baseUrl).toString();
};