@roottale/cms-core 0.65.1 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +354 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +568 -4
- package/dist/index.d.ts +568 -4
- package/dist/index.js +336 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -225,39 +225,51 @@ function validateCollections(collections) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/category-tree.ts
|
|
228
|
-
function
|
|
228
|
+
function basePathPrefix(basePath) {
|
|
229
|
+
if (!basePath.trim()) return null;
|
|
229
230
|
return basePath.replace(/\/+$/, "");
|
|
230
231
|
}
|
|
231
|
-
function
|
|
232
|
-
if (!Number.isInteger(
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
232
|
+
function categoryTreeAncestry(categoryDepth, termId, terms) {
|
|
233
|
+
if (!Number.isInteger(categoryDepth) || categoryDepth < 1) return null;
|
|
235
234
|
const termsById = new Map(terms.map((term) => [term.id, term]));
|
|
236
235
|
if (termsById.size !== terms.length) return null;
|
|
237
|
-
if (terms.some((term) => term.parentId === leafId)) return null;
|
|
238
236
|
const reversed = [];
|
|
239
237
|
const visited = /* @__PURE__ */ new Set();
|
|
240
|
-
let currentId =
|
|
238
|
+
let currentId = termId;
|
|
241
239
|
while (currentId) {
|
|
242
240
|
if (visited.has(currentId)) return null;
|
|
243
241
|
visited.add(currentId);
|
|
244
242
|
const current = termsById.get(currentId);
|
|
245
243
|
if (!current || !current.slug) return null;
|
|
246
244
|
reversed.push(current);
|
|
245
|
+
if (reversed.length > categoryDepth) return null;
|
|
247
246
|
currentId = current.parentId ?? null;
|
|
248
247
|
}
|
|
249
|
-
if (reversed.length !== presentation.categoryDepth) return null;
|
|
250
248
|
return reversed.reverse();
|
|
251
249
|
}
|
|
250
|
+
function hasChildCategory(termId, terms) {
|
|
251
|
+
return terms.some((term) => term.parentId === termId);
|
|
252
|
+
}
|
|
253
|
+
function resolveCategoryTreeChain(presentation, termId, terms) {
|
|
254
|
+
const chain = categoryTreeAncestry(presentation.categoryDepth, termId, terms);
|
|
255
|
+
if (!chain) return null;
|
|
256
|
+
if (presentation.entryCategory === "leaf") return chain;
|
|
257
|
+
if (hasChildCategory(termId, terms)) return null;
|
|
258
|
+
return chain.length === presentation.categoryDepth ? chain : null;
|
|
259
|
+
}
|
|
260
|
+
function resolveCategoryTreeSelection(presentation, termId, terms) {
|
|
261
|
+
if (hasChildCategory(termId, terms)) return null;
|
|
262
|
+
return resolveCategoryTreeChain(presentation, termId, terms);
|
|
263
|
+
}
|
|
252
264
|
function resolveCategoryTreeHubPaths(presentation, termId, terms) {
|
|
253
265
|
const chain = resolveCategoryTreeChain(presentation, termId, terms);
|
|
254
|
-
const
|
|
255
|
-
if (!chain ||
|
|
256
|
-
const paths = [
|
|
266
|
+
const prefix = basePathPrefix(presentation.basePath);
|
|
267
|
+
if (!chain || prefix === null) return null;
|
|
268
|
+
const paths = [prefix || "/"];
|
|
257
269
|
const segments = [];
|
|
258
270
|
for (const term of chain) {
|
|
259
271
|
segments.push(encodeURIComponent(term.slug));
|
|
260
|
-
paths.push(`${
|
|
272
|
+
paths.push(`${prefix}/${segments.join("/")}`);
|
|
261
273
|
}
|
|
262
274
|
return paths;
|
|
263
275
|
}
|
|
@@ -961,10 +973,10 @@ function scopeImportedCss(css, scope, allowedAssetUrls = []) {
|
|
|
961
973
|
|
|
962
974
|
// src/internal-content-links.ts
|
|
963
975
|
var INTERNAL_CONTENT_LINK_TOKEN_SOURCE = String.raw`\[\[internal:([a-z0-9가-힣]+(?:[.-][a-z0-9가-힣]+)*)\|([^\]\n|]+)\]\]`;
|
|
964
|
-
function findInternalContentLinkTokens(
|
|
976
|
+
function findInternalContentLinkTokens(text2) {
|
|
965
977
|
const pattern = new RegExp(INTERNAL_CONTENT_LINK_TOKEN_SOURCE, "giu");
|
|
966
978
|
const tokens = [];
|
|
967
|
-
for (const match of
|
|
979
|
+
for (const match of text2.matchAll(pattern)) {
|
|
968
980
|
const [raw, key, label] = match;
|
|
969
981
|
if (!key || !label || match.index === void 0) continue;
|
|
970
982
|
tokens.push({
|
|
@@ -1023,19 +1035,19 @@ function escapeHtmlText(value) {
|
|
|
1023
1035
|
var INTERNAL_CONTENT_LINK_CLASS = "rt-internal-link";
|
|
1024
1036
|
var INTERNAL_CONTENT_LINK_PENDING_CLASS = "rt-internal-link--pending";
|
|
1025
1037
|
var INTERNAL_CONTENT_LINK_PENDING_ATTR = "data-rt-internal-link-pending";
|
|
1026
|
-
function renderInternalContentLinksInText(
|
|
1027
|
-
const tokens = findInternalContentLinkTokens(
|
|
1028
|
-
if (tokens.length === 0) return { html:
|
|
1038
|
+
function renderInternalContentLinksInText(text2, resolve) {
|
|
1039
|
+
const tokens = findInternalContentLinkTokens(text2);
|
|
1040
|
+
if (tokens.length === 0) return { html: text2, hasTokens: false };
|
|
1029
1041
|
let cursor = 0;
|
|
1030
1042
|
let html = "";
|
|
1031
1043
|
for (const token of tokens) {
|
|
1032
|
-
html +=
|
|
1044
|
+
html += text2.slice(cursor, token.index);
|
|
1033
1045
|
const path = resolve(token.key);
|
|
1034
1046
|
const label = escapeHtmlText(token.label);
|
|
1035
1047
|
html += isSafeInternalPath(path) ? `<a href="${escapeHtmlText(path)}" class="${INTERNAL_CONTENT_LINK_CLASS}">${label}</a>` : `<span class="${INTERNAL_CONTENT_LINK_CLASS} ${INTERNAL_CONTENT_LINK_PENDING_CLASS}" ${INTERNAL_CONTENT_LINK_PENDING_ATTR}="${escapeHtmlText(token.key)}">${label}</span>`;
|
|
1036
1048
|
cursor = token.index + token.raw.length;
|
|
1037
1049
|
}
|
|
1038
|
-
html +=
|
|
1050
|
+
html += text2.slice(cursor);
|
|
1039
1051
|
return { html, hasTokens: true };
|
|
1040
1052
|
}
|
|
1041
1053
|
var INTERNAL_LINK_BLOCKED_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
@@ -1069,12 +1081,12 @@ function renderInternalContentLinksInHtml(html, resolve) {
|
|
|
1069
1081
|
let cursor = 0;
|
|
1070
1082
|
let out = "";
|
|
1071
1083
|
let hasTokens = false;
|
|
1072
|
-
const emitText = (
|
|
1073
|
-
if (blockedDepth > 0 || !
|
|
1074
|
-
out +=
|
|
1084
|
+
const emitText = (text2) => {
|
|
1085
|
+
if (blockedDepth > 0 || !text2) {
|
|
1086
|
+
out += text2;
|
|
1075
1087
|
return;
|
|
1076
1088
|
}
|
|
1077
|
-
const rendered = renderInternalContentLinksInText(
|
|
1089
|
+
const rendered = renderInternalContentLinksInText(text2, resolve);
|
|
1078
1090
|
hasTokens ||= rendered.hasTokens;
|
|
1079
1091
|
out += rendered.html;
|
|
1080
1092
|
};
|
|
@@ -1213,8 +1225,8 @@ function fromCodePointSafe2(n) {
|
|
|
1213
1225
|
}
|
|
1214
1226
|
}
|
|
1215
1227
|
function decodeUrlProbe(value) {
|
|
1216
|
-
const
|
|
1217
|
-
return [...
|
|
1228
|
+
const decoded2 = value.replace(/&#x([0-9a-f]+);?/gi, (_, h) => fromCodePointSafe2(parseInt(h, 16))).replace(/&#(\d+);?/g, (_, d) => fromCodePointSafe2(parseInt(d, 10))).replace(/&([a-z]+);/gi, (m, name) => NAMED_ENTITY[name.toLowerCase()] ?? m);
|
|
1229
|
+
return [...decoded2].filter((c) => {
|
|
1218
1230
|
const n = c.codePointAt(0) ?? 0;
|
|
1219
1231
|
return n > 32 && n !== 127;
|
|
1220
1232
|
}).join("").toLowerCase();
|
|
@@ -1838,6 +1850,287 @@ function formatSectionLockViolations(violations) {
|
|
|
1838
1850
|
return `${label} \uAD6C\uC870 \uBCC0\uACBD(${violation.field})`;
|
|
1839
1851
|
}).join(" / ");
|
|
1840
1852
|
}
|
|
1853
|
+
|
|
1854
|
+
// src/metadata-resolver.ts
|
|
1855
|
+
function text(value) {
|
|
1856
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
function imageUrl(value, base) {
|
|
1859
|
+
const candidate = text(value);
|
|
1860
|
+
if (!candidate || candidate.startsWith("//") || /[\\\u0000-\u0020\u007f]/u.test(candidate)) return void 0;
|
|
1861
|
+
if (candidate.startsWith("/") && !base) return candidate;
|
|
1862
|
+
try {
|
|
1863
|
+
const url = new URL(candidate, base);
|
|
1864
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return void 0;
|
|
1865
|
+
return url.href;
|
|
1866
|
+
} catch {
|
|
1867
|
+
return void 0;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
function resolveMetadata(input) {
|
|
1871
|
+
const layers = [input.overrides, input.defaults, input.siteDefaults, input.fallback];
|
|
1872
|
+
const result = {};
|
|
1873
|
+
for (const field of ["title", "description", "ogImageAlt"]) {
|
|
1874
|
+
for (const layer of layers) {
|
|
1875
|
+
const value = text(layer?.[field]);
|
|
1876
|
+
if (value !== void 0) {
|
|
1877
|
+
result[field] = value;
|
|
1878
|
+
break;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
for (const layer of layers) {
|
|
1883
|
+
const value = imageUrl(layer?.ogImage, input.imageBaseUrl);
|
|
1884
|
+
if (value !== void 0) {
|
|
1885
|
+
result.ogImage = value;
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
for (const field of ["noindex", "nofollow"]) {
|
|
1890
|
+
for (const layer of layers) {
|
|
1891
|
+
const value = layer?.[field];
|
|
1892
|
+
if (typeof value === "boolean") {
|
|
1893
|
+
result[field] = value;
|
|
1894
|
+
break;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (input.robots?.forceNoindex) result.noindex = true;
|
|
1899
|
+
if (input.robots?.forceNofollow) result.nofollow = true;
|
|
1900
|
+
return result;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/metadata-contract.ts
|
|
1904
|
+
import { z as z2 } from "zod";
|
|
1905
|
+
var METADATA_FIELDS = ["title", "description", "ogImage", "ogImageAlt", "noindex", "nofollow"];
|
|
1906
|
+
var MetadataFieldSchema = z2.enum(METADATA_FIELDS);
|
|
1907
|
+
var MetadataTargetKindSchema = z2.enum(["route", "post", "term"]);
|
|
1908
|
+
var textValue = (max) => z2.string().trim().min(1).max(max);
|
|
1909
|
+
var imageValue = textValue(2048).refine((value) => {
|
|
1910
|
+
if (/[\\\u0000-\u0020\u007f]/u.test(value) || value.startsWith("//")) return false;
|
|
1911
|
+
if (value.startsWith("/")) return true;
|
|
1912
|
+
try {
|
|
1913
|
+
const url = new URL(value);
|
|
1914
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
|
|
1915
|
+
} catch {
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
}, "\uACF5\uC720 \uC774\uBBF8\uC9C0\uC5D0\uB294 \uC0AC\uC774\uD2B8 \uB0B4\uBD80 \uACBD\uB85C\uB098 HTTP/HTTPS \uC8FC\uC18C\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694");
|
|
1919
|
+
var metadataFields = {
|
|
1920
|
+
// Existing content titles (500) plus a site title template (400) remain valid automatic values.
|
|
1921
|
+
title: textValue(1e3),
|
|
1922
|
+
description: textValue(2e3),
|
|
1923
|
+
ogImage: imageValue,
|
|
1924
|
+
// Automatic alt text may use the complete automatic title and its site suffix.
|
|
1925
|
+
ogImageAlt: textValue(1e3),
|
|
1926
|
+
noindex: z2.boolean(),
|
|
1927
|
+
nofollow: z2.boolean()
|
|
1928
|
+
};
|
|
1929
|
+
var MetadataValuesSchema = z2.object(metadataFields).partial().strict();
|
|
1930
|
+
var MetadataPatchSchema = z2.object({
|
|
1931
|
+
title: textValue(300).nullable().optional(),
|
|
1932
|
+
description: metadataFields.description.nullable().optional(),
|
|
1933
|
+
ogImage: metadataFields.ogImage.nullable().optional(),
|
|
1934
|
+
ogImageAlt: textValue(500).nullable().optional(),
|
|
1935
|
+
noindex: metadataFields.noindex.nullable().optional(),
|
|
1936
|
+
nofollow: metadataFields.nofollow.nullable().optional()
|
|
1937
|
+
}).strict();
|
|
1938
|
+
var MetadataRobotsPolicySchema = z2.object({
|
|
1939
|
+
forceNoindex: z2.boolean().optional(),
|
|
1940
|
+
forceNofollow: z2.boolean().optional()
|
|
1941
|
+
}).strict();
|
|
1942
|
+
function readMetadataValues(input) {
|
|
1943
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
1944
|
+
const record = input;
|
|
1945
|
+
const output = {};
|
|
1946
|
+
for (const field of METADATA_FIELDS) {
|
|
1947
|
+
const parsed = metadataFields[field].safeParse(record[field]);
|
|
1948
|
+
if (parsed.success) output[field] = parsed.data;
|
|
1949
|
+
}
|
|
1950
|
+
return MetadataValuesSchema.parse(output);
|
|
1951
|
+
}
|
|
1952
|
+
function mergeMetadataPatch(current, input) {
|
|
1953
|
+
const patch = MetadataPatchSchema.parse(input);
|
|
1954
|
+
const next = { ...current };
|
|
1955
|
+
for (const field of METADATA_FIELDS) {
|
|
1956
|
+
if (patch[field] === null) delete next[field];
|
|
1957
|
+
else if (patch[field] !== void 0) next[field] = patch[field];
|
|
1958
|
+
}
|
|
1959
|
+
return next;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// src/metadata-defaults.ts
|
|
1963
|
+
import { z as z3 } from "zod";
|
|
1964
|
+
var fallbackText = z3.string().max(2e3);
|
|
1965
|
+
var imageOrigin = z3.string().max(2048).refine((value) => {
|
|
1966
|
+
try {
|
|
1967
|
+
const url = new URL(value);
|
|
1968
|
+
return url.protocol === "https:" && url.origin === value;
|
|
1969
|
+
} catch {
|
|
1970
|
+
return false;
|
|
1971
|
+
}
|
|
1972
|
+
}, "\uC774\uBBF8\uC9C0 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0\uB294 \uACBD\uB85C \uC5C6\uB294 HTTPS origin\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4");
|
|
1973
|
+
var MetadataTextRuleSchema = z3.object({
|
|
1974
|
+
source: z3.enum(["content", "fallback"]).optional(),
|
|
1975
|
+
stripLeadingLabel: z3.enum(["question", "answer"]).optional(),
|
|
1976
|
+
replaceLineBreakTokens: z3.boolean().optional(),
|
|
1977
|
+
removeEmDashes: z3.boolean().optional(),
|
|
1978
|
+
normalizeWhitespace: z3.boolean().optional(),
|
|
1979
|
+
maxLength: z3.number().int().min(1).max(2e3).optional(),
|
|
1980
|
+
ellipsis: z3.string().max(10).optional(),
|
|
1981
|
+
fallback: fallbackText.optional(),
|
|
1982
|
+
fallbackWithParent: fallbackText.optional(),
|
|
1983
|
+
fallbackWithoutParent: fallbackText.optional(),
|
|
1984
|
+
fallbackBySlug: z3.record(z3.string().max(200), fallbackText).optional()
|
|
1985
|
+
}).strict().refine((rule) => !rule.ellipsis || rule.maxLength !== void 0 && rule.ellipsis.length < rule.maxLength, "\uB9D0\uC904\uC784\uD45C\uC5D0\uB294 \uB354 \uAE34 \uCD5C\uB300 \uAE38\uC774\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
1986
|
+
var MetadataDefaultRulesSchema = z3.object({
|
|
1987
|
+
title: MetadataTextRuleSchema.optional(),
|
|
1988
|
+
description: MetadataTextRuleSchema.optional(),
|
|
1989
|
+
useFirstBodyImage: z3.boolean().optional(),
|
|
1990
|
+
useTermImage: z3.boolean().optional(),
|
|
1991
|
+
imageAltSource: z3.enum(["content", "title"]).optional(),
|
|
1992
|
+
legacyImageSource: z3.literal("legacy-thumbnail").optional(),
|
|
1993
|
+
imageAllowedOrigins: z3.array(imageOrigin).min(1).max(32).optional(),
|
|
1994
|
+
legacyImageAllowedOrigins: z3.array(imageOrigin).min(1).max(32).optional(),
|
|
1995
|
+
normalizeCloudflareImages: z3.boolean().optional(),
|
|
1996
|
+
fallback: MetadataValuesSchema.optional()
|
|
1997
|
+
}).strict().refine((rule) => !rule.legacyImageSource || Boolean(rule.legacyImageAllowedOrigins?.length), "\uC774\uC804 \uC774\uBBF8\uC9C0 \uC6D0\uBCF8\uC5D0\uB294 \uD5C8\uC6A9 origin \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
1998
|
+
function cleanText(value, rule) {
|
|
1999
|
+
let text2 = value.trim();
|
|
2000
|
+
if (rule?.stripLeadingLabel === "question") text2 = text2.replace(/^\s*Q[..:]?\s*/iu, "");
|
|
2001
|
+
if (rule?.stripLeadingLabel === "answer") text2 = text2.replace(/^\s*A[..:]?\s*/iu, "");
|
|
2002
|
+
if (rule?.replaceLineBreakTokens) text2 = text2.replace(/\[\[br(?::[^\]]*)?\]\]/giu, " ");
|
|
2003
|
+
if (rule?.removeEmDashes) text2 = text2.replaceAll("\u2014", "");
|
|
2004
|
+
if (rule?.normalizeWhitespace) text2 = text2.replace(/\s+/gu, " ");
|
|
2005
|
+
return text2.trim();
|
|
2006
|
+
}
|
|
2007
|
+
function formatText(value, rule, input, name) {
|
|
2008
|
+
let text2 = rule?.source === "fallback" ? "" : cleanText(value ?? "", rule);
|
|
2009
|
+
if (!text2) {
|
|
2010
|
+
const fallback = input.parentId ? rule?.fallbackWithParent ?? rule?.fallback : (input.slug ? rule?.fallbackBySlug?.[input.slug] : void 0) ?? rule?.fallbackWithoutParent ?? rule?.fallback;
|
|
2011
|
+
text2 = cleanText((fallback ?? "").replaceAll("%s", () => name), rule);
|
|
2012
|
+
}
|
|
2013
|
+
if (!text2) return void 0;
|
|
2014
|
+
if (rule?.maxLength && text2.length > rule.maxLength) {
|
|
2015
|
+
const suffix = rule.ellipsis ?? "";
|
|
2016
|
+
const head = text2.slice(0, rule.maxLength - suffix.length);
|
|
2017
|
+
return suffix ? head.trimEnd() + suffix : head;
|
|
2018
|
+
}
|
|
2019
|
+
return text2;
|
|
2020
|
+
}
|
|
2021
|
+
function metadataContentImageUrl(value, normalizeCloudflareImages = false) {
|
|
2022
|
+
if (!normalizeCloudflareImages) return value;
|
|
2023
|
+
try {
|
|
2024
|
+
const url = new URL(value);
|
|
2025
|
+
const [account, id, variant] = url.pathname.split("/").filter(Boolean);
|
|
2026
|
+
if (url.protocol === "https:" && url.hostname === "imagedelivery.net" && account && id && variant) {
|
|
2027
|
+
url.pathname = `/${account}/${id}/lg`;
|
|
2028
|
+
return url.href;
|
|
2029
|
+
}
|
|
2030
|
+
} catch {
|
|
2031
|
+
}
|
|
2032
|
+
return value;
|
|
2033
|
+
}
|
|
2034
|
+
function legacyImageUrl(value, rule) {
|
|
2035
|
+
if (!value || rule?.legacyImageSource !== "legacy-thumbnail") return void 0;
|
|
2036
|
+
try {
|
|
2037
|
+
const url = new URL(value);
|
|
2038
|
+
if (!rule.legacyImageAllowedOrigins?.includes(url.origin)) return void 0;
|
|
2039
|
+
return readMetadataValues({ ogImage: url.href }).ogImage;
|
|
2040
|
+
} catch {
|
|
2041
|
+
return void 0;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
function contentCandidateAllowed(value, input, rule) {
|
|
2045
|
+
if (!rule?.imageAllowedOrigins) return true;
|
|
2046
|
+
if (value.startsWith("/") && !value.startsWith("//")) return true;
|
|
2047
|
+
try {
|
|
2048
|
+
const url = new URL(value);
|
|
2049
|
+
if (url.protocol !== "https:") return false;
|
|
2050
|
+
const origins = [...rule.imageAllowedOrigins];
|
|
2051
|
+
if (input.imageBaseUrl) origins.push(new URL(input.imageBaseUrl).origin);
|
|
2052
|
+
return origins.includes(url.origin);
|
|
2053
|
+
} catch {
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
function formatMetadataDefaults(input, declaration) {
|
|
2058
|
+
const dynamic = declaration[input.kind];
|
|
2059
|
+
const rule = dynamic?.defaultRules?.[input.modelKey];
|
|
2060
|
+
const name = cleanText(input.title, rule?.title);
|
|
2061
|
+
const title = formatText(input.title, rule?.title, input, name);
|
|
2062
|
+
const template = dynamic?.titleTemplates?.[input.modelKey];
|
|
2063
|
+
const description = formatText(input.description, rule?.description, input, name);
|
|
2064
|
+
const bodyImage = input.firstBodyImage && (input.firstBodyImage.startsWith("https://") || /^\/(?!\/)/u.test(input.firstBodyImage)) ? input.firstBodyImage : void 0;
|
|
2065
|
+
const candidateImage = input.kind === "terms" && rule?.useTermImage === false ? void 0 : input.featuredImage || (rule?.useFirstBodyImage ? bodyImage : void 0) || legacyImageUrl(input.legacyImage, rule);
|
|
2066
|
+
const ogImage = candidateImage && contentCandidateAllowed(candidateImage, input, rule) ? readMetadataValues({ ogImage: metadataContentImageUrl(candidateImage, rule?.normalizeCloudflareImages) }).ogImage : void 0;
|
|
2067
|
+
return {
|
|
2068
|
+
...title ? { title: template ? template.replace("%s", () => title) : title } : {},
|
|
2069
|
+
...description ? { description } : {},
|
|
2070
|
+
...ogImage ? { ogImage } : {}
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// src/redirect-paths.ts
|
|
2075
|
+
var CONTROL_OR_BACKSLASH = /[\\\u0000-\u001f\u007f]/u;
|
|
2076
|
+
var PROTECTED_PREFIX = /^\/(?:api|_next|_vercel|auth|admin|login|logout|revalidate|preview|assets|static|images|fonts|\.well-known)(?:\/|$)/i;
|
|
2077
|
+
var STATIC_EXTENSION = /\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|txt|xml|json|woff2?|map)$/i;
|
|
2078
|
+
function decoded(value) {
|
|
2079
|
+
try {
|
|
2080
|
+
return decodeURIComponent(value);
|
|
2081
|
+
} catch {
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function trimInternalPath(path) {
|
|
2086
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
2087
|
+
if (path.length > 1) path = path.replace(/\/+$/, "");
|
|
2088
|
+
return path || "/";
|
|
2089
|
+
}
|
|
2090
|
+
function normalizeRedirectPath(raw) {
|
|
2091
|
+
return trimInternalPath((decoded(raw) ?? raw).split(/[?#]/, 1)[0] ?? "");
|
|
2092
|
+
}
|
|
2093
|
+
function isProtectedRedirectPath(path) {
|
|
2094
|
+
const normalized = normalizeRedirectPath(path);
|
|
2095
|
+
return PROTECTED_PREFIX.test(normalized) || STATIC_EXTENSION.test(normalized);
|
|
2096
|
+
}
|
|
2097
|
+
function unsafePath(path) {
|
|
2098
|
+
return CONTROL_OR_BACKSLASH.test(path) || path.startsWith("//") || /(?:^|\/)\.{1,2}(?:\/|$)/.test(path) || /[%](?:2f|5c|0[0-9a-f]|1[0-9a-f]|7f)/i.test(path);
|
|
2099
|
+
}
|
|
2100
|
+
function normalizeRedirectSource(raw) {
|
|
2101
|
+
const invalid = { ok: false, code: "invalid_source_path" };
|
|
2102
|
+
if (CONTROL_OR_BACKSLASH.test(raw)) return invalid;
|
|
2103
|
+
const trimmed = raw.trim();
|
|
2104
|
+
if (!trimmed || /^[a-z][a-z\d+.-]*:/i.test(trimmed) || trimmed.startsWith("//")) return invalid;
|
|
2105
|
+
const rawPath = trimmed.split(/[?#]/, 1)[0] ?? "";
|
|
2106
|
+
const path = decoded(rawPath);
|
|
2107
|
+
if (!path || unsafePath(path) || /[?#*]/.test(path)) return invalid;
|
|
2108
|
+
const normalized = trimInternalPath(path);
|
|
2109
|
+
if (isProtectedRedirectPath(normalized)) return invalid;
|
|
2110
|
+
return { ok: true, value: normalized };
|
|
2111
|
+
}
|
|
2112
|
+
function normalizeRedirectTarget(raw) {
|
|
2113
|
+
const invalid = { ok: false, code: "invalid_target" };
|
|
2114
|
+
const decodedRaw = decoded(raw);
|
|
2115
|
+
if (decodedRaw === null || CONTROL_OR_BACKSLASH.test(raw) || CONTROL_OR_BACKSLASH.test(decodedRaw)) return invalid;
|
|
2116
|
+
const trimmed = raw.trim();
|
|
2117
|
+
if (!trimmed || trimmed.startsWith("//")) return invalid;
|
|
2118
|
+
if (trimmed.startsWith("/")) {
|
|
2119
|
+
const suffixIndex = trimmed.search(/[?#]/);
|
|
2120
|
+
const rawPath = suffixIndex === -1 ? trimmed : trimmed.slice(0, suffixIndex);
|
|
2121
|
+
const path = decoded(rawPath);
|
|
2122
|
+
if (!path || unsafePath(path) || /[?#]/.test(path)) return invalid;
|
|
2123
|
+
const suffix = suffixIndex === -1 ? "" : trimmed.slice(suffixIndex);
|
|
2124
|
+
return { ok: true, value: `${trimInternalPath(path)}${suffix}`, kind: "internal" };
|
|
2125
|
+
}
|
|
2126
|
+
try {
|
|
2127
|
+
const url = new URL(trimmed);
|
|
2128
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) return invalid;
|
|
2129
|
+
return { ok: true, value: url.toString(), kind: "external" };
|
|
2130
|
+
} catch {
|
|
2131
|
+
return invalid;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
1841
2134
|
export {
|
|
1842
2135
|
BLOCK_NAME_PATTERN,
|
|
1843
2136
|
CMS_FONT_SIZES,
|
|
@@ -1854,6 +2147,14 @@ export {
|
|
|
1854
2147
|
INTERNAL_CONTENT_LINK_PENDING_CLASS,
|
|
1855
2148
|
INTERNAL_CONTENT_LINK_TOKEN_SOURCE,
|
|
1856
2149
|
LEGACY_BLOG_DETAIL_PATH,
|
|
2150
|
+
METADATA_FIELDS,
|
|
2151
|
+
MetadataDefaultRulesSchema,
|
|
2152
|
+
MetadataFieldSchema,
|
|
2153
|
+
MetadataPatchSchema,
|
|
2154
|
+
MetadataRobotsPolicySchema,
|
|
2155
|
+
MetadataTargetKindSchema,
|
|
2156
|
+
MetadataTextRuleSchema,
|
|
2157
|
+
MetadataValuesSchema,
|
|
1857
2158
|
PHASE_1_CORE_BLOCKS,
|
|
1858
2159
|
PHASE_1_CSP,
|
|
1859
2160
|
POST_FOOTER_SLOT,
|
|
@@ -1882,6 +2183,7 @@ export {
|
|
|
1882
2183
|
findInternalContentLinkTokens,
|
|
1883
2184
|
findSectionLockViolations,
|
|
1884
2185
|
findSitePatternPlacementRule,
|
|
2186
|
+
formatMetadataDefaults,
|
|
1885
2187
|
formatSectionLockViolations,
|
|
1886
2188
|
htmlToPlainText,
|
|
1887
2189
|
injectStableIdsTree,
|
|
@@ -1891,18 +2193,25 @@ export {
|
|
|
1891
2193
|
isAllowedEmbedSrc,
|
|
1892
2194
|
isAllowedMapEmbedSrc,
|
|
1893
2195
|
isInCollectionMode,
|
|
2196
|
+
isProtectedRedirectPath,
|
|
1894
2197
|
isSectionContentPropPath,
|
|
1895
2198
|
isSitePatternSlotKey,
|
|
1896
2199
|
isValidBlock,
|
|
1897
2200
|
isValidSitePatternKey,
|
|
1898
2201
|
issuePreviewToken,
|
|
2202
|
+
mergeMetadataPatch,
|
|
2203
|
+
metadataContentImageUrl,
|
|
1899
2204
|
normalizeEmbed,
|
|
1900
2205
|
normalizeHexColor,
|
|
1901
2206
|
normalizeImageAlt,
|
|
2207
|
+
normalizeRedirectPath,
|
|
2208
|
+
normalizeRedirectSource,
|
|
2209
|
+
normalizeRedirectTarget,
|
|
1902
2210
|
parseBlock,
|
|
1903
2211
|
postsForCollection,
|
|
1904
2212
|
postsInAnyCollection,
|
|
1905
2213
|
postsNotInAnyCollection,
|
|
2214
|
+
readMetadataValues,
|
|
1906
2215
|
readSitePatternPlacements,
|
|
1907
2216
|
readSitePatternPresentation,
|
|
1908
2217
|
referencedSitePatternKeys,
|
|
@@ -1912,6 +2221,8 @@ export {
|
|
|
1912
2221
|
resolveCategoryTreeChain,
|
|
1913
2222
|
resolveCategoryTreeEntryPath,
|
|
1914
2223
|
resolveCategoryTreeHubPaths,
|
|
2224
|
+
resolveCategoryTreeSelection,
|
|
2225
|
+
resolveMetadata,
|
|
1915
2226
|
resolvePatternSlots,
|
|
1916
2227
|
resolvePostCategorySlug,
|
|
1917
2228
|
resolvePostCollection,
|