@roottale/cms-core 0.65.0 → 0.66.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @roottale/cms-core
2
2
 
3
+ ## 0.66.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a58c5d2: 계층형 분류 모델(`category_tree`)에 `entryCategory: "leaf"`를 추가한다. 하위 분류가 없는
8
+ 분류면 단계와 무관하게 글을 붙일 수 있고(`categoryDepth`는 최대 단계), 새로 고를 때는
9
+ `resolveCategoryTreeSelection`으로 하위가 없는지 확인한다. 생략하면 기존처럼
10
+ `categoryDepth` 단계 말단에만 붙는다.
11
+
12
+ ### Patch Changes
13
+
14
+ - d5ef48e: `basePath: "/"`인 계층형 분류 모델(`category_tree`)도 허브·상세 주소를 계산합니다. 이전에는 루트를 빈 basePath(주소 없음)와 같게 보아 `resolveCategoryTreeHubPaths`·`resolveCategoryTreeEntryPath`·`resolvePostPublicPath`가 `null`을 돌려주었고, 웹훅에 상세 경로가 빠져 글 상세 캐시가 갱신되지 않았습니다.
15
+
16
+ ## 0.65.1
17
+
3
18
  ## 0.65.0
4
19
 
5
20
  ## 0.64.0
package/dist/index.cjs CHANGED
@@ -103,6 +103,7 @@ __export(src_exports, {
103
103
  resolveCategoryTreeChain: () => resolveCategoryTreeChain,
104
104
  resolveCategoryTreeEntryPath: () => resolveCategoryTreeEntryPath,
105
105
  resolveCategoryTreeHubPaths: () => resolveCategoryTreeHubPaths,
106
+ resolveCategoryTreeSelection: () => resolveCategoryTreeSelection,
106
107
  resolvePatternSlots: () => resolvePatternSlots,
107
108
  resolvePostCategorySlug: () => resolvePostCategorySlug,
108
109
  resolvePostCollection: () => resolvePostCollection,
@@ -349,39 +350,51 @@ function validateCollections(collections) {
349
350
  }
350
351
 
351
352
  // src/category-tree.ts
352
- function normalizeBasePath(basePath) {
353
+ function basePathPrefix(basePath) {
354
+ if (!basePath.trim()) return null;
353
355
  return basePath.replace(/\/+$/, "");
354
356
  }
355
- function resolveCategoryTreeChain(presentation, leafId, terms) {
356
- if (!Number.isInteger(presentation.categoryDepth) || presentation.categoryDepth < 1) {
357
- return null;
358
- }
357
+ function categoryTreeAncestry(categoryDepth, termId, terms) {
358
+ if (!Number.isInteger(categoryDepth) || categoryDepth < 1) return null;
359
359
  const termsById = new Map(terms.map((term) => [term.id, term]));
360
360
  if (termsById.size !== terms.length) return null;
361
- if (terms.some((term) => term.parentId === leafId)) return null;
362
361
  const reversed = [];
363
362
  const visited = /* @__PURE__ */ new Set();
364
- let currentId = leafId;
363
+ let currentId = termId;
365
364
  while (currentId) {
366
365
  if (visited.has(currentId)) return null;
367
366
  visited.add(currentId);
368
367
  const current = termsById.get(currentId);
369
368
  if (!current || !current.slug) return null;
370
369
  reversed.push(current);
370
+ if (reversed.length > categoryDepth) return null;
371
371
  currentId = current.parentId ?? null;
372
372
  }
373
- if (reversed.length !== presentation.categoryDepth) return null;
374
373
  return reversed.reverse();
375
374
  }
375
+ function hasChildCategory(termId, terms) {
376
+ return terms.some((term) => term.parentId === termId);
377
+ }
378
+ function resolveCategoryTreeChain(presentation, termId, terms) {
379
+ const chain = categoryTreeAncestry(presentation.categoryDepth, termId, terms);
380
+ if (!chain) return null;
381
+ if (presentation.entryCategory === "leaf") return chain;
382
+ if (hasChildCategory(termId, terms)) return null;
383
+ return chain.length === presentation.categoryDepth ? chain : null;
384
+ }
385
+ function resolveCategoryTreeSelection(presentation, termId, terms) {
386
+ if (hasChildCategory(termId, terms)) return null;
387
+ return resolveCategoryTreeChain(presentation, termId, terms);
388
+ }
376
389
  function resolveCategoryTreeHubPaths(presentation, termId, terms) {
377
390
  const chain = resolveCategoryTreeChain(presentation, termId, terms);
378
- const basePath = normalizeBasePath(presentation.basePath);
379
- if (!chain || !basePath) return null;
380
- const paths = [basePath];
391
+ const prefix = basePathPrefix(presentation.basePath);
392
+ if (!chain || prefix === null) return null;
393
+ const paths = [prefix || "/"];
381
394
  const segments = [];
382
395
  for (const term of chain) {
383
396
  segments.push(encodeURIComponent(term.slug));
384
- paths.push(`${basePath}/${segments.join("/")}`);
397
+ paths.push(`${prefix}/${segments.join("/")}`);
385
398
  }
386
399
  return paths;
387
400
  }
@@ -2037,6 +2050,7 @@ function formatSectionLockViolations(violations) {
2037
2050
  resolveCategoryTreeChain,
2038
2051
  resolveCategoryTreeEntryPath,
2039
2052
  resolveCategoryTreeHubPaths,
2053
+ resolveCategoryTreeSelection,
2040
2054
  resolvePatternSlots,
2041
2055
  resolvePostCategorySlug,
2042
2056
  resolvePostCollection,