@roottale/cms-core 0.56.0 → 0.58.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 +40 -0
- package/dist/index.cjs +348 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -1
- package/dist/index.d.ts +153 -1
- package/dist/index.js +319 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @roottale/cms-core
|
|
2
2
|
|
|
3
|
+
## 0.58.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- dd82c92: 공통 블록 표시 형태(presentation) — 블록의 모양(본문처럼/카드·배경색·링크 버튼·버튼 색)을 어드민이
|
|
8
|
+
정해 공개 API `presentation` 으로 내려준다. 사이트는 색·모양을 CSS 에 고정하지 않는다.
|
|
9
|
+
- `@roottale/cms-core`: `readSitePatternPresentation`·`sitePatternPresentationAttributes`·
|
|
10
|
+
`DEFAULT_SITE_PATTERN_PRESENTATION`.
|
|
11
|
+
- `@roottale/cms-client`: `CmsSitePattern.presentation`(구 서버는 기본값), `sitePatternPresentationAttributes`
|
|
12
|
+
(래퍼 data 속성 + `--rt-pattern-bg`·`--rt-pattern-btn-n` CSS 변수).
|
|
13
|
+
- `@roottale/cms-renderer-next`: `RootTaleBlogPost`·`RootTalePostPattern` 이 data 속성·변수를 얹고,
|
|
14
|
+
`cms-public.css` 가 카드/버튼 모양을 그 값으로만 그린다.
|
|
15
|
+
|
|
16
|
+
## 0.57.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- e222471: 본문 예약 내부 링크(`[[internal:키|문구]]`)를 공용 렌더러가 해석한다.
|
|
21
|
+
- `@roottale/cms-core`: 토큰 정규식·탐색(`findInternalContentLinkTokens`), 경로→키
|
|
22
|
+
(`internalContentKeyFromPath`), 발행 글 경로 색인(`internalContentPathIndex`, 옛 slug 포함),
|
|
23
|
+
텍스트·정화된 HTML 안의 토큰을 링크로 바꾸는 `renderInternalContentLinksInText/InHtml`.
|
|
24
|
+
- `@roottale/cms-renderer-next`: `RenderTiptap`·`RootTaleBlogPost` 에 `internalLinks`
|
|
25
|
+
(Map·함수) 옵션. `RootTaleBlogPost` 는 기본 `"auto"` — 본문에 토큰이 있을 때만
|
|
26
|
+
`fetchInternalContentPathIndex` 로 발행 글 경로 색인을 만들어 링크로 렌더한다.
|
|
27
|
+
대상이 없는 토큰은 문구만 남기고 `data-rt-internal-link-pending="키"` 를 단다.
|
|
28
|
+
`<code>`·`<pre>`·기존 링크 안은 건드리지 않는다.
|
|
29
|
+
- 문서: FRONT 가 토큰을 직접 해석하지 않아도 되는 경로를 안내한다.
|
|
30
|
+
|
|
31
|
+
- 92d0ad4: 공통 블록(Site Patterns) — 여러 글의 같은 자리(글 하단)에 붙는 재사용 본문.
|
|
32
|
+
- `@roottale/cms-client`: 글 응답 `patternSlots`(자리 key → 블록 key | null, 구 서버는 빈
|
|
33
|
+
객체), `fetchSitePatterns`(발행 블록 목록, `GET /v1/cms/public/patterns`),
|
|
34
|
+
`selectSitePatternForSlot`, `SITE_PATTERNS_CACHE_TAG`(`SETTINGS_CACHE_TAGS` 에 포함 —
|
|
35
|
+
`theme.updated` 로 함께 갱신), `POST_FOOTER_PATTERN_SLOT`.
|
|
36
|
+
- `@roottale/cms-renderer-next`: `RootTaleBlogPost` 가 `patternSlots.post_footer` 블록을 본문
|
|
37
|
+
바로 아래에 자동으로 그린다(목록 요청 실패는 블록만 생략). 자체 글 화면용
|
|
38
|
+
`RootTalePostPattern` 서버 컴포넌트와 `.rt-cms-post-pattern` 스타일 추가.
|
|
39
|
+
- `@roottale/cms-core`: 자리 선언·배치 규칙 정규화·`resolvePatternSlots` (플랫폼 서버·어드민과
|
|
40
|
+
공유하는 순수 규칙).
|
|
41
|
+
- 문서: `theme-and-settings.md` "공통 블록", `api-reference.md` `GET /patterns`·`pattern_slots`.
|
|
42
|
+
|
|
3
43
|
## 0.56.0
|
|
4
44
|
|
|
5
45
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -31,12 +31,23 @@ 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
|
+
DEFAULT_SITE_PATTERN_PRESENTATION: () => DEFAULT_SITE_PATTERN_PRESENTATION,
|
|
35
|
+
EMPTY_SITE_PATTERN_PLACEMENTS: () => EMPTY_SITE_PATTERN_PLACEMENTS,
|
|
34
36
|
FIELD_BINDING_SOURCE: () => FIELD_BINDING_SOURCE,
|
|
35
37
|
FIELD_BINDING_SOURCE_ALIAS: () => FIELD_BINDING_SOURCE_ALIAS,
|
|
36
38
|
IMAGE_ALT_MAX_LENGTH: () => IMAGE_ALT_MAX_LENGTH,
|
|
39
|
+
INTERNAL_CONTENT_LINK_CLASS: () => INTERNAL_CONTENT_LINK_CLASS,
|
|
40
|
+
INTERNAL_CONTENT_LINK_PENDING_ATTR: () => INTERNAL_CONTENT_LINK_PENDING_ATTR,
|
|
41
|
+
INTERNAL_CONTENT_LINK_PENDING_CLASS: () => INTERNAL_CONTENT_LINK_PENDING_CLASS,
|
|
42
|
+
INTERNAL_CONTENT_LINK_TOKEN_SOURCE: () => INTERNAL_CONTENT_LINK_TOKEN_SOURCE,
|
|
37
43
|
LEGACY_BLOG_DETAIL_PATH: () => LEGACY_BLOG_DETAIL_PATH,
|
|
38
44
|
PHASE_1_CORE_BLOCKS: () => PHASE_1_CORE_BLOCKS,
|
|
39
45
|
PHASE_1_CSP: () => PHASE_1_CSP,
|
|
46
|
+
POST_FOOTER_SLOT: () => POST_FOOTER_SLOT,
|
|
47
|
+
SITE_PATTERN_BUTTON_COLORS_MAX: () => SITE_PATTERN_BUTTON_COLORS_MAX,
|
|
48
|
+
SITE_PATTERN_KEY_PATTERN: () => SITE_PATTERN_KEY_PATTERN,
|
|
49
|
+
SITE_PATTERN_PLACEMENTS_KEY: () => SITE_PATTERN_PLACEMENTS_KEY,
|
|
50
|
+
SITE_PATTERN_SLOTS: () => SITE_PATTERN_SLOTS,
|
|
40
51
|
applyBlockBindings: () => applyBlockBindings,
|
|
41
52
|
bindingSrcValue: () => bindingSrcValue,
|
|
42
53
|
bindingTextValue: () => bindingTextValue,
|
|
@@ -44,27 +55,42 @@ __export(src_exports, {
|
|
|
44
55
|
boundFieldValue: () => boundFieldValue,
|
|
45
56
|
chainResolvers: () => chainResolvers,
|
|
46
57
|
createFieldBindingResolver: () => createFieldBindingResolver,
|
|
58
|
+
emptyPatternSlots: () => emptyPatternSlots,
|
|
47
59
|
ensureImportedHtmlDocumentRoots: () => ensureImportedHtmlDocumentRoots,
|
|
48
60
|
ensureStableIds: () => ensureStableIds,
|
|
49
61
|
extractFaqEntries: () => extractFaqEntries,
|
|
50
62
|
faqPageJsonLd: () => faqPageJsonLd,
|
|
63
|
+
findInternalContentLinkTokens: () => findInternalContentLinkTokens,
|
|
64
|
+
findSitePatternPlacementRule: () => findSitePatternPlacementRule,
|
|
51
65
|
htmlToPlainText: () => htmlToPlainText,
|
|
52
66
|
injectStableIdsTree: () => injectStableIdsTree,
|
|
67
|
+
internalContentKeyFromPath: () => internalContentKeyFromPath,
|
|
68
|
+
internalContentLinkResolverFrom: () => internalContentLinkResolverFrom,
|
|
69
|
+
internalContentPathIndex: () => internalContentPathIndex,
|
|
53
70
|
isAllowedEmbedSrc: () => isAllowedEmbedSrc,
|
|
54
71
|
isAllowedMapEmbedSrc: () => isAllowedMapEmbedSrc,
|
|
55
72
|
isInCollectionMode: () => isInCollectionMode,
|
|
73
|
+
isSitePatternSlotKey: () => isSitePatternSlotKey,
|
|
56
74
|
isValidBlock: () => isValidBlock,
|
|
75
|
+
isValidSitePatternKey: () => isValidSitePatternKey,
|
|
57
76
|
issuePreviewToken: () => issuePreviewToken,
|
|
58
77
|
normalizeEmbed: () => normalizeEmbed,
|
|
78
|
+
normalizeHexColor: () => normalizeHexColor,
|
|
59
79
|
normalizeImageAlt: () => normalizeImageAlt,
|
|
60
80
|
parseBlock: () => parseBlock,
|
|
61
81
|
postsForCollection: () => postsForCollection,
|
|
62
82
|
postsInAnyCollection: () => postsInAnyCollection,
|
|
63
83
|
postsNotInAnyCollection: () => postsNotInAnyCollection,
|
|
84
|
+
readSitePatternPlacements: () => readSitePatternPlacements,
|
|
85
|
+
readSitePatternPresentation: () => readSitePatternPresentation,
|
|
86
|
+
referencedSitePatternKeys: () => referencedSitePatternKeys,
|
|
87
|
+
renderInternalContentLinksInHtml: () => renderInternalContentLinksInHtml,
|
|
88
|
+
renderInternalContentLinksInText: () => renderInternalContentLinksInText,
|
|
64
89
|
resolveCategoryPath: () => resolveCategoryPath,
|
|
65
90
|
resolveCategoryTreeChain: () => resolveCategoryTreeChain,
|
|
66
91
|
resolveCategoryTreeEntryPath: () => resolveCategoryTreeEntryPath,
|
|
67
92
|
resolveCategoryTreeHubPaths: () => resolveCategoryTreeHubPaths,
|
|
93
|
+
resolvePatternSlots: () => resolvePatternSlots,
|
|
68
94
|
resolvePostCategorySlug: () => resolvePostCategorySlug,
|
|
69
95
|
resolvePostCollection: () => resolvePostCollection,
|
|
70
96
|
resolvePostPath: () => resolvePostPath,
|
|
@@ -75,6 +101,9 @@ __export(src_exports, {
|
|
|
75
101
|
sanitizeImportedHtml: () => sanitizeImportedHtml,
|
|
76
102
|
scopeImportedCss: () => scopeImportedCss,
|
|
77
103
|
serializeBlock: () => serializeBlock,
|
|
104
|
+
setSitePatternPlacementRule: () => setSitePatternPlacementRule,
|
|
105
|
+
sitePatternButtonColor: () => sitePatternButtonColor,
|
|
106
|
+
sitePatternPresentationAttributes: () => sitePatternPresentationAttributes,
|
|
78
107
|
validateCollections: () => validateCollections,
|
|
79
108
|
verifyPreviewToken: () => verifyPreviewToken
|
|
80
109
|
});
|
|
@@ -1041,6 +1070,152 @@ function scopeImportedCss(css, scope, allowedAssetUrls = []) {
|
|
|
1041
1070
|
}
|
|
1042
1071
|
}
|
|
1043
1072
|
|
|
1073
|
+
// src/internal-content-links.ts
|
|
1074
|
+
var INTERNAL_CONTENT_LINK_TOKEN_SOURCE = String.raw`\[\[internal:([a-z0-9가-힣]+(?:[.-][a-z0-9가-힣]+)*)\|([^\]\n|]+)\]\]`;
|
|
1075
|
+
function findInternalContentLinkTokens(text) {
|
|
1076
|
+
const pattern = new RegExp(INTERNAL_CONTENT_LINK_TOKEN_SOURCE, "giu");
|
|
1077
|
+
const tokens = [];
|
|
1078
|
+
for (const match of text.matchAll(pattern)) {
|
|
1079
|
+
const [raw, key, label] = match;
|
|
1080
|
+
if (!key || !label || match.index === void 0) continue;
|
|
1081
|
+
tokens.push({
|
|
1082
|
+
index: match.index,
|
|
1083
|
+
raw,
|
|
1084
|
+
key: key.toLowerCase(),
|
|
1085
|
+
label: label.trim()
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
return tokens;
|
|
1089
|
+
}
|
|
1090
|
+
function internalContentKeyFromPath(path) {
|
|
1091
|
+
if (!path) return null;
|
|
1092
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
1093
|
+
try {
|
|
1094
|
+
return decodeURIComponent(segment).trim().toLowerCase();
|
|
1095
|
+
} catch {
|
|
1096
|
+
return "";
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
if (segments.length === 0 || segments.some((segment) => segment === "" || segment.includes("."))) {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
return segments.join(".");
|
|
1103
|
+
}
|
|
1104
|
+
function internalContentPathIndex(entries) {
|
|
1105
|
+
const index = /* @__PURE__ */ new Map();
|
|
1106
|
+
const previous = /* @__PURE__ */ new Map();
|
|
1107
|
+
for (const entry of entries) {
|
|
1108
|
+
const path = entry.path;
|
|
1109
|
+
const key = internalContentKeyFromPath(path);
|
|
1110
|
+
if (!path || !key) continue;
|
|
1111
|
+
index.set(key, path);
|
|
1112
|
+
const segments = key.split(".");
|
|
1113
|
+
for (const oldSlug of entry.previousSlugs ?? []) {
|
|
1114
|
+
const oldKey = [...segments.slice(0, -1), oldSlug.trim().toLowerCase()].join(".");
|
|
1115
|
+
if (oldKey && !oldKey.includes("..")) previous.set(oldKey, path);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
for (const [key, path] of previous) {
|
|
1119
|
+
if (!index.has(key)) index.set(key, path);
|
|
1120
|
+
}
|
|
1121
|
+
return index;
|
|
1122
|
+
}
|
|
1123
|
+
function internalContentLinkResolverFrom(source) {
|
|
1124
|
+
if (!source) return () => null;
|
|
1125
|
+
if (typeof source === "function") return source;
|
|
1126
|
+
return (key) => source.get(key.toLowerCase()) ?? null;
|
|
1127
|
+
}
|
|
1128
|
+
function isSafeInternalPath(path) {
|
|
1129
|
+
return typeof path === "string" && path.startsWith("/") && !path.startsWith("//");
|
|
1130
|
+
}
|
|
1131
|
+
function escapeHtmlText(value) {
|
|
1132
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1133
|
+
}
|
|
1134
|
+
var INTERNAL_CONTENT_LINK_CLASS = "rt-internal-link";
|
|
1135
|
+
var INTERNAL_CONTENT_LINK_PENDING_CLASS = "rt-internal-link--pending";
|
|
1136
|
+
var INTERNAL_CONTENT_LINK_PENDING_ATTR = "data-rt-internal-link-pending";
|
|
1137
|
+
function renderInternalContentLinksInText(text, resolve) {
|
|
1138
|
+
const tokens = findInternalContentLinkTokens(text);
|
|
1139
|
+
if (tokens.length === 0) return { html: text, hasTokens: false };
|
|
1140
|
+
let cursor = 0;
|
|
1141
|
+
let html = "";
|
|
1142
|
+
for (const token of tokens) {
|
|
1143
|
+
html += text.slice(cursor, token.index);
|
|
1144
|
+
const path = resolve(token.key);
|
|
1145
|
+
const label = escapeHtmlText(token.label);
|
|
1146
|
+
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>`;
|
|
1147
|
+
cursor = token.index + token.raw.length;
|
|
1148
|
+
}
|
|
1149
|
+
html += text.slice(cursor);
|
|
1150
|
+
return { html, hasTokens: true };
|
|
1151
|
+
}
|
|
1152
|
+
var INTERNAL_LINK_BLOCKED_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
1153
|
+
"a",
|
|
1154
|
+
"code",
|
|
1155
|
+
"pre",
|
|
1156
|
+
"script",
|
|
1157
|
+
"style",
|
|
1158
|
+
"textarea"
|
|
1159
|
+
]);
|
|
1160
|
+
var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "input", "source", "wbr"]);
|
|
1161
|
+
function htmlTagEnd(html, start) {
|
|
1162
|
+
let quote = null;
|
|
1163
|
+
for (let index = start + 1; index < html.length; index += 1) {
|
|
1164
|
+
const character = html[index];
|
|
1165
|
+
if (quote) {
|
|
1166
|
+
if (character === quote) quote = null;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (character === '"' || character === "'") {
|
|
1170
|
+
quote = character;
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (character === ">") return index;
|
|
1174
|
+
}
|
|
1175
|
+
return -1;
|
|
1176
|
+
}
|
|
1177
|
+
function renderInternalContentLinksInHtml(html, resolve) {
|
|
1178
|
+
if (!html.includes("[[internal:")) return { html, hasTokens: false };
|
|
1179
|
+
let blockedDepth = 0;
|
|
1180
|
+
let cursor = 0;
|
|
1181
|
+
let out = "";
|
|
1182
|
+
let hasTokens = false;
|
|
1183
|
+
const emitText = (text) => {
|
|
1184
|
+
if (blockedDepth > 0 || !text) {
|
|
1185
|
+
out += text;
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const rendered = renderInternalContentLinksInText(text, resolve);
|
|
1189
|
+
hasTokens ||= rendered.hasTokens;
|
|
1190
|
+
out += rendered.html;
|
|
1191
|
+
};
|
|
1192
|
+
while (cursor < html.length) {
|
|
1193
|
+
const tagStart = html.indexOf("<", cursor);
|
|
1194
|
+
if (tagStart < 0) {
|
|
1195
|
+
emitText(html.slice(cursor));
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
emitText(html.slice(cursor, tagStart));
|
|
1199
|
+
const tagEnd = htmlTagEnd(html, tagStart);
|
|
1200
|
+
if (tagEnd < 0) {
|
|
1201
|
+
out += html.slice(tagStart);
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
const tag = html.slice(tagStart, tagEnd + 1);
|
|
1205
|
+
out += tag;
|
|
1206
|
+
const parsed = /^<\s*(\/?)\s*([a-z0-9-]+)/i.exec(tag);
|
|
1207
|
+
const tagName = parsed?.[2]?.toLowerCase();
|
|
1208
|
+
if (tagName && INTERNAL_LINK_BLOCKED_HTML_TAGS.has(tagName)) {
|
|
1209
|
+
const isClosing = parsed?.[1] === "/";
|
|
1210
|
+
const isVoid = VOID_HTML_TAGS.has(tagName) || /\/\s*>$/.test(tag);
|
|
1211
|
+
if (isClosing) blockedDepth = Math.max(0, blockedDepth - 1);
|
|
1212
|
+
else if (!isVoid) blockedDepth += 1;
|
|
1213
|
+
}
|
|
1214
|
+
cursor = tagEnd + 1;
|
|
1215
|
+
}
|
|
1216
|
+
return { html: out, hasTokens };
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1044
1219
|
// src/preview-token.ts
|
|
1045
1220
|
var import_node_crypto = require("crypto");
|
|
1046
1221
|
var DELIMITER = ".";
|
|
@@ -1348,15 +1523,170 @@ function ensureStableIds(block) {
|
|
|
1348
1523
|
function injectStableIdsTree(blocks) {
|
|
1349
1524
|
return blocks.map((b) => ensureStableIds(b));
|
|
1350
1525
|
}
|
|
1526
|
+
|
|
1527
|
+
// src/site-patterns.ts
|
|
1528
|
+
var SITE_PATTERN_SLOTS = [
|
|
1529
|
+
{ key: "post_footer", label: "\uAE00 \uD558\uB2E8", description: "\uAE00 \uBCF8\uBB38 \uBC14\uB85C \uC544\uB798" }
|
|
1530
|
+
];
|
|
1531
|
+
var POST_FOOTER_SLOT = "post_footer";
|
|
1532
|
+
var SITE_PATTERN_PLACEMENTS_KEY = "site_pattern_placements";
|
|
1533
|
+
var SITE_PATTERN_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
1534
|
+
function isValidSitePatternKey(key) {
|
|
1535
|
+
return SITE_PATTERN_KEY_PATTERN.test(key);
|
|
1536
|
+
}
|
|
1537
|
+
function isSitePatternSlotKey(value) {
|
|
1538
|
+
return SITE_PATTERN_SLOTS.some((slot) => slot.key === value);
|
|
1539
|
+
}
|
|
1540
|
+
var EMPTY_SITE_PATTERN_PLACEMENTS = { rules: [] };
|
|
1541
|
+
function isRecord(value) {
|
|
1542
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1543
|
+
}
|
|
1544
|
+
function readNullableKey(value) {
|
|
1545
|
+
if (value === null) return null;
|
|
1546
|
+
if (typeof value === "string") {
|
|
1547
|
+
const trimmed = value.trim();
|
|
1548
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1549
|
+
}
|
|
1550
|
+
return void 0;
|
|
1551
|
+
}
|
|
1552
|
+
function readSitePatternPlacements(raw) {
|
|
1553
|
+
let value = raw;
|
|
1554
|
+
if (typeof value === "string") {
|
|
1555
|
+
try {
|
|
1556
|
+
value = JSON.parse(value);
|
|
1557
|
+
} catch {
|
|
1558
|
+
return EMPTY_SITE_PATTERN_PLACEMENTS;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (!isRecord(value) || !Array.isArray(value.rules)) {
|
|
1562
|
+
return EMPTY_SITE_PATTERN_PLACEMENTS;
|
|
1563
|
+
}
|
|
1564
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1565
|
+
for (const item of value.rules) {
|
|
1566
|
+
if (!isRecord(item)) continue;
|
|
1567
|
+
const slot = typeof item.slot === "string" ? item.slot : "";
|
|
1568
|
+
if (!isSitePatternSlotKey(slot)) continue;
|
|
1569
|
+
const collectionKey = readNullableKey(item.collectionKey);
|
|
1570
|
+
if (collectionKey === void 0) continue;
|
|
1571
|
+
const patternKey = readNullableKey(item.patternKey);
|
|
1572
|
+
if (patternKey === void 0) continue;
|
|
1573
|
+
if (patternKey !== null && !isValidSitePatternKey(patternKey)) continue;
|
|
1574
|
+
byTarget.set(`${slot}\0${collectionKey ?? ""}`, {
|
|
1575
|
+
slot,
|
|
1576
|
+
collectionKey,
|
|
1577
|
+
patternKey
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
return { rules: [...byTarget.values()] };
|
|
1581
|
+
}
|
|
1582
|
+
function findSitePatternPlacementRule(placements, slot, collectionKey) {
|
|
1583
|
+
return placements.rules.find(
|
|
1584
|
+
(rule) => rule.slot === slot && rule.collectionKey === collectionKey
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
function setSitePatternPlacementRule(placements, target, patternKey) {
|
|
1588
|
+
const rest = placements.rules.filter(
|
|
1589
|
+
(rule) => !(rule.slot === target.slot && rule.collectionKey === target.collectionKey)
|
|
1590
|
+
);
|
|
1591
|
+
if (patternKey === void 0) return { rules: rest };
|
|
1592
|
+
return {
|
|
1593
|
+
rules: [
|
|
1594
|
+
...rest,
|
|
1595
|
+
{ slot: target.slot, collectionKey: target.collectionKey, patternKey }
|
|
1596
|
+
]
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
function emptyPatternSlots() {
|
|
1600
|
+
return Object.fromEntries(SITE_PATTERN_SLOTS.map((slot) => [slot.key, null]));
|
|
1601
|
+
}
|
|
1602
|
+
function resolvePatternSlots(post, placements, publishedKeys) {
|
|
1603
|
+
const slots = emptyPatternSlots();
|
|
1604
|
+
for (const slot of SITE_PATTERN_SLOTS) {
|
|
1605
|
+
const specific = post.collectionKey !== null ? findSitePatternPlacementRule(placements, slot.key, post.collectionKey) : void 0;
|
|
1606
|
+
const rule = specific ?? findSitePatternPlacementRule(placements, slot.key, null);
|
|
1607
|
+
const key = rule?.patternKey ?? null;
|
|
1608
|
+
slots[slot.key] = key !== null && publishedKeys.has(key) ? key : null;
|
|
1609
|
+
}
|
|
1610
|
+
return slots;
|
|
1611
|
+
}
|
|
1612
|
+
function referencedSitePatternKeys(placements) {
|
|
1613
|
+
return new Set(
|
|
1614
|
+
placements.rules.map((rule) => rule.patternKey).filter((key) => key !== null)
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
var DEFAULT_SITE_PATTERN_PRESENTATION = {
|
|
1618
|
+
layout: "plain",
|
|
1619
|
+
background: "#f8f9fa",
|
|
1620
|
+
linkStyle: "text",
|
|
1621
|
+
buttonColors: ["#03c75a", "#1a1a1a", "#666666"]
|
|
1622
|
+
};
|
|
1623
|
+
var SITE_PATTERN_BUTTON_COLORS_MAX = 4;
|
|
1624
|
+
var HEX_COLOR_RE2 = /^#[0-9a-f]{6}$/;
|
|
1625
|
+
function normalizeHexColor(value) {
|
|
1626
|
+
if (typeof value !== "string") return null;
|
|
1627
|
+
let hex = value.trim().toLowerCase();
|
|
1628
|
+
if (/^#[0-9a-f]{3}$/.test(hex)) hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
|
|
1629
|
+
return HEX_COLOR_RE2.test(hex) ? hex : null;
|
|
1630
|
+
}
|
|
1631
|
+
function readSitePatternPresentation(raw) {
|
|
1632
|
+
let value = raw;
|
|
1633
|
+
if (typeof value === "string") {
|
|
1634
|
+
try {
|
|
1635
|
+
value = JSON.parse(value);
|
|
1636
|
+
} catch {
|
|
1637
|
+
value = null;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
const base = DEFAULT_SITE_PATTERN_PRESENTATION;
|
|
1641
|
+
if (!isRecord(value)) return base;
|
|
1642
|
+
const layout = value.layout === "card" ? "card" : "plain";
|
|
1643
|
+
const linkStyle = value.linkStyle === "buttons" ? "buttons" : "text";
|
|
1644
|
+
const background = normalizeHexColor(value.background) ?? base.background;
|
|
1645
|
+
const colors = Array.isArray(value.buttonColors) ? value.buttonColors.map(normalizeHexColor).filter((c) => c !== null).slice(0, SITE_PATTERN_BUTTON_COLORS_MAX) : [];
|
|
1646
|
+
return {
|
|
1647
|
+
layout,
|
|
1648
|
+
background,
|
|
1649
|
+
linkStyle,
|
|
1650
|
+
buttonColors: colors.length > 0 ? colors : base.buttonColors
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
function sitePatternButtonColor(presentation, index) {
|
|
1654
|
+
const colors = presentation.buttonColors;
|
|
1655
|
+
return colors[Math.min(index, colors.length - 1)] ?? DEFAULT_SITE_PATTERN_PRESENTATION.buttonColors[0];
|
|
1656
|
+
}
|
|
1657
|
+
function sitePatternPresentationAttributes(presentation) {
|
|
1658
|
+
const cssVariables = { "--rt-pattern-bg": presentation.background };
|
|
1659
|
+
presentation.buttonColors.forEach((color, index) => {
|
|
1660
|
+
cssVariables[`--rt-pattern-btn-${index + 1}`] = color;
|
|
1661
|
+
});
|
|
1662
|
+
return {
|
|
1663
|
+
dataset: {
|
|
1664
|
+
"data-pattern-layout": presentation.layout,
|
|
1665
|
+
"data-pattern-link-style": presentation.linkStyle
|
|
1666
|
+
},
|
|
1667
|
+
cssVariables
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1351
1670
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1352
1671
|
0 && (module.exports = {
|
|
1353
1672
|
BLOCK_NAME_PATTERN,
|
|
1673
|
+
DEFAULT_SITE_PATTERN_PRESENTATION,
|
|
1674
|
+
EMPTY_SITE_PATTERN_PLACEMENTS,
|
|
1354
1675
|
FIELD_BINDING_SOURCE,
|
|
1355
1676
|
FIELD_BINDING_SOURCE_ALIAS,
|
|
1356
1677
|
IMAGE_ALT_MAX_LENGTH,
|
|
1678
|
+
INTERNAL_CONTENT_LINK_CLASS,
|
|
1679
|
+
INTERNAL_CONTENT_LINK_PENDING_ATTR,
|
|
1680
|
+
INTERNAL_CONTENT_LINK_PENDING_CLASS,
|
|
1681
|
+
INTERNAL_CONTENT_LINK_TOKEN_SOURCE,
|
|
1357
1682
|
LEGACY_BLOG_DETAIL_PATH,
|
|
1358
1683
|
PHASE_1_CORE_BLOCKS,
|
|
1359
1684
|
PHASE_1_CSP,
|
|
1685
|
+
POST_FOOTER_SLOT,
|
|
1686
|
+
SITE_PATTERN_BUTTON_COLORS_MAX,
|
|
1687
|
+
SITE_PATTERN_KEY_PATTERN,
|
|
1688
|
+
SITE_PATTERN_PLACEMENTS_KEY,
|
|
1689
|
+
SITE_PATTERN_SLOTS,
|
|
1360
1690
|
applyBlockBindings,
|
|
1361
1691
|
bindingSrcValue,
|
|
1362
1692
|
bindingTextValue,
|
|
@@ -1364,27 +1694,42 @@ function injectStableIdsTree(blocks) {
|
|
|
1364
1694
|
boundFieldValue,
|
|
1365
1695
|
chainResolvers,
|
|
1366
1696
|
createFieldBindingResolver,
|
|
1697
|
+
emptyPatternSlots,
|
|
1367
1698
|
ensureImportedHtmlDocumentRoots,
|
|
1368
1699
|
ensureStableIds,
|
|
1369
1700
|
extractFaqEntries,
|
|
1370
1701
|
faqPageJsonLd,
|
|
1702
|
+
findInternalContentLinkTokens,
|
|
1703
|
+
findSitePatternPlacementRule,
|
|
1371
1704
|
htmlToPlainText,
|
|
1372
1705
|
injectStableIdsTree,
|
|
1706
|
+
internalContentKeyFromPath,
|
|
1707
|
+
internalContentLinkResolverFrom,
|
|
1708
|
+
internalContentPathIndex,
|
|
1373
1709
|
isAllowedEmbedSrc,
|
|
1374
1710
|
isAllowedMapEmbedSrc,
|
|
1375
1711
|
isInCollectionMode,
|
|
1712
|
+
isSitePatternSlotKey,
|
|
1376
1713
|
isValidBlock,
|
|
1714
|
+
isValidSitePatternKey,
|
|
1377
1715
|
issuePreviewToken,
|
|
1378
1716
|
normalizeEmbed,
|
|
1717
|
+
normalizeHexColor,
|
|
1379
1718
|
normalizeImageAlt,
|
|
1380
1719
|
parseBlock,
|
|
1381
1720
|
postsForCollection,
|
|
1382
1721
|
postsInAnyCollection,
|
|
1383
1722
|
postsNotInAnyCollection,
|
|
1723
|
+
readSitePatternPlacements,
|
|
1724
|
+
readSitePatternPresentation,
|
|
1725
|
+
referencedSitePatternKeys,
|
|
1726
|
+
renderInternalContentLinksInHtml,
|
|
1727
|
+
renderInternalContentLinksInText,
|
|
1384
1728
|
resolveCategoryPath,
|
|
1385
1729
|
resolveCategoryTreeChain,
|
|
1386
1730
|
resolveCategoryTreeEntryPath,
|
|
1387
1731
|
resolveCategoryTreeHubPaths,
|
|
1732
|
+
resolvePatternSlots,
|
|
1388
1733
|
resolvePostCategorySlug,
|
|
1389
1734
|
resolvePostCollection,
|
|
1390
1735
|
resolvePostPath,
|
|
@@ -1395,6 +1740,9 @@ function injectStableIdsTree(blocks) {
|
|
|
1395
1740
|
sanitizeImportedHtml,
|
|
1396
1741
|
scopeImportedCss,
|
|
1397
1742
|
serializeBlock,
|
|
1743
|
+
setSitePatternPlacementRule,
|
|
1744
|
+
sitePatternButtonColor,
|
|
1745
|
+
sitePatternPresentationAttributes,
|
|
1398
1746
|
validateCollections,
|
|
1399
1747
|
verifyPreviewToken
|
|
1400
1748
|
});
|