@paroicms/server
Version:
The ParoiCMS server
445 lines • 15.9 kB
JavaScript
import { getRegularDocumentTypeByName, getRoutingDocumentTypeByName, } from "@paroicms/internal-anywhere-lib";
import { shouldPrefixWithLanguage } from "../../helpers/url-helpers.js";
export function parsePageRoute(siteContext, urlPath) {
const { siteSchema } = siteContext;
const { path } = parseUrlParameters(urlPath);
const tokens = path
.split("/")
.slice(1)
.filter((token) => token.length > 0);
const languageResult = parseLanguageFromUrl(siteSchema, tokens);
if (!languageResult)
return;
const { language, urlSegments } = languageResult;
const urlCacheKey = [];
if (shouldPrefixWithLanguage(siteSchema, language))
urlCacheKey.push(language);
const hierarchyResult = parseDocumentHierarchy(siteSchema, urlSegments, language, urlCacheKey);
if (!hierarchyResult)
return;
return {
language,
redirectTo: hierarchyResult.redirectTo,
frontendAppPath: hierarchyResult.frontendAppPath,
urlCacheKey: urlCacheKey.join("/"),
documentHierarchy: hierarchyResult.documentHierarchy,
};
}
function parseUrlParameters(urlPath) {
return {
path: urlPath,
};
}
function parseLanguageFromUrl(siteSchema, tokens) {
let language;
let urlSegments;
if (siteSchema.languageRoutingMode === "prefixAll") {
language = tokens[0];
urlSegments = tokens.slice(1);
}
else if (siteSchema.languageRoutingMode === "prefixSecondary") {
if (tokens.length > 0 &&
siteSchema.languages.includes(tokens[0]) &&
tokens[0] !== siteSchema.defaultLanguage) {
language = tokens[0];
urlSegments = tokens.slice(1);
}
else {
language = siteSchema.defaultLanguage;
urlSegments = tokens;
}
}
else
throw new Error(`invalid routing mode "${siteSchema.languageRoutingMode}"`);
if (!language || !siteSchema.languages.includes(language))
return;
return { language, urlSegments };
}
function parseDocumentHierarchy(siteSchema, urlSegments, language, urlCacheKey) {
const documentHierarchy = [];
const homeDocument = siteSchema.nodeTypes.home;
documentHierarchy.push({
entryKind: "routing",
documentType: homeDocument,
});
const homeFrontendAppResult = checkFrontendAppWithRoutingPrecedence(homeDocument, urlSegments, 0, siteSchema, language);
if (homeFrontendAppResult.shouldUseFrontendApp) {
return {
documentHierarchy,
redirectTo: homeDocument.redirectTo,
frontendAppPath: `/${urlSegments.join("/")}`,
};
}
return parseDocumentHierarchyRecursive({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
currentParent: homeDocument,
index: 0,
});
}
function parseDocumentHierarchyRecursive({ siteSchema, urlSegments, language, urlCacheKey, documentHierarchy, currentParent, index, }) {
const routingResult = parseRoutingDocuments({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
currentParent,
index,
});
if (routingResult.frontendAppPath || routingResult.index >= urlSegments.length) {
return {
documentHierarchy,
redirectTo: routingResult.redirectTo,
frontendAppPath: routingResult.frontendAppPath,
};
}
return parseRegularDocuments({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
parentCandidates: [routingResult.currentParent],
index: routingResult.index,
redirectTo: routingResult.redirectTo,
});
}
function parseRoutingDocuments({ siteSchema, urlSegments, language, urlCacheKey, documentHierarchy, currentParent, index, }) {
let currentIndex = index;
let parent = currentParent;
while (currentIndex < urlSegments.length) {
const urlSegment = urlSegments[currentIndex];
const routingDocument = findRoutingDocumentByUrlPath({
siteSchema,
parent,
urlSegment,
language,
});
if (!routingDocument)
break;
documentHierarchy.push({
entryKind: "routing",
documentType: routingDocument,
});
urlCacheKey.push(urlSegment);
parent = routingDocument;
currentIndex++;
const frontendAppResult = checkFrontendAppWithRoutingPrecedence(routingDocument, urlSegments, currentIndex, siteSchema, language);
if (frontendAppResult.shouldUseFrontendApp) {
return {
currentParent: parent,
index: currentIndex,
redirectTo: routingDocument.redirectTo,
frontendAppPath: `/${urlSegments.slice(currentIndex).join("/")}`,
};
}
}
return {
currentParent: parent,
index: currentIndex,
redirectTo: parent.redirectTo,
frontendAppPath: undefined,
};
}
function parseRegularDocuments({ siteSchema, urlSegments, language, urlCacheKey, documentHierarchy, parentCandidates, index, redirectTo, }) {
let currentIndex = index;
let candidates = parentCandidates;
let lastRedirectTo = redirectTo;
if (currentIndex >= urlSegments.length) {
return {
documentHierarchy,
redirectTo: lastRedirectTo,
frontendAppPath: undefined,
};
}
while (currentIndex < urlSegments.length) {
const found = findRegularDocumentByUrlPath({
siteSchema,
parentCandidates: candidates,
urlSegments,
index: currentIndex,
});
if (!found)
return;
const isClusterRoot = found.candidateTypes.some((type) => (type.routingChildren?.length ?? 0) > 0);
const lookupFields = found.lookupKind === "relativeId"
? { lookupKind: found.lookupKind, relativeId: found.relativeId }
: { lookupKind: found.lookupKind, slug: found.slug };
documentHierarchy.push({
entryKind: "regular",
...lookupFields,
candidateTypes: found.candidateTypes,
isClusterRoot,
});
urlCacheKey.push(found.lookupKind === "relativeId" ? `regular:${found.relativeId}` : `slug:${found.slug}`);
lastRedirectTo = undefined;
currentIndex += found.skipUrlSegments;
candidates = found.candidateTypes;
const frontendAppResult = checkRegularDocumentFrontendApp({
candidateTypes: found.candidateTypes,
urlSegments,
index: currentIndex,
siteSchema,
language,
});
if (frontendAppResult.shouldUseFrontendApp) {
return {
documentHierarchy,
redirectTo: lastRedirectTo,
frontendAppPath: `/${urlSegments.slice(currentIndex).join("/")}`,
};
}
if (isClusterRoot && currentIndex < urlSegments.length) {
const clusterResult = parseClusterDocuments({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
regularCandidates: found.candidateTypes,
index: currentIndex,
});
return clusterResult;
}
}
return {
documentHierarchy,
redirectTo: lastRedirectTo,
frontendAppPath: undefined,
};
}
function parseClusterDocuments({ siteSchema, urlSegments, language, urlCacheKey, documentHierarchy, regularCandidates, index, }) {
let currentIndex = index;
let lastRedirectTo;
while (currentIndex < urlSegments.length) {
const urlSegment = urlSegments[currentIndex];
let routingDocument;
for (const candidateType of regularCandidates) {
if (!candidateType.routingChildren)
continue;
routingDocument = findRoutingDocumentByUrlPath({
siteSchema,
parent: candidateType,
urlSegment,
language,
});
if (routingDocument)
break;
}
if (!routingDocument) {
const hasRegularChildren = regularCandidates.some((type) => type.regularChildren?.length);
if (hasRegularChildren) {
return parseRegularDocuments({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
parentCandidates: regularCandidates,
index: currentIndex,
redirectTo: lastRedirectTo,
});
}
return {
documentHierarchy,
redirectTo: lastRedirectTo,
frontendAppPath: undefined,
};
}
documentHierarchy.push({
entryKind: "routing",
documentType: routingDocument,
});
urlCacheKey.push(urlSegment);
currentIndex++;
lastRedirectTo = routingDocument.redirectTo;
const frontendAppResult = checkFrontendAppWithRoutingPrecedence(routingDocument, urlSegments, currentIndex, siteSchema, language);
if (frontendAppResult.shouldUseFrontendApp) {
return {
documentHierarchy,
redirectTo: routingDocument.redirectTo,
frontendAppPath: `/${urlSegments.slice(currentIndex).join("/")}`,
};
}
if (routingDocument.regularChildren && currentIndex < urlSegments.length) {
return parseDocumentHierarchyRecursive({
siteSchema,
urlSegments,
language,
urlCacheKey,
documentHierarchy,
currentParent: routingDocument,
index: currentIndex,
});
}
}
return {
documentHierarchy,
redirectTo: lastRedirectTo,
frontendAppPath: undefined,
};
}
function checkRegularDocumentFrontendApp({ candidateTypes, urlSegments, index, siteSchema, language, }) {
const hasFrontendApp = candidateTypes.some((type) => type.hasFrontendApp);
if (!hasFrontendApp) {
return { shouldUseFrontendApp: false };
}
const hasRoutingPrecedence = index < urlSegments.length &&
candidateTypes.some((type) => {
if (!type.routingChildren?.length)
return false;
const nextUrlSegment = urlSegments[index];
return type.routingChildren.some((childTypeName) => {
const childType = getRoutingDocumentTypeByName(siteSchema, childTypeName);
return childType.route && nextUrlSegment === childType.route[language];
});
});
return { shouldUseFrontendApp: !hasRoutingPrecedence };
}
function checkFrontendAppWithRoutingPrecedence(document, urlSegments, currentIndex, siteSchema, language) {
if (!document.hasFrontendApp) {
return { shouldUseFrontendApp: false };
}
if (currentIndex >= urlSegments.length) {
return { shouldUseFrontendApp: true };
}
const nextUrlSegment = urlSegments[currentIndex];
const hasRoutingMatch = (document.routingChildren ?? []).some((childTypeName) => {
const childType = getRoutingDocumentTypeByName(siteSchema, childTypeName);
return childType.route && nextUrlSegment === childType.route[language];
});
if (hasRoutingMatch) {
return { shouldUseFrontendApp: false };
}
return { shouldUseFrontendApp: true };
}
function findRoutingDocumentByUrlPath({ siteSchema, parent, urlSegment, language, }) {
for (const typeName of parent.routingChildren ?? []) {
const documentType = getRoutingDocumentTypeByName(siteSchema, typeName);
if (!documentType.route)
continue;
if (urlSegment === documentType.route[language])
return documentType;
}
}
function findRegularDocumentByUrlPath({ siteSchema, parentCandidates, urlSegments, index: partsIndex, }) {
const candidateTypes = parentCandidates
.reduce((childTypeNames, parent) => {
if (parent.regularChildren)
childTypeNames.push(...parent.regularChildren);
return childTypeNames;
}, [])
?.map((typeName) => getRegularDocumentTypeByName(siteSchema, typeName));
if (!candidateTypes || candidateTypes.length === 0)
return;
const route = candidateTypes[0].route;
if (route === ":relativeId-:slug") {
const urlPath = urlSegments[partsIndex];
const relativeId = parseRegularRelativeIdSlug(urlPath);
return {
lookupKind: "relativeId",
relativeId,
candidateTypes,
skipUrlSegments: 1,
};
}
if (route === ":yyyy/:mm/:dd/:relativeId-:slug") {
if (urlSegments.length < partsIndex + 3 ||
!isYear(urlSegments[partsIndex]) ||
!isMonth(urlSegments[partsIndex + 1]) ||
!isDay(urlSegments[partsIndex + 2])) {
return;
}
const urlPath = urlSegments[partsIndex + 3];
const relativeId = parseRegularRelativeIdSlug(urlPath);
return {
lookupKind: "relativeId",
relativeId,
candidateTypes,
skipUrlSegments: 4,
};
}
if (route === ":slug") {
const slug = urlSegments[partsIndex];
return {
lookupKind: "slug",
slug,
candidateTypes,
skipUrlSegments: 1,
};
}
if (route === ":relativeId") {
const relativeId = urlSegments[partsIndex];
return {
lookupKind: "relativeId",
relativeId,
candidateTypes,
skipUrlSegments: 1,
};
}
}
function parseRegularRelativeIdSlug(urlPath) {
const index = urlPath.indexOf("-");
return index === -1 ? urlPath : urlPath.substring(0, index);
}
function isYear(s) {
return /[0-9]{4}/.test(s);
}
function isMonth(s) {
if (!/[0-9]{2}/.test(s))
return false;
const val = Number(s);
return val >= 1 && val <= 12;
}
function isDay(s) {
if (!/[0-9]{2}/.test(s))
return false;
const val = Number(s);
return val >= 1 && val <= 31;
}
export function findParentParsedRouteWithDocument(parsedRoute) {
let curRoute = parsedRoute;
while (true) {
curRoute = findParentParsedRoute(curRoute);
if (!curRoute)
break;
if (!curRoute.redirectTo)
return curRoute;
}
}
function findParentParsedRoute(parsedRoute) {
const documentHierarchy = [...parsedRoute.documentHierarchy];
const urlCacheKey = parsedRoute.urlCacheKey.split("/");
if (documentHierarchy.length <= 1)
return;
const lastEntry = documentHierarchy.pop();
if (!lastEntry)
return;
if (lastEntry.entryKind === "regular") {
const expectedKey = lastEntry.lookupKind === "relativeId"
? `regular:${lastEntry.relativeId}`
: `slug:${lastEntry.slug}`;
if (urlCacheKey[urlCacheKey.length - 1] === expectedKey) {
urlCacheKey.pop();
}
}
else {
urlCacheKey.pop();
}
const lastRemainingEntry = documentHierarchy[documentHierarchy.length - 1];
const redirectTo = lastRemainingEntry?.entryKind === "routing"
? lastRemainingEntry.documentType.redirectTo
: undefined;
return {
language: parsedRoute.language,
documentHierarchy,
redirectTo,
urlCacheKey: urlCacheKey.join("/"),
frontendAppPath: undefined,
};
}
//# sourceMappingURL=page-route-parser.js.map