@roottale/cms-core 0.55.0 → 0.57.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 +36 -0
- package/dist/index.cjs +308 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +174 -1
- package/dist/index.d.ts +174 -1
- package/dist/index.js +283 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @roottale/cms-core
|
|
2
2
|
|
|
3
|
+
## 0.57.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e222471: 본문 예약 내부 링크(`[[internal:키|문구]]`)를 공용 렌더러가 해석한다.
|
|
8
|
+
- `@roottale/cms-core`: 토큰 정규식·탐색(`findInternalContentLinkTokens`), 경로→키
|
|
9
|
+
(`internalContentKeyFromPath`), 발행 글 경로 색인(`internalContentPathIndex`, 옛 slug 포함),
|
|
10
|
+
텍스트·정화된 HTML 안의 토큰을 링크로 바꾸는 `renderInternalContentLinksInText/InHtml`.
|
|
11
|
+
- `@roottale/cms-renderer-next`: `RenderTiptap`·`RootTaleBlogPost` 에 `internalLinks`
|
|
12
|
+
(Map·함수) 옵션. `RootTaleBlogPost` 는 기본 `"auto"` — 본문에 토큰이 있을 때만
|
|
13
|
+
`fetchInternalContentPathIndex` 로 발행 글 경로 색인을 만들어 링크로 렌더한다.
|
|
14
|
+
대상이 없는 토큰은 문구만 남기고 `data-rt-internal-link-pending="키"` 를 단다.
|
|
15
|
+
`<code>`·`<pre>`·기존 링크 안은 건드리지 않는다.
|
|
16
|
+
- 문서: FRONT 가 토큰을 직접 해석하지 않아도 되는 경로를 안내한다.
|
|
17
|
+
|
|
18
|
+
- 92d0ad4: 공통 블록(Site Patterns) — 여러 글의 같은 자리(글 하단)에 붙는 재사용 본문.
|
|
19
|
+
- `@roottale/cms-client`: 글 응답 `patternSlots`(자리 key → 블록 key | null, 구 서버는 빈
|
|
20
|
+
객체), `fetchSitePatterns`(발행 블록 목록, `GET /v1/cms/public/patterns`),
|
|
21
|
+
`selectSitePatternForSlot`, `SITE_PATTERNS_CACHE_TAG`(`SETTINGS_CACHE_TAGS` 에 포함 —
|
|
22
|
+
`theme.updated` 로 함께 갱신), `POST_FOOTER_PATTERN_SLOT`.
|
|
23
|
+
- `@roottale/cms-renderer-next`: `RootTaleBlogPost` 가 `patternSlots.post_footer` 블록을 본문
|
|
24
|
+
바로 아래에 자동으로 그린다(목록 요청 실패는 블록만 생략). 자체 글 화면용
|
|
25
|
+
`RootTalePostPattern` 서버 컴포넌트와 `.rt-cms-post-pattern` 스타일 추가.
|
|
26
|
+
- `@roottale/cms-core`: 자리 선언·배치 규칙 정규화·`resolvePatternSlots` (플랫폼 서버·어드민과
|
|
27
|
+
공유하는 순수 규칙).
|
|
28
|
+
- 문서: `theme-and-settings.md` "공통 블록", `api-reference.md` `GET /patterns`·`pattern_slots`.
|
|
29
|
+
|
|
30
|
+
## 0.56.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- 9eb39e8: ADR-0105 개정 — 글의 정규 공개 경로 계산기 `resolvePostPublicPath` 를 추가합니다.
|
|
35
|
+
모델 presentation(fixed_page·detail·category_tree) → 호환 컬렉션 → 레거시 `/blog` 순서
|
|
36
|
+
하나로 계산하며, 플랫폼의 웹훅·사이트맵·단축링크·내부 링크 키·`posts.public_path`
|
|
37
|
+
물질화가 모두 이 함수를 씁니다.
|
|
38
|
+
|
|
3
39
|
## 0.55.0
|
|
4
40
|
|
|
5
41
|
## 0.54.0
|
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
BLOCK_NAME_PATTERN: () => BLOCK_NAME_PATTERN,
|
|
34
|
+
EMPTY_SITE_PATTERN_PLACEMENTS: () => EMPTY_SITE_PATTERN_PLACEMENTS,
|
|
34
35
|
FIELD_BINDING_SOURCE: () => FIELD_BINDING_SOURCE,
|
|
35
36
|
FIELD_BINDING_SOURCE_ALIAS: () => FIELD_BINDING_SOURCE_ALIAS,
|
|
36
37
|
IMAGE_ALT_MAX_LENGTH: () => IMAGE_ALT_MAX_LENGTH,
|
|
38
|
+
INTERNAL_CONTENT_LINK_CLASS: () => INTERNAL_CONTENT_LINK_CLASS,
|
|
39
|
+
INTERNAL_CONTENT_LINK_PENDING_ATTR: () => INTERNAL_CONTENT_LINK_PENDING_ATTR,
|
|
40
|
+
INTERNAL_CONTENT_LINK_PENDING_CLASS: () => INTERNAL_CONTENT_LINK_PENDING_CLASS,
|
|
41
|
+
INTERNAL_CONTENT_LINK_TOKEN_SOURCE: () => INTERNAL_CONTENT_LINK_TOKEN_SOURCE,
|
|
42
|
+
LEGACY_BLOG_DETAIL_PATH: () => LEGACY_BLOG_DETAIL_PATH,
|
|
37
43
|
PHASE_1_CORE_BLOCKS: () => PHASE_1_CORE_BLOCKS,
|
|
38
44
|
PHASE_1_CSP: () => PHASE_1_CSP,
|
|
45
|
+
POST_FOOTER_SLOT: () => POST_FOOTER_SLOT,
|
|
46
|
+
SITE_PATTERN_KEY_PATTERN: () => SITE_PATTERN_KEY_PATTERN,
|
|
47
|
+
SITE_PATTERN_PLACEMENTS_KEY: () => SITE_PATTERN_PLACEMENTS_KEY,
|
|
48
|
+
SITE_PATTERN_SLOTS: () => SITE_PATTERN_SLOTS,
|
|
39
49
|
applyBlockBindings: () => applyBlockBindings,
|
|
40
50
|
bindingSrcValue: () => bindingSrcValue,
|
|
41
51
|
bindingTextValue: () => bindingTextValue,
|
|
@@ -43,16 +53,24 @@ __export(src_exports, {
|
|
|
43
53
|
boundFieldValue: () => boundFieldValue,
|
|
44
54
|
chainResolvers: () => chainResolvers,
|
|
45
55
|
createFieldBindingResolver: () => createFieldBindingResolver,
|
|
56
|
+
emptyPatternSlots: () => emptyPatternSlots,
|
|
46
57
|
ensureImportedHtmlDocumentRoots: () => ensureImportedHtmlDocumentRoots,
|
|
47
58
|
ensureStableIds: () => ensureStableIds,
|
|
48
59
|
extractFaqEntries: () => extractFaqEntries,
|
|
49
60
|
faqPageJsonLd: () => faqPageJsonLd,
|
|
61
|
+
findInternalContentLinkTokens: () => findInternalContentLinkTokens,
|
|
62
|
+
findSitePatternPlacementRule: () => findSitePatternPlacementRule,
|
|
50
63
|
htmlToPlainText: () => htmlToPlainText,
|
|
51
64
|
injectStableIdsTree: () => injectStableIdsTree,
|
|
65
|
+
internalContentKeyFromPath: () => internalContentKeyFromPath,
|
|
66
|
+
internalContentLinkResolverFrom: () => internalContentLinkResolverFrom,
|
|
67
|
+
internalContentPathIndex: () => internalContentPathIndex,
|
|
52
68
|
isAllowedEmbedSrc: () => isAllowedEmbedSrc,
|
|
53
69
|
isAllowedMapEmbedSrc: () => isAllowedMapEmbedSrc,
|
|
54
70
|
isInCollectionMode: () => isInCollectionMode,
|
|
71
|
+
isSitePatternSlotKey: () => isSitePatternSlotKey,
|
|
55
72
|
isValidBlock: () => isValidBlock,
|
|
73
|
+
isValidSitePatternKey: () => isValidSitePatternKey,
|
|
56
74
|
issuePreviewToken: () => issuePreviewToken,
|
|
57
75
|
normalizeEmbed: () => normalizeEmbed,
|
|
58
76
|
normalizeImageAlt: () => normalizeImageAlt,
|
|
@@ -60,19 +78,26 @@ __export(src_exports, {
|
|
|
60
78
|
postsForCollection: () => postsForCollection,
|
|
61
79
|
postsInAnyCollection: () => postsInAnyCollection,
|
|
62
80
|
postsNotInAnyCollection: () => postsNotInAnyCollection,
|
|
81
|
+
readSitePatternPlacements: () => readSitePatternPlacements,
|
|
82
|
+
referencedSitePatternKeys: () => referencedSitePatternKeys,
|
|
83
|
+
renderInternalContentLinksInHtml: () => renderInternalContentLinksInHtml,
|
|
84
|
+
renderInternalContentLinksInText: () => renderInternalContentLinksInText,
|
|
63
85
|
resolveCategoryPath: () => resolveCategoryPath,
|
|
64
86
|
resolveCategoryTreeChain: () => resolveCategoryTreeChain,
|
|
65
87
|
resolveCategoryTreeEntryPath: () => resolveCategoryTreeEntryPath,
|
|
66
88
|
resolveCategoryTreeHubPaths: () => resolveCategoryTreeHubPaths,
|
|
89
|
+
resolvePatternSlots: () => resolvePatternSlots,
|
|
67
90
|
resolvePostCategorySlug: () => resolvePostCategorySlug,
|
|
68
91
|
resolvePostCollection: () => resolvePostCollection,
|
|
69
92
|
resolvePostPath: () => resolvePostPath,
|
|
93
|
+
resolvePostPublicPath: () => resolvePostPublicPath,
|
|
70
94
|
resolvePostUrl: () => resolvePostUrl,
|
|
71
95
|
sanitizeHtml: () => sanitizeHtml,
|
|
72
96
|
sanitizeImportedCss: () => sanitizeImportedCss,
|
|
73
97
|
sanitizeImportedHtml: () => sanitizeImportedHtml,
|
|
74
98
|
scopeImportedCss: () => scopeImportedCss,
|
|
75
99
|
serializeBlock: () => serializeBlock,
|
|
100
|
+
setSitePatternPlacementRule: () => setSitePatternPlacementRule,
|
|
76
101
|
validateCollections: () => validateCollections,
|
|
77
102
|
verifyPreviewToken: () => verifyPreviewToken
|
|
78
103
|
});
|
|
@@ -359,6 +384,27 @@ function resolveCategoryTreeEntryPath(presentation, entry, terms) {
|
|
|
359
384
|
return leafPath ? `${leafPath}/${encodeURIComponent(entry.slug)}` : null;
|
|
360
385
|
}
|
|
361
386
|
|
|
387
|
+
// src/public-path.ts
|
|
388
|
+
var LEGACY_BLOG_DETAIL_PATH = "/blog";
|
|
389
|
+
function resolvePostPublicPath(post, context) {
|
|
390
|
+
const presentation = context.presentation;
|
|
391
|
+
if (presentation && presentation.kind !== "data_only") {
|
|
392
|
+
if (presentation.kind === "fixed_page") return presentation.path;
|
|
393
|
+
if (presentation.kind === "detail") {
|
|
394
|
+
if (!presentation.detailPath.includes(":slug")) return null;
|
|
395
|
+
return presentation.detailPath.replace(":slug", encodeURIComponent(post.slug));
|
|
396
|
+
}
|
|
397
|
+
return resolveCategoryTreeEntryPath(presentation, post, context.categories);
|
|
398
|
+
}
|
|
399
|
+
const collectionKey = post.collectionKey ?? (presentation ? context.compatibilityCollectionKey ?? post.modelKey ?? null : null);
|
|
400
|
+
const routable = { slug: post.slug, terms: post.terms, collectionKey };
|
|
401
|
+
if (!resolvePostCollection(routable, context.collections)) {
|
|
402
|
+
if (presentation) return null;
|
|
403
|
+
return `${LEGACY_BLOG_DETAIL_PATH}/${post.slug}`;
|
|
404
|
+
}
|
|
405
|
+
return resolvePostPath(routable, context.collections);
|
|
406
|
+
}
|
|
407
|
+
|
|
362
408
|
// src/delimiter.ts
|
|
363
409
|
function serializeBlock(block) {
|
|
364
410
|
const exportable = sanitizeAttributesForExport(block);
|
|
@@ -1018,6 +1064,152 @@ function scopeImportedCss(css, scope, allowedAssetUrls = []) {
|
|
|
1018
1064
|
}
|
|
1019
1065
|
}
|
|
1020
1066
|
|
|
1067
|
+
// src/internal-content-links.ts
|
|
1068
|
+
var INTERNAL_CONTENT_LINK_TOKEN_SOURCE = String.raw`\[\[internal:([a-z0-9가-힣]+(?:[.-][a-z0-9가-힣]+)*)\|([^\]\n|]+)\]\]`;
|
|
1069
|
+
function findInternalContentLinkTokens(text) {
|
|
1070
|
+
const pattern = new RegExp(INTERNAL_CONTENT_LINK_TOKEN_SOURCE, "giu");
|
|
1071
|
+
const tokens = [];
|
|
1072
|
+
for (const match of text.matchAll(pattern)) {
|
|
1073
|
+
const [raw, key, label] = match;
|
|
1074
|
+
if (!key || !label || match.index === void 0) continue;
|
|
1075
|
+
tokens.push({
|
|
1076
|
+
index: match.index,
|
|
1077
|
+
raw,
|
|
1078
|
+
key: key.toLowerCase(),
|
|
1079
|
+
label: label.trim()
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
return tokens;
|
|
1083
|
+
}
|
|
1084
|
+
function internalContentKeyFromPath(path) {
|
|
1085
|
+
if (!path) return null;
|
|
1086
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
1087
|
+
try {
|
|
1088
|
+
return decodeURIComponent(segment).trim().toLowerCase();
|
|
1089
|
+
} catch {
|
|
1090
|
+
return "";
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
if (segments.length === 0 || segments.some((segment) => segment === "" || segment.includes("."))) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
return segments.join(".");
|
|
1097
|
+
}
|
|
1098
|
+
function internalContentPathIndex(entries) {
|
|
1099
|
+
const index = /* @__PURE__ */ new Map();
|
|
1100
|
+
const previous = /* @__PURE__ */ new Map();
|
|
1101
|
+
for (const entry of entries) {
|
|
1102
|
+
const path = entry.path;
|
|
1103
|
+
const key = internalContentKeyFromPath(path);
|
|
1104
|
+
if (!path || !key) continue;
|
|
1105
|
+
index.set(key, path);
|
|
1106
|
+
const segments = key.split(".");
|
|
1107
|
+
for (const oldSlug of entry.previousSlugs ?? []) {
|
|
1108
|
+
const oldKey = [...segments.slice(0, -1), oldSlug.trim().toLowerCase()].join(".");
|
|
1109
|
+
if (oldKey && !oldKey.includes("..")) previous.set(oldKey, path);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
for (const [key, path] of previous) {
|
|
1113
|
+
if (!index.has(key)) index.set(key, path);
|
|
1114
|
+
}
|
|
1115
|
+
return index;
|
|
1116
|
+
}
|
|
1117
|
+
function internalContentLinkResolverFrom(source) {
|
|
1118
|
+
if (!source) return () => null;
|
|
1119
|
+
if (typeof source === "function") return source;
|
|
1120
|
+
return (key) => source.get(key.toLowerCase()) ?? null;
|
|
1121
|
+
}
|
|
1122
|
+
function isSafeInternalPath(path) {
|
|
1123
|
+
return typeof path === "string" && path.startsWith("/") && !path.startsWith("//");
|
|
1124
|
+
}
|
|
1125
|
+
function escapeHtmlText(value) {
|
|
1126
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1127
|
+
}
|
|
1128
|
+
var INTERNAL_CONTENT_LINK_CLASS = "rt-internal-link";
|
|
1129
|
+
var INTERNAL_CONTENT_LINK_PENDING_CLASS = "rt-internal-link--pending";
|
|
1130
|
+
var INTERNAL_CONTENT_LINK_PENDING_ATTR = "data-rt-internal-link-pending";
|
|
1131
|
+
function renderInternalContentLinksInText(text, resolve) {
|
|
1132
|
+
const tokens = findInternalContentLinkTokens(text);
|
|
1133
|
+
if (tokens.length === 0) return { html: text, hasTokens: false };
|
|
1134
|
+
let cursor = 0;
|
|
1135
|
+
let html = "";
|
|
1136
|
+
for (const token of tokens) {
|
|
1137
|
+
html += text.slice(cursor, token.index);
|
|
1138
|
+
const path = resolve(token.key);
|
|
1139
|
+
const label = escapeHtmlText(token.label);
|
|
1140
|
+
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>`;
|
|
1141
|
+
cursor = token.index + token.raw.length;
|
|
1142
|
+
}
|
|
1143
|
+
html += text.slice(cursor);
|
|
1144
|
+
return { html, hasTokens: true };
|
|
1145
|
+
}
|
|
1146
|
+
var INTERNAL_LINK_BLOCKED_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
1147
|
+
"a",
|
|
1148
|
+
"code",
|
|
1149
|
+
"pre",
|
|
1150
|
+
"script",
|
|
1151
|
+
"style",
|
|
1152
|
+
"textarea"
|
|
1153
|
+
]);
|
|
1154
|
+
var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "input", "source", "wbr"]);
|
|
1155
|
+
function htmlTagEnd(html, start) {
|
|
1156
|
+
let quote = null;
|
|
1157
|
+
for (let index = start + 1; index < html.length; index += 1) {
|
|
1158
|
+
const character = html[index];
|
|
1159
|
+
if (quote) {
|
|
1160
|
+
if (character === quote) quote = null;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (character === '"' || character === "'") {
|
|
1164
|
+
quote = character;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (character === ">") return index;
|
|
1168
|
+
}
|
|
1169
|
+
return -1;
|
|
1170
|
+
}
|
|
1171
|
+
function renderInternalContentLinksInHtml(html, resolve) {
|
|
1172
|
+
if (!html.includes("[[internal:")) return { html, hasTokens: false };
|
|
1173
|
+
let blockedDepth = 0;
|
|
1174
|
+
let cursor = 0;
|
|
1175
|
+
let out = "";
|
|
1176
|
+
let hasTokens = false;
|
|
1177
|
+
const emitText = (text) => {
|
|
1178
|
+
if (blockedDepth > 0 || !text) {
|
|
1179
|
+
out += text;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const rendered = renderInternalContentLinksInText(text, resolve);
|
|
1183
|
+
hasTokens ||= rendered.hasTokens;
|
|
1184
|
+
out += rendered.html;
|
|
1185
|
+
};
|
|
1186
|
+
while (cursor < html.length) {
|
|
1187
|
+
const tagStart = html.indexOf("<", cursor);
|
|
1188
|
+
if (tagStart < 0) {
|
|
1189
|
+
emitText(html.slice(cursor));
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
emitText(html.slice(cursor, tagStart));
|
|
1193
|
+
const tagEnd = htmlTagEnd(html, tagStart);
|
|
1194
|
+
if (tagEnd < 0) {
|
|
1195
|
+
out += html.slice(tagStart);
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
const tag = html.slice(tagStart, tagEnd + 1);
|
|
1199
|
+
out += tag;
|
|
1200
|
+
const parsed = /^<\s*(\/?)\s*([a-z0-9-]+)/i.exec(tag);
|
|
1201
|
+
const tagName = parsed?.[2]?.toLowerCase();
|
|
1202
|
+
if (tagName && INTERNAL_LINK_BLOCKED_HTML_TAGS.has(tagName)) {
|
|
1203
|
+
const isClosing = parsed?.[1] === "/";
|
|
1204
|
+
const isVoid = VOID_HTML_TAGS.has(tagName) || /\/\s*>$/.test(tag);
|
|
1205
|
+
if (isClosing) blockedDepth = Math.max(0, blockedDepth - 1);
|
|
1206
|
+
else if (!isVoid) blockedDepth += 1;
|
|
1207
|
+
}
|
|
1208
|
+
cursor = tagEnd + 1;
|
|
1209
|
+
}
|
|
1210
|
+
return { html: out, hasTokens };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1021
1213
|
// src/preview-token.ts
|
|
1022
1214
|
var import_node_crypto = require("crypto");
|
|
1023
1215
|
var DELIMITER = ".";
|
|
@@ -1325,14 +1517,115 @@ function ensureStableIds(block) {
|
|
|
1325
1517
|
function injectStableIdsTree(blocks) {
|
|
1326
1518
|
return blocks.map((b) => ensureStableIds(b));
|
|
1327
1519
|
}
|
|
1520
|
+
|
|
1521
|
+
// src/site-patterns.ts
|
|
1522
|
+
var SITE_PATTERN_SLOTS = [
|
|
1523
|
+
{ key: "post_footer", label: "\uAE00 \uD558\uB2E8", description: "\uAE00 \uBCF8\uBB38 \uBC14\uB85C \uC544\uB798" }
|
|
1524
|
+
];
|
|
1525
|
+
var POST_FOOTER_SLOT = "post_footer";
|
|
1526
|
+
var SITE_PATTERN_PLACEMENTS_KEY = "site_pattern_placements";
|
|
1527
|
+
var SITE_PATTERN_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
1528
|
+
function isValidSitePatternKey(key) {
|
|
1529
|
+
return SITE_PATTERN_KEY_PATTERN.test(key);
|
|
1530
|
+
}
|
|
1531
|
+
function isSitePatternSlotKey(value) {
|
|
1532
|
+
return SITE_PATTERN_SLOTS.some((slot) => slot.key === value);
|
|
1533
|
+
}
|
|
1534
|
+
var EMPTY_SITE_PATTERN_PLACEMENTS = { rules: [] };
|
|
1535
|
+
function isRecord(value) {
|
|
1536
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1537
|
+
}
|
|
1538
|
+
function readNullableKey(value) {
|
|
1539
|
+
if (value === null) return null;
|
|
1540
|
+
if (typeof value === "string") {
|
|
1541
|
+
const trimmed = value.trim();
|
|
1542
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1543
|
+
}
|
|
1544
|
+
return void 0;
|
|
1545
|
+
}
|
|
1546
|
+
function readSitePatternPlacements(raw) {
|
|
1547
|
+
let value = raw;
|
|
1548
|
+
if (typeof value === "string") {
|
|
1549
|
+
try {
|
|
1550
|
+
value = JSON.parse(value);
|
|
1551
|
+
} catch {
|
|
1552
|
+
return EMPTY_SITE_PATTERN_PLACEMENTS;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
if (!isRecord(value) || !Array.isArray(value.rules)) {
|
|
1556
|
+
return EMPTY_SITE_PATTERN_PLACEMENTS;
|
|
1557
|
+
}
|
|
1558
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1559
|
+
for (const item of value.rules) {
|
|
1560
|
+
if (!isRecord(item)) continue;
|
|
1561
|
+
const slot = typeof item.slot === "string" ? item.slot : "";
|
|
1562
|
+
if (!isSitePatternSlotKey(slot)) continue;
|
|
1563
|
+
const collectionKey = readNullableKey(item.collectionKey);
|
|
1564
|
+
if (collectionKey === void 0) continue;
|
|
1565
|
+
const patternKey = readNullableKey(item.patternKey);
|
|
1566
|
+
if (patternKey === void 0) continue;
|
|
1567
|
+
if (patternKey !== null && !isValidSitePatternKey(patternKey)) continue;
|
|
1568
|
+
byTarget.set(`${slot}\0${collectionKey ?? ""}`, {
|
|
1569
|
+
slot,
|
|
1570
|
+
collectionKey,
|
|
1571
|
+
patternKey
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
return { rules: [...byTarget.values()] };
|
|
1575
|
+
}
|
|
1576
|
+
function findSitePatternPlacementRule(placements, slot, collectionKey) {
|
|
1577
|
+
return placements.rules.find(
|
|
1578
|
+
(rule) => rule.slot === slot && rule.collectionKey === collectionKey
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
function setSitePatternPlacementRule(placements, target, patternKey) {
|
|
1582
|
+
const rest = placements.rules.filter(
|
|
1583
|
+
(rule) => !(rule.slot === target.slot && rule.collectionKey === target.collectionKey)
|
|
1584
|
+
);
|
|
1585
|
+
if (patternKey === void 0) return { rules: rest };
|
|
1586
|
+
return {
|
|
1587
|
+
rules: [
|
|
1588
|
+
...rest,
|
|
1589
|
+
{ slot: target.slot, collectionKey: target.collectionKey, patternKey }
|
|
1590
|
+
]
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
function emptyPatternSlots() {
|
|
1594
|
+
return Object.fromEntries(SITE_PATTERN_SLOTS.map((slot) => [slot.key, null]));
|
|
1595
|
+
}
|
|
1596
|
+
function resolvePatternSlots(post, placements, publishedKeys) {
|
|
1597
|
+
const slots = emptyPatternSlots();
|
|
1598
|
+
for (const slot of SITE_PATTERN_SLOTS) {
|
|
1599
|
+
const specific = post.collectionKey !== null ? findSitePatternPlacementRule(placements, slot.key, post.collectionKey) : void 0;
|
|
1600
|
+
const rule = specific ?? findSitePatternPlacementRule(placements, slot.key, null);
|
|
1601
|
+
const key = rule?.patternKey ?? null;
|
|
1602
|
+
slots[slot.key] = key !== null && publishedKeys.has(key) ? key : null;
|
|
1603
|
+
}
|
|
1604
|
+
return slots;
|
|
1605
|
+
}
|
|
1606
|
+
function referencedSitePatternKeys(placements) {
|
|
1607
|
+
return new Set(
|
|
1608
|
+
placements.rules.map((rule) => rule.patternKey).filter((key) => key !== null)
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1328
1611
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1329
1612
|
0 && (module.exports = {
|
|
1330
1613
|
BLOCK_NAME_PATTERN,
|
|
1614
|
+
EMPTY_SITE_PATTERN_PLACEMENTS,
|
|
1331
1615
|
FIELD_BINDING_SOURCE,
|
|
1332
1616
|
FIELD_BINDING_SOURCE_ALIAS,
|
|
1333
1617
|
IMAGE_ALT_MAX_LENGTH,
|
|
1618
|
+
INTERNAL_CONTENT_LINK_CLASS,
|
|
1619
|
+
INTERNAL_CONTENT_LINK_PENDING_ATTR,
|
|
1620
|
+
INTERNAL_CONTENT_LINK_PENDING_CLASS,
|
|
1621
|
+
INTERNAL_CONTENT_LINK_TOKEN_SOURCE,
|
|
1622
|
+
LEGACY_BLOG_DETAIL_PATH,
|
|
1334
1623
|
PHASE_1_CORE_BLOCKS,
|
|
1335
1624
|
PHASE_1_CSP,
|
|
1625
|
+
POST_FOOTER_SLOT,
|
|
1626
|
+
SITE_PATTERN_KEY_PATTERN,
|
|
1627
|
+
SITE_PATTERN_PLACEMENTS_KEY,
|
|
1628
|
+
SITE_PATTERN_SLOTS,
|
|
1336
1629
|
applyBlockBindings,
|
|
1337
1630
|
bindingSrcValue,
|
|
1338
1631
|
bindingTextValue,
|
|
@@ -1340,16 +1633,24 @@ function injectStableIdsTree(blocks) {
|
|
|
1340
1633
|
boundFieldValue,
|
|
1341
1634
|
chainResolvers,
|
|
1342
1635
|
createFieldBindingResolver,
|
|
1636
|
+
emptyPatternSlots,
|
|
1343
1637
|
ensureImportedHtmlDocumentRoots,
|
|
1344
1638
|
ensureStableIds,
|
|
1345
1639
|
extractFaqEntries,
|
|
1346
1640
|
faqPageJsonLd,
|
|
1641
|
+
findInternalContentLinkTokens,
|
|
1642
|
+
findSitePatternPlacementRule,
|
|
1347
1643
|
htmlToPlainText,
|
|
1348
1644
|
injectStableIdsTree,
|
|
1645
|
+
internalContentKeyFromPath,
|
|
1646
|
+
internalContentLinkResolverFrom,
|
|
1647
|
+
internalContentPathIndex,
|
|
1349
1648
|
isAllowedEmbedSrc,
|
|
1350
1649
|
isAllowedMapEmbedSrc,
|
|
1351
1650
|
isInCollectionMode,
|
|
1651
|
+
isSitePatternSlotKey,
|
|
1352
1652
|
isValidBlock,
|
|
1653
|
+
isValidSitePatternKey,
|
|
1353
1654
|
issuePreviewToken,
|
|
1354
1655
|
normalizeEmbed,
|
|
1355
1656
|
normalizeImageAlt,
|
|
@@ -1357,19 +1658,26 @@ function injectStableIdsTree(blocks) {
|
|
|
1357
1658
|
postsForCollection,
|
|
1358
1659
|
postsInAnyCollection,
|
|
1359
1660
|
postsNotInAnyCollection,
|
|
1661
|
+
readSitePatternPlacements,
|
|
1662
|
+
referencedSitePatternKeys,
|
|
1663
|
+
renderInternalContentLinksInHtml,
|
|
1664
|
+
renderInternalContentLinksInText,
|
|
1360
1665
|
resolveCategoryPath,
|
|
1361
1666
|
resolveCategoryTreeChain,
|
|
1362
1667
|
resolveCategoryTreeEntryPath,
|
|
1363
1668
|
resolveCategoryTreeHubPaths,
|
|
1669
|
+
resolvePatternSlots,
|
|
1364
1670
|
resolvePostCategorySlug,
|
|
1365
1671
|
resolvePostCollection,
|
|
1366
1672
|
resolvePostPath,
|
|
1673
|
+
resolvePostPublicPath,
|
|
1367
1674
|
resolvePostUrl,
|
|
1368
1675
|
sanitizeHtml,
|
|
1369
1676
|
sanitizeImportedCss,
|
|
1370
1677
|
sanitizeImportedHtml,
|
|
1371
1678
|
scopeImportedCss,
|
|
1372
1679
|
serializeBlock,
|
|
1680
|
+
setSitePatternPlacementRule,
|
|
1373
1681
|
validateCollections,
|
|
1374
1682
|
verifyPreviewToken
|
|
1375
1683
|
});
|