@roottale/cms-core 0.53.1 → 0.55.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,14 @@
1
1
  # @roottale/cms-core
2
2
 
3
+ ## 0.55.0
4
+
5
+ ## 0.54.0
6
+
7
+ ### Minor Changes
8
+
9
+ - befa5bb: 콘텐츠 유형별 카테고리 주소와 카테고리 SEO 공개 필드에 더해, 1~3단계 부모·자식
10
+ 분류로 목록·허브·상세 주소를 구성하는 범용 콘텐츠 모델 계약을 추가합니다.
11
+
3
12
  ## 0.53.1
4
13
 
5
14
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -60,6 +60,11 @@ __export(src_exports, {
60
60
  postsForCollection: () => postsForCollection,
61
61
  postsInAnyCollection: () => postsInAnyCollection,
62
62
  postsNotInAnyCollection: () => postsNotInAnyCollection,
63
+ resolveCategoryPath: () => resolveCategoryPath,
64
+ resolveCategoryTreeChain: () => resolveCategoryTreeChain,
65
+ resolveCategoryTreeEntryPath: () => resolveCategoryTreeEntryPath,
66
+ resolveCategoryTreeHubPaths: () => resolveCategoryTreeHubPaths,
67
+ resolvePostCategorySlug: () => resolvePostCategorySlug,
63
68
  resolvePostCollection: () => resolvePostCollection,
64
69
  resolvePostPath: () => resolvePostPath,
65
70
  resolvePostUrl: () => resolvePostUrl,
@@ -202,18 +207,38 @@ function resolvePostCollection(post, collections) {
202
207
  return collections.find((c) => c.key === post.collectionKey) ?? null;
203
208
  }
204
209
  function resolvePostUrl(siteUrl, post, collections) {
205
- const collection = resolvePostCollection(post, collections);
206
- if (!collection) return null;
207
- if (collection.detail === false) return null;
208
- if (!collection.basePath) return null;
209
- return `${trimTrailingSlash(siteUrl)}${trimTrailingSlash(collection.basePath)}/${encodeURIComponent(post.slug)}`;
210
+ const path = resolvePostPath(post, collections);
211
+ return path ? `${trimTrailingSlash(siteUrl)}${path}` : null;
212
+ }
213
+ function resolvePostCategorySlug(post, collection) {
214
+ const allowed = new Set(collection.categories);
215
+ const slugs = [
216
+ ...new Set(
217
+ post.terms.filter(
218
+ (term) => term.taxonomy === "category" && allowed.has(term.slug)
219
+ ).map((term) => term.slug)
220
+ )
221
+ ];
222
+ return slugs.length === 1 ? slugs[0] : null;
223
+ }
224
+ function resolveCategoryPath(collection, categorySlug) {
225
+ if (!collection.archives || !collection.basePath || !categorySlug) return null;
226
+ const basePath = trimTrailingSlash(collection.basePath);
227
+ const encodedSlug = encodeURIComponent(categorySlug);
228
+ return collection.categoryPath === "direct" ? `${basePath}/${encodedSlug}` : `${basePath}/categories/${encodedSlug}`;
210
229
  }
211
230
  function resolvePostPath(post, collections) {
212
231
  const collection = resolvePostCollection(post, collections);
213
232
  if (!collection) return null;
214
233
  if (collection.detail === false) return null;
215
234
  if (!collection.basePath) return null;
216
- return `${trimTrailingSlash(collection.basePath)}/${encodeURIComponent(post.slug)}`;
235
+ const basePath = trimTrailingSlash(collection.basePath);
236
+ const encodedPostSlug = encodeURIComponent(post.slug);
237
+ if (collection.detailPath !== "category") {
238
+ return `${basePath}/${encodedPostSlug}`;
239
+ }
240
+ const categorySlug = resolvePostCategorySlug(post, collection);
241
+ return categorySlug ? `${basePath}/${encodeURIComponent(categorySlug)}/${encodedPostSlug}` : null;
217
242
  }
218
243
  function postsForCollection(posts, collections, key) {
219
244
  return posts.filter((p) => resolvePostCollection(p, collections)?.key === key);
@@ -253,10 +278,87 @@ function validateCollections(collections) {
253
278
  }
254
279
  categoryOwner.set(slug, c.key);
255
280
  }
281
+ if (c.detailPath === "category") {
282
+ if (!c.basePath) {
283
+ problems.push(
284
+ `category \uC0C1\uC138 \uACBD\uB85C\uB294 basePath \uAC00 \uD544\uC694\uD568: "${c.key}"`
285
+ );
286
+ }
287
+ if (c.detail === false) {
288
+ problems.push(
289
+ `category \uC0C1\uC138 \uACBD\uB85C\uB294 \uAE00\uBCC4 \uC0C1\uC138 \uD398\uC774\uC9C0\uAC00 \uD544\uC694\uD568: "${c.key}"`
290
+ );
291
+ }
292
+ if (!c.archives) {
293
+ problems.push(
294
+ `category \uC0C1\uC138 \uACBD\uB85C\uB294 \uCE74\uD14C\uACE0\uB9AC \uC544\uCE74\uC774\uBE0C\uAC00 \uD544\uC694\uD568: "${c.key}"`
295
+ );
296
+ }
297
+ if (c.categoryCardinality !== "exactly-one") {
298
+ problems.push(
299
+ `category \uC0C1\uC138 \uACBD\uB85C\uB294 categoryCardinality "exactly-one" \uC774 \uD544\uC694\uD568: "${c.key}"`
300
+ );
301
+ }
302
+ }
256
303
  }
257
304
  return problems;
258
305
  }
