79 lines
2.3 KiB
JavaScript
79 lines
2.3 KiB
JavaScript
const METHOD_PREFIX = /^(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+/i;
|
|
|
|
function normalizeLink(link, baseUrl = "", verifiedRoutes = [], options = {}) {
|
|
if (!link || typeof link.href !== "string") return null;
|
|
const routeSet = verifiedRoutes instanceof Set ? verifiedRoutes : new Set(verifiedRoutes);
|
|
const href = decodeHtmlEntities(link.href).trim().replace(METHOD_PREFIX, "");
|
|
const label = stripTags(String(link.label || routeLabel(href))).trim();
|
|
if (!href || !label) return null;
|
|
|
|
if (href.startsWith("/")) {
|
|
const pathname = routePath(href);
|
|
if (!routeSet.has(pathname)) return null;
|
|
return { href: absoluteInternalUrl(href, baseUrl), label, internal: true };
|
|
}
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(href);
|
|
} catch {
|
|
return null;
|
|
}
|
|
if (!["http:", "https:"].includes(parsed.protocol)) return null;
|
|
|
|
const base = safeBaseUrl(baseUrl);
|
|
if (base && parsed.origin === base.origin) {
|
|
if (!routeSet.has(parsed.pathname)) return null;
|
|
return { href: parsed.href, label, internal: true };
|
|
}
|
|
if (options.allowExternal === false || link.external === false) return null;
|
|
return { href: parsed.href, label, internal: false };
|
|
}
|
|
|
|
function absoluteInternalUrl(route, baseUrl) {
|
|
const base = safeBaseUrl(baseUrl);
|
|
return base ? new URL(route, base).href : route;
|
|
}
|
|
|
|
function safeBaseUrl(value) {
|
|
if (!value) return null;
|
|
try {
|
|
const parsed = new URL(value);
|
|
return ["http:", "https:"].includes(parsed.protocol) ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function routePath(value) {
|
|
return String(value || "").split(/[?#]/, 1)[0];
|
|
}
|
|
|
|
function routeLabel(route) {
|
|
const pathname = routePath(String(route || "").replace(METHOD_PREFIX, ""));
|
|
const segment = pathname.split("/").filter(Boolean).pop() || "Lumi page";
|
|
return segment.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
}
|
|
|
|
function stripTags(value) {
|
|
return String(value || "").replace(/<[^>]*>/g, "");
|
|
}
|
|
|
|
function decodeHtmlEntities(value) {
|
|
return String(value || "")
|
|
.replace(/&/gi, "&")
|
|
.replace(/"/gi, "\"")
|
|
.replace(/'|'/gi, "'")
|
|
.replace(/</gi, "<")
|
|
.replace(/>/gi, ">");
|
|
}
|
|
|
|
module.exports = {
|
|
METHOD_PREFIX,
|
|
normalizeLink,
|
|
absoluteInternalUrl,
|
|
safeBaseUrl,
|
|
routeLabel,
|
|
stripTags
|
|
};
|