@elastic/eui
Version:
Elastic UI Component Library
114 lines (110 loc) • 4.63 kB
JavaScript
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import visit from 'unist-util-visit';
export var DEFAULT_OPTIONS = {
allowRelative: true,
allowDocumentRelative: false,
allowProtocols: ['https:', 'http:', 'mailto:']
};
export function euiMarkdownLinkValidator(_ref) {
var _ref$allowRelative = _ref.allowRelative,
allowRelative = _ref$allowRelative === void 0 ? DEFAULT_OPTIONS.allowRelative : _ref$allowRelative,
_ref$allowDocumentRel = _ref.allowDocumentRelative,
allowDocumentRelative = _ref$allowDocumentRel === void 0 ? DEFAULT_OPTIONS.allowDocumentRelative : _ref$allowDocumentRel,
_ref$allowProtocols = _ref.allowProtocols,
allowProtocols = _ref$allowProtocols === void 0 ? DEFAULT_OPTIONS.allowProtocols : _ref$allowProtocols,
baseUrl = _ref.baseUrl;
return function (ast) {
visit(ast, 'link', function (_node) {
var node = _node;
if (allowRelative && allowDocumentRelative && (
// Prevent throwing in non-browser environments if baseUrl isn't configured
typeof window !== 'undefined' || baseUrl) && node.url && isDocumentRelativeUrl(node.url)) {
var newUrl = resolveDocumentRelativeUrl(node.url, baseUrl !== null && baseUrl !== void 0 ? baseUrl : window.location.href);
if (newUrl) {
node.url = newUrl;
} else {
mutateLinkToText(node);
}
} else if (!validateUrl(node.url, {
allowRelative: allowRelative,
allowProtocols: allowProtocols
})) {
mutateLinkToText(node);
}
});
};
}
export function mutateLinkToText(node) {
var _node$children, _node$url;
// this is an upsupported url, convert to a text node
node.type = 'text';
// and, if the link text matches the url there's only one value to show
// otherwise render as the markdown syntax so both text & url remain, unlinked
var linkText = ((_node$children = node.children) === null || _node$children === void 0 || (_node$children = _node$children[0]) === null || _node$children === void 0 ? void 0 : _node$children.value) || '';
var linkUrl = (_node$url = node.url) !== null && _node$url !== void 0 ? _node$url : '';
if (linkText === linkUrl) {
node.value = linkText;
} else {
node.value = "[".concat(linkText, "](").concat(node.url, ")");
}
delete node.children;
delete node.title;
delete node.url;
return node;
}
export function validateUrl(url, _ref2) {
var _ref2$allowRelative = _ref2.allowRelative,
allowRelative = _ref2$allowRelative === void 0 ? DEFAULT_OPTIONS.allowRelative : _ref2$allowRelative,
_ref2$allowProtocols = _ref2.allowProtocols,
allowProtocols = _ref2$allowProtocols === void 0 ? DEFAULT_OPTIONS.allowProtocols : _ref2$allowProtocols;
// relative captures both relative paths `/` and protocols `//`
var isRelative = url.startsWith('/');
if (isRelative) {
return allowRelative;
}
try {
var parsedUrl = new URL(url);
return allowProtocols.indexOf(parsedUrl.protocol) !== -1;
} catch (e) {
// failed to parse input as url
return false;
}
}
/**
* Tests whether a URL is a document relative URL (e.g. "discover", "dashboards#/view/123")
* that has no scheme, no leading slash, and is not an anchor or query-only link.
*/
export function isDocumentRelativeUrl(url) {
if (url.startsWith('/') || url.startsWith('#') || url.startsWith('?')) {
return false;
}
// Return false if the url starts with a protocol/scheme. Catches http:, https:, mailto:, etc.
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
return false;
}
return true;
}
/**
* Resolves a document relative URL against a base URL. This mostly replicates
* native browser resolution of e.g. `<a href="discover">`, except that
* trailing slashes are always removed to ensure consistent resolution,
* regardless of whether the current page URL ends with a trailing slash or not.
* Without this, "baz" on "/foo/bar/" resolves to "/foo/bar/baz" instead of the
* expected "/foo/baz".
*/
function resolveDocumentRelativeUrl(url, baseUrl) {
var normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
try {
var resolved = new URL(url, normalizedBase);
return "".concat(resolved.pathname).concat(resolved.search).concat(resolved.hash);
} catch (e) {
// failed to parse URL
return null;
}
}