259
306
 
307
+ // src/category-tree.ts
308
+ function normalizeBasePath(basePath) {
309
+ return basePath.replace(/\/+$/, "");
310
+ }
311
+ function resolveCategoryTreeChain(presentation, leafId, terms) {
312
+ if (!Number.isInteger(presentation.categoryDepth) || presentation.categoryDepth < 1) {
313
+ return null;
314
+ }
315
+ const termsById = new Map(terms.map((term) => [term.id, term]));
316
+ if (termsById.size !== terms.length) return null;
317
+ if (terms.some((term) => term.parentId === leafId)) return null;
318
+ const reversed = [];
319
+ const visited = /* @__PURE__ */ new Set();
320
+ let currentId = leafId;
321
+ while (currentId) {
322
+ if (visited.has(currentId)) return null;
323
+ visited.add(currentId);
324
+ const current = termsById.get(currentId);
325
+ if (!current || !current.slug) return null;
326
+ reversed.push(current);
327
+ currentId = current.parentId ?? null;
328
+ }
329
+ if (reversed.length !== presentation.categoryDepth) return null;
330
+ return reversed.reverse();
331
+ }
332
+ function resolveCategoryTreeHubPaths(presentation, termId, terms) {
333
+ const chain = resolveCategoryTreeChain(presentation, termId, terms);
334
+ const basePath = normalizeBasePath(presentation.basePath);
335
+ if (!chain || !basePath) return null;
336
+ const paths = [basePath];
337
+ const segments = [];
338
+ for (const term of chain) {
339
+ segments.push(encodeURIComponent(term.slug));
340
+ paths.push(`${basePath}/${segments.join("/")}`);
341
+ }
342
+ return paths;
343
+ }
344
+ function resolveCategoryTreeEntryPath(presentation, entry, terms) {
345
+ if (!entry.slug) return null;
346
+ const knownIds = new Set(terms.map((term) => term.id));
347
+ const categoryIds = [
348
+ ...new Set(
349
+ entry.terms.filter((term) => term.taxonomy === "category" && knownIds.has(term.id)).map((term) => term.id)
350
+ )
351
+ ];
352
+ if (categoryIds.length !== 1) return null;
353
+ const hubs = resolveCategoryTreeHubPaths(
354
+ presentation,
355
+ categoryIds[0],
356
+ terms
357
+ );
358
+ const leafPath = hubs?.at(-1);
359
+ return leafPath ? `${leafPath}/${encodeURIComponent(entry.slug)}` : null;
360
+ }
361
+
260
362
  // src/delimiter.ts
261
363
  function serializeBlock(block) {
262
364
  const exportable = sanitizeAttributesForExport(block);
@@ -1255,6 +1357,11 @@ function injectStableIdsTree(blocks) {
1255
1357
  postsForCollection,
1256
1358
  postsInAnyCollection,
1257
1359
  postsNotInAnyCollection,
1360
+ resolveCategoryPath,
1361
+ resolveCategoryTreeChain,
1362
+ resolveCategoryTreeEntryPath,
1363
+ resolveCategoryTreeHubPaths,
1364
+ resolvePostCategorySlug,
1258
1365
  resolvePostCollection,
1259
1366
  resolvePostPath,
1260
1367
  resolvePostUrl,