@roottale/cms-core 0.53.1 → 0.56.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 +18 -0
- package/dist/index.cjs +138 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -2
- package/dist/index.d.ts +105 -2
- package/dist/index.js +131 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @roottale/cms-core
|
|
2
2
|
|
|
3
|
+
## 0.56.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9eb39e8: ADR-0105 개정 — 글의 정규 공개 경로 계산기 `resolvePostPublicPath` 를 추가합니다.
|
|
8
|
+
모델 presentation(fixed_page·detail·category_tree) → 호환 컬렉션 → 레거시 `/blog` 순서
|
|
9
|
+
하나로 계산하며, 플랫폼의 웹훅·사이트맵·단축링크·내부 링크 키·`posts.public_path`
|
|
10
|
+
물질화가 모두 이 함수를 씁니다.
|
|
11
|
+
|
|
12
|
+
## 0.55.0
|
|
13
|
+
|
|
14
|
+
## 0.54.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- befa5bb: 콘텐츠 유형별 카테고리 주소와 카테고리 SEO 공개 필드에 더해, 1~3단계 부모·자식
|
|
19
|
+
분류로 목록·허브·상세 주소를 구성하는 범용 콘텐츠 모델 계약을 추가합니다.
|
|
20
|
+
|
|
3
21
|
## 0.53.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(src_exports, {
|
|
|
34
34
|
FIELD_BINDING_SOURCE: () => FIELD_BINDING_SOURCE,
|
|
35
35
|
FIELD_BINDING_SOURCE_ALIAS: () => FIELD_BINDING_SOURCE_ALIAS,
|
|
36
36
|
IMAGE_ALT_MAX_LENGTH: () => IMAGE_ALT_MAX_LENGTH,
|
|
37
|
+
LEGACY_BLOG_DETAIL_PATH: () => LEGACY_BLOG_DETAIL_PATH,
|
|
37
38
|
PHASE_1_CORE_BLOCKS: () => PHASE_1_CORE_BLOCKS,
|
|
38
39
|
PHASE_1_CSP: () => PHASE_1_CSP,
|
|
39
40
|
applyBlockBindings: () => applyBlockBindings,
|
|
@@ -60,8 +61,14 @@ __export(src_exports, {
|
|
|
60
61
|
postsForCollection: () => postsForCollection,
|
|
61
62
|
postsInAnyCollection: () => postsInAnyCollection,
|
|
62
63
|
postsNotInAnyCollection: () => postsNotInAnyCollection,
|
|
64
|
+
resolveCategoryPath: () => resolveCategoryPath,
|
|
65
|
+
resolveCategoryTreeChain: () => resolveCategoryTreeChain,
|
|
66
|
+
resolveCategoryTreeEntryPath: () => resolveCategoryTreeEntryPath,
|
|
67
|
+
resolveCategoryTreeHubPaths: () => resolveCategoryTreeHubPaths,
|
|
68
|
+
resolvePostCategorySlug: () => resolvePostCategorySlug,
|
|
63
69
|
resolvePostCollection: () => resolvePostCollection,
|
|
64
70
|
resolvePostPath: () => resolvePostPath,
|
|
71
|
+
resolvePostPublicPath: () => resolvePostPublicPath,
|
|
65
72
|
resolvePostUrl: () => resolvePostUrl,
|
|
66
73
|
sanitizeHtml: () => sanitizeHtml,
|
|
67
74
|
sanitizeImportedCss: () => sanitizeImportedCss,
|
|
@@ -202,18 +209,38 @@ function resolvePostCollection(post, collections) {
|
|
|
202
209
|
return collections.find((c) => c.key === post.collectionKey) ?? null;
|
|
203
210
|
}
|
|
204
211
|
function resolvePostUrl(siteUrl, post, collections) {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
const path = resolvePostPath(post, collections);
|
|
213
|
+
return path ? `${trimTrailingSlash(siteUrl)}${path}` : null;
|
|
214
|
+
}
|
|
215
|
+
function resolvePostCategorySlug(post, collection) {
|
|
216
|
+
const allowed = new Set(collection.categories);
|
|
217
|
+
const slugs = [
|
|
218
|
+
...new Set(
|
|
219
|
+
post.terms.filter(
|
|
220
|
+
(term) => term.taxonomy === "category" && allowed.has(term.slug)
|
|
221
|
+
).map((term) => term.slug)
|
|
222
|
+
)
|
|
223
|
+
];
|
|
224
|
+
return slugs.length === 1 ? slugs[0] : null;
|
|
225
|
+
}
|
|
226
|
+
function resolveCategoryPath(collection, categorySlug) {
|
|
227
|
+
if (!collection.archives || !collection.basePath || !categorySlug) return null;
|
|
228
|
+
const basePath = trimTrailingSlash(collection.basePath);
|
|
229
|
+
const encodedSlug = encodeURIComponent(categorySlug);
|
|
230
|
+
return collection.categoryPath === "direct" ? `${basePath}/${encodedSlug}` : `${basePath}/categories/${encodedSlug}`;
|
|
210
231
|
}
|
|
211
232
|
function resolvePostPath(post, collections) {
|
|
212
233
|
const collection = resolvePostCollection(post, collections);
|
|
213
234
|
if (!collection) return null;
|
|
214
235
|
if (collection.detail === false) return null;
|
|
215
236
|
if (!collection.basePath) return null;
|
|
216
|
-
|
|
237
|
+
const basePath = trimTrailingSlash(collection.basePath);
|
|
238
|
+
const encodedPostSlug = encodeURIComponent(post.slug);
|
|
239
|
+
if (collection.detailPath !== "category") {
|
|
240
|
+
return `${basePath}/${encodedPostSlug}`;
|
|
241
|
+
}
|
|
242
|
+
const categorySlug = resolvePostCategorySlug(post, collection);
|
|
243
|
+
return categorySlug ? `${basePath}/${encodeURIComponent(categorySlug)}/${encodedPostSlug}` : null;
|
|
217
244
|
}
|
|
218
245
|
function postsForCollection(posts, collections, key) {
|
|
219
246
|
return posts.filter((p) => resolvePostCollection(p, collections)?.key === key);
|
|
@@ -253,10 +280,108 @@ function validateCollections(collections) {
|
|
|
253
280
|
}
|
|
254
281
|
categoryOwner.set(slug, c.key);
|
|
255
282
|
}
|
|
283
|
+
if (c.detailPath === "category") {
|
|
284
|
+
if (!c.basePath) {
|
|
285
|
+
problems.push(
|
|
286
|
+
`category \uC0C1\uC138 \uACBD\uB85C\uB294 basePath \uAC00 \uD544\uC694\uD568: "${c.key}"`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (c.detail === false) {
|
|
290
|
+
problems.push(
|
|
291
|
+
`category \uC0C1\uC138 \uACBD\uB85C\uB294 \uAE00\uBCC4 \uC0C1\uC138 \uD398\uC774\uC9C0\uAC00 \uD544\uC694\uD568: "${c.key}"`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (!c.archives) {
|
|
295
|
+
problems.push(
|
|
296
|
+
`category \uC0C1\uC138 \uACBD\uB85C\uB294 \uCE74\uD14C\uACE0\uB9AC \uC544\uCE74\uC774\uBE0C\uAC00 \uD544\uC694\uD568: "${c.key}"`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (c.categoryCardinality !== "exactly-one") {
|
|
300
|
+
problems.push(
|
|
301
|
+
`category \uC0C1\uC138 \uACBD\uB85C\uB294 categoryCardinality "exactly-one" \uC774 \uD544\uC694\uD568: "${c.key}"`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
256
305
|
}
|
|
257
306
|
return problems;
|
|
258
307
|
}
|
|
259
308
|
|
|
309
|
+
// src/category-tree.ts
|
|
310
|
+
function normalizeBasePath(basePath) {
|
|
311
|
+
return basePath.replace(/\/+$/, "");
|
|
312
|
+
}
|
|
313
|
+
function resolveCategoryTreeChain(presentation, leafId, terms) {
|
|
314
|
+
if (!Number.isInteger(presentation.categoryDepth) || presentation.categoryDepth < 1) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const termsById = new Map(terms.map((term) => [term.id, term]));
|
|
318
|
+
if (termsById.size !== terms.length) return null;
|
|
319
|
+
if (terms.some((term) => term.parentId === leafId)) return null;
|
|
320
|
+
const reversed = [];
|
|
321
|
+
const visited = /* @__PURE__ */ new Set();
|
|
322
|
+
let currentId = leafId;
|
|
323
|
+
while (currentId) {
|
|
324
|
+
if (visited.has(currentId)) return null;
|
|
325
|
+
visited.add(currentId);
|
|
326
|
+
const current = termsById.get(currentId);
|
|
327
|
+
if (!current || !current.slug) return null;
|
|
328
|
+
reversed.push(current);
|
|
329
|
+
currentId = current.parentId ?? null;
|
|
330
|
+
}
|
|
331
|
+
if (reversed.length !== presentation.categoryDepth) return null;
|
|
332
|
+
return reversed.reverse();
|
|
333
|
+
}
|
|
334
|
+
function resolveCategoryTreeHubPaths(presentation, termId, terms) {
|
|
335
|
+
const chain = resolveCategoryTreeChain(presentation, termId, terms);
|
|
336
|
+
const basePath = normalizeBasePath(presentation.basePath);
|
|
337
|
+
if (!chain || !basePath) return null;
|
|
338
|
+
const paths = [basePath];
|
|
339
|
+
const segments = [];
|
|
340
|
+
for (const term of chain) {
|
|
341
|
+
segments.push(encodeURIComponent(term.slug));
|
|
342
|
+
paths.push(`${basePath}/${segments.join("/")}`);
|
|
343
|
+
}
|
|
344
|
+
return paths;
|
|
345
|
+
}
|
|
346
|
+
function resolveCategoryTreeEntryPath(presentation, entry, terms) {
|
|
347
|
+
if (!entry.slug) return null;
|
|
348
|
+
const knownIds = new Set(terms.map((term) => term.id));
|
|
349
|
+
const categoryIds = [
|
|
350
|
+
...new Set(
|
|
351
|
+
entry.terms.filter((term) => term.taxonomy === "category" && knownIds.has(term.id)).map((term) => term.id)
|
|
352
|
+
)
|
|
353
|
+
];
|
|
354
|
+
if (categoryIds.length !== 1) return null;
|
|
355
|
+
const hubs = resolveCategoryTreeHubPaths(
|
|
356
|
+
presentation,
|
|
357
|
+
categoryIds[0],
|
|
358
|
+
terms
|
|
359
|
+
);
|
|
360
|
+
const leafPath = hubs?.at(-1);
|
|
361
|
+
return leafPath ? `${leafPath}/${encodeURIComponent(entry.slug)}` : null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/public-path.ts
|
|
365
|
+
var LEGACY_BLOG_DETAIL_PATH = "/blog";
|
|
366
|
+
function resolvePostPublicPath(post, context) {
|
|
367
|
+
const presentation = context.presentation;
|
|
368
|
+
if (presentation && presentation.kind !== "data_only") {
|
|
369
|
+
if (presentation.kind === "fixed_page") return presentation.path;
|
|
370
|
+
if (presentation.kind === "detail") {
|
|
371
|
+
if (!presentation.detailPath.includes(":slug")) return null;
|
|
372
|
+
return presentation.detailPath.replace(":slug", encodeURIComponent(post.slug));
|
|
373
|
+
}
|
|
374
|
+
return resolveCategoryTreeEntryPath(presentation, post, context.categories);
|
|
375
|
+
}
|
|
376
|
+
const collectionKey = post.collectionKey ?? (presentation ? context.compatibilityCollectionKey ?? post.modelKey ?? null : null);
|
|
377
|
+
const routable = { slug: post.slug, terms: post.terms, collectionKey };
|
|
378
|
+
if (!resolvePostCollection(routable, context.collections)) {
|
|
379
|
+
if (presentation) return null;
|
|
380
|
+
return `${LEGACY_BLOG_DETAIL_PATH}/${post.slug}`;
|
|
381
|
+
}
|
|
382
|
+
return resolvePostPath(routable, context.collections);
|
|
383
|
+
}
|
|
384
|
+
|
|
260
385
|
// src/delimiter.ts
|
|
261
386
|
function serializeBlock(block) {
|
|
262
387
|
const exportable = sanitizeAttributesForExport(block);
|
|
@@ -1229,6 +1354,7 @@ function injectStableIdsTree(blocks) {
|
|
|
1229
1354
|
FIELD_BINDING_SOURCE,
|
|
1230
1355
|
FIELD_BINDING_SOURCE_ALIAS,
|
|
1231
1356
|
IMAGE_ALT_MAX_LENGTH,
|
|
1357
|
+
LEGACY_BLOG_DETAIL_PATH,
|
|
1232
1358
|
PHASE_1_CORE_BLOCKS,
|
|
1233
1359
|
PHASE_1_CSP,
|
|
1234
1360
|
applyBlockBindings,
|
|
@@ -1255,8 +1381,14 @@ function injectStableIdsTree(blocks) {
|
|
|
1255
1381
|
postsForCollection,
|
|
1256
1382
|
postsInAnyCollection,
|
|
1257
1383
|
postsNotInAnyCollection,
|
|
1384
|
+
resolveCategoryPath,
|
|
1385
|
+
resolveCategoryTreeChain,
|
|
1386
|
+
resolveCategoryTreeEntryPath,
|
|
1387
|
+
resolveCategoryTreeHubPaths,
|
|
1388
|
+
resolvePostCategorySlug,
|
|
1258
1389
|
resolvePostCollection,
|
|
1259
1390
|
resolvePostPath,
|
|
1391
|
+
resolvePostPublicPath,
|
|
1260
1392
|
resolvePostUrl,
|
|
1261
1393
|
sanitizeHtml,
|
|
1262
1394
|
sanitizeImportedCss,
|