@roottale/cms-renderer-next 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 +59 -0
- package/dist/{blog-filter-BQNQL3Mk.d.ts → blog-filter-qE9Lj49C.d.ts} +7 -1
- package/dist/{chunk-FJANIAZN.js → chunk-GDP2VGJR.js} +38 -6
- package/dist/chunk-GDP2VGJR.js.map +1 -0
- package/dist/cms-public.css +25 -3
- package/dist/routes.d.ts +42 -8
- package/dist/routes.js +35 -16
- package/dist/routes.js.map +1 -1
- package/dist/server.d.ts +49 -4
- package/dist/server.js +394 -213
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-FJANIAZN.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# @roottale/cms-renderer-next
|
|
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
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [e222471]
|
|
33
|
+
- Updated dependencies [92d0ad4]
|
|
34
|
+
- @roottale/cms-core@0.57.0
|
|
35
|
+
- @roottale/cms-client@0.57.0
|
|
36
|
+
|
|
37
|
+
## 0.56.0
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- 422c388: ADR-0105 Amendment 1 — FRONT 가 글 주소를 다시 계산하지 않습니다. `RootTaleBlogList`·
|
|
42
|
+
`RootTaleBlogPost`(연관 글·빵부스러기)의 기본 글 링크와 `buildPostMetadata` 의 canonical 이
|
|
43
|
+
플랫폼이 저장한 정규 공개 경로 `post.path` 를 먼저 읽고, 없을 때만 종전처럼 `collections`
|
|
44
|
+
규칙 → `/blog/{slug}` 로 폴백합니다. `storedPostPath(post)` 헬퍼를 `routes` 에서 내보냅니다.
|
|
45
|
+
문서(seo·collections·blog)와 Next.js 예제가 같은 규칙으로 갱신됐습니다.
|
|
46
|
+
- 41740c3: ADR-0105 Amendment 1 — 사이트맵·RSS·llms.txt 의 글 URL 이 공개 API 가 내려주는 저장된 정규
|
|
47
|
+
공개 경로(`post.path`)를 먼저 쓰고, 없으면 종전처럼 컬렉션 규칙·`blogBasePath` 로 계산합니다.
|
|
48
|
+
어느 유형에도 속하지 않는 레거시 글은 종전대로 사이트 설정을 따릅니다.
|
|
49
|
+
- 86c5712: RootTale 표준 블로그 주소 `/blog/{category}/{slug}` 지원 — `RootTaleSiteConfig.categoryPath`
|
|
50
|
+
(`"direct"` 면 사이트맵 카테고리 허브가 `/blog/{category}`), collection 모드 사이트맵이 각 컬렉션의
|
|
51
|
+
`categoryPath` 를 따르며, `makeCategoryHref(basePath, categoryPath)` 를 `server`·`routes` 에서
|
|
52
|
+
내보냅니다. 문서에 표준 규칙과 라우트 배치를 적었습니다.
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- Updated dependencies [18aaa9e]
|
|
57
|
+
- Updated dependencies [9eb39e8]
|
|
58
|
+
- Updated dependencies [d303835]
|
|
59
|
+
- @roottale/cms-client@0.56.0
|
|
60
|
+
- @roottale/cms-core@0.56.0
|
|
61
|
+
|
|
3
62
|
## 0.55.0
|
|
4
63
|
|
|
5
64
|
### Patch Changes
|
|
@@ -30,5 +30,11 @@ declare function collectCategoriesWithCounts(posts: CmsPostContent[]): BlogCateg
|
|
|
30
30
|
declare function filterPostsByCategory(posts: CmsPostContent[], activeSlug: string | null | undefined): CmsPostContent[];
|
|
31
31
|
/** 기본 카테고리 링크: 전체 → `/blog`, 특정 → `/blog/categories/<slug>`. */
|
|
32
32
|
declare function defaultCategoryHref(slug: string | null): string;
|
|
33
|
+
/**
|
|
34
|
+
* 카테고리 링크 함수 — 목록·카테고리 허브·빵부스러기의 `categoryHref` 로 넘긴다.
|
|
35
|
+
* `"direct"` 는 RootTale 표준(`/blog/{category}` 허브, 글은 `/blog/{category}/{slug}`),
|
|
36
|
+
* `"namespaced"` 는 종전 `/blog/categories/{slug}`. 전체(`null`)는 언제나 목록.
|
|
37
|
+
*/
|
|
38
|
+
declare function makeCategoryHref(basePath: string, categoryPath: "namespaced" | "direct"): (slug: string | null) => string;
|
|
33
39
|
|
|
34
|
-
export { type BlogCategoryWithCount as B, type BlogCategory as a, collectCategoriesWithCounts as b, collectCategories as c, defaultCategoryHref as d, filterPostsByCategory as f };
|
|
40
|
+
export { type BlogCategoryWithCount as B, type BlogCategory as a, collectCategoriesWithCounts as b, collectCategories as c, defaultCategoryHref as d, filterPostsByCategory as f, makeCategoryHref as m };
|
|
@@ -41,7 +41,16 @@ function filterPostsByCategory(posts, activeSlug) {
|
|
|
41
41
|
return posts.filter((p) => categoryTerms(p).some((t) => t.slug === activeSlug));
|
|
42
42
|
}
|
|
43
43
|
function defaultCategoryHref(slug) {
|
|
44
|
-
return
|
|
44
|
+
return makeCategoryHref("/blog", "namespaced")(slug);
|
|
45
|
+
}
|
|
46
|
+
function makeCategoryHref(basePath, categoryPath) {
|
|
47
|
+
const base = basePath.replace(/\/+$/, "") || "";
|
|
48
|
+
const list = base || "/";
|
|
49
|
+
return (slug) => {
|
|
50
|
+
if (!slug) return list;
|
|
51
|
+
const encoded = encodeURIComponent(slug);
|
|
52
|
+
return categoryPath === "direct" ? `${base}/${encoded}` : `${base}/categories/${encoded}`;
|
|
53
|
+
};
|
|
45
54
|
}
|
|
46
55
|
function collectCollectionCategories(posts, collections, key) {
|
|
47
56
|
const owned = postsForCollection(posts, collections, key);
|
|
@@ -50,9 +59,29 @@ function collectCollectionCategories(posts, collections, key) {
|
|
|
50
59
|
return collectCategoriesWithCounts(owned).filter((c) => allowed.has(c.slug));
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
// src/post-public-url.ts
|
|
63
|
+
import { resolvePostUrl as resolvePostUrl2 } from "@roottale/cms-core";
|
|
64
|
+
function trimSlash(value) {
|
|
65
|
+
return value.replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
function storedPostPath(post) {
|
|
68
|
+
if (typeof post.path !== "string" || !post.path.startsWith("/")) return null;
|
|
69
|
+
if (!post.collectionKey && !post.modelKey) return null;
|
|
70
|
+
return post.path;
|
|
71
|
+
}
|
|
72
|
+
function postPublicUrl(post, options) {
|
|
73
|
+
const siteUrl = trimSlash(options.siteUrl);
|
|
74
|
+
const stored = storedPostPath(post);
|
|
75
|
+
if (stored) return `${siteUrl}${stored}`;
|
|
76
|
+
if (options.inCollectionMode) {
|
|
77
|
+
return resolvePostUrl2(siteUrl, post, options.collections);
|
|
78
|
+
}
|
|
79
|
+
return `${siteUrl}${trimSlash(options.blogBasePath)}/${post.slug}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
// src/routes-shared.ts
|
|
54
83
|
import { fetchPosts } from "@roottale/cms-client/server";
|
|
55
|
-
import { resolvePostCollection as resolvePostCollection2, resolvePostUrl as
|
|
84
|
+
import { resolvePostCollection as resolvePostCollection2, resolvePostUrl as resolvePostUrl3 } from "@roottale/cms-core";
|
|
56
85
|
async function resolveCollectionsResult(input) {
|
|
57
86
|
if (typeof input === "function") {
|
|
58
87
|
try {
|
|
@@ -70,7 +99,7 @@ async function resolveCollectionsResult(input) {
|
|
|
70
99
|
async function resolveCollections(input) {
|
|
71
100
|
return (await resolveCollectionsResult(input)).collections;
|
|
72
101
|
}
|
|
73
|
-
function
|
|
102
|
+
function trimSlash2(u) {
|
|
74
103
|
return u.replace(/\/+$/, "");
|
|
75
104
|
}
|
|
76
105
|
async function safeFetchPosts(config, limit) {
|
|
@@ -93,12 +122,15 @@ export {
|
|
|
93
122
|
collectCategoriesWithCounts,
|
|
94
123
|
filterPostsByCategory,
|
|
95
124
|
defaultCategoryHref,
|
|
125
|
+
makeCategoryHref,
|
|
96
126
|
collectCollectionCategories,
|
|
127
|
+
storedPostPath,
|
|
128
|
+
postPublicUrl,
|
|
97
129
|
resolveCollectionsResult,
|
|
98
130
|
resolveCollections,
|
|
99
|
-
trimSlash,
|
|
131
|
+
trimSlash2 as trimSlash,
|
|
100
132
|
safeFetchPosts,
|
|
101
133
|
resolvePostCollection2 as resolvePostCollection,
|
|
102
|
-
|
|
134
|
+
resolvePostUrl3 as resolvePostUrl
|
|
103
135
|
};
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-GDP2VGJR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blog-filter.ts","../src/post-public-url.ts","../src/routes-shared.ts"],"sourcesContent":["/**\n * `RootTaleBlogList` 카테고리 필터 — 순수 헬퍼(서버 필터, client JS 불요).\n *\n * SSR-only 정책 유지: 필터는 `?category=<slug>` URL 파라미터로 동작하고(칩 =\n * 서버 렌더 `<a>` 링크), consumer 가 searchParams 의 category 를 `activeCategory`\n * 로 주입한다. hydration 없이 SEO 친화 + 공유 가능한 URL.\n */\n\nimport type { CmsPostContent, CmsTermRef } from \"@roottale/cms-client/server\";\n\nexport interface BlogCategory {\n name: string;\n slug: string;\n}\n\nexport interface BlogCategoryWithCount extends BlogCategory {\n count: number;\n description?: string;\n seoTitle?: string;\n seoDescription?: string;\n imageUrl?: string;\n imageAlt?: string;\n}\n\nfunction categoryTerms(post: CmsPostContent): CmsTermRef[] {\n return (post.terms ?? []).filter((t) => t.taxonomy === \"category\");\n}\n\n/**\n * 글 목록에 등장하는 카테고리를 slug 기준 unique 로 수집. 서버가 보내는 정렬\n * (category → name asc)을 보존하기 위해 처음 등장 순서를 유지한다.\n */\nexport function collectCategories(posts: CmsPostContent[]): BlogCategory[] {\n return collectCategoriesWithCounts(posts).map(({ name, slug }) => ({\n name,\n slug,\n }));\n}\n\nexport function collectCategoriesWithCounts(\n posts: CmsPostContent[],\n): BlogCategoryWithCount[] {\n const bySlug = new Map<string, BlogCategoryWithCount>();\n const out: BlogCategoryWithCount[] = [];\n for (const post of posts) {\n for (const t of categoryTerms(post)) {\n const current = bySlug.get(t.slug);\n if (current) {\n current.count += 1;\n } else {\n const category = { name: t.name, slug: t.slug, count: 1 };\n bySlug.set(t.slug, category);\n out.push(category);\n }\n }\n }\n return out;\n}\n\n/** activeSlug 가 비면 전체 반환. 아니면 그 카테고리를 가진 글만. */\nexport function filterPostsByCategory(\n posts: CmsPostContent[],\n activeSlug: string | null | undefined,\n): CmsPostContent[] {\n if (!activeSlug) return posts;\n return posts.filter((p) => categoryTerms(p).some((t) => t.slug === activeSlug));\n}\n\n/** 기본 카테고리 링크: 전체 → `/blog`, 특정 → `/blog/categories/<slug>`. */\nexport function defaultCategoryHref(slug: string | null): string {\n return makeCategoryHref(\"/blog\", \"namespaced\")(slug);\n}\n\n/**\n * 카테고리 링크 함수 — 목록·카테고리 허브·빵부스러기의 `categoryHref` 로 넘긴다.\n * `\"direct\"` 는 RootTale 표준(`/blog/{category}` 허브, 글은 `/blog/{category}/{slug}`),\n * `\"namespaced\"` 는 종전 `/blog/categories/{slug}`. 전체(`null`)는 언제나 목록.\n */\nexport function makeCategoryHref(\n basePath: string,\n categoryPath: \"namespaced\" | \"direct\",\n): (slug: string | null) => string {\n const base = basePath.replace(/\\/+$/, \"\") || \"\";\n const list = base || \"/\";\n return (slug) => {\n if (!slug) return list;\n const encoded = encodeURIComponent(slug);\n return categoryPath === \"direct\" ? `${base}/${encoded}` : `${base}/categories/${encoded}`;\n };\n}\n\n// ── Collections (ADR-0060) — cms-core 라우팅 프리미티브 재export + 렌더 헬퍼 ──────\n//\n// 라우팅 코어(RouteCollection/resolvePostCollection/resolvePostUrl/postsForCollection/\n// validateCollections)는 `@roottale/cms-core` 에 있어 next/astro 가 공유한다\n// (ADR-0029 parity — codex consult `019ef577` 반영). 여기서는 next 소비자 편의를\n// 위해 재export + CmsPostContent 기반 스트림 스코프 카테고리 집계만 추가한다.\nimport {\n type RouteCollection,\n postsForCollection,\n} from \"@roottale/cms-core\";\n\nexport {\n type RouteCollection,\n type RoutablePost,\n isInCollectionMode,\n resolvePostCollection,\n resolvePostUrl,\n postsForCollection,\n postsInAnyCollection,\n validateCollections,\n} from \"@roottale/cms-core\";\n\n/**\n * 특정 collection 안에서 등장하는 카테고리 집계 — 그 스트림의 카테고리 칩/아카이브용.\n * **전체 정렬 리스트로 first-wins 해석**해 그 스트림에 최종 귀속된 글만 대상(한 글이\n * 여러 스트림 리스트에 중복되는 것을 방지 — codex 지적).\n */\nexport function collectCollectionCategories(\n posts: CmsPostContent[],\n collections: readonly RouteCollection[],\n key: string,\n): BlogCategoryWithCount[] {\n const owned = postsForCollection(posts, collections, key);\n const collection = collections.find((c) => c.key === key);\n const allowed = new Set(collection?.categories ?? []);\n return collectCategoriesWithCounts(owned).filter((c) => allowed.has(c.slug));\n}\n","/**\n * 글의 공개 URL — ADR-0105 Amendment 1.\n *\n * 플랫폼이 저장한 정규 공개 경로(`post.path`, 공개 API `path`)가 있으면 그것을 쓴다.\n * 없으면(구 서버·물질화 전) 지금까지처럼 컬렉션 규칙(`resolvePostUrl`)이나 레거시\n * `blogBasePath` 로 계산한다. 어느 유형에도 속하지 않는 레거시 글은 사이트 설정의\n * `blogBasePath` 가 진실이라 저장 경로보다 우선한다(플랫폼 폴백은 `/blog` 고정).\n */\nimport { resolvePostUrl, type RoutablePost, type RouteCollection } from \"@roottale/cms-core\";\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport interface PostPublicUrlOptions {\n readonly siteUrl: string;\n readonly collections: RouteCollection[] | undefined;\n readonly inCollectionMode: boolean;\n readonly blogBasePath: string;\n}\n\n/** 공개 API 글의 구조적 최소형 — 구 cms-client 에는 `path`·`modelKey` 가 없어도 된다. */\nexport interface PublicUrlPost extends RoutablePost {\n readonly path?: string | null;\n readonly modelKey?: string | null;\n}\n\n/** 소속(모델·컬렉션)이 있는 글의 저장 경로. 레거시 미소속 글은 사이트 설정을 따른다. */\nexport function storedPostPath(\n post: Pick<PublicUrlPost, \"path\" | \"collectionKey\" | \"modelKey\">,\n): string | null {\n if (typeof post.path !== \"string\" || !post.path.startsWith(\"/\")) return null;\n if (!post.collectionKey && !post.modelKey) return null;\n return post.path;\n}\n\n/** 사이트맵·피드·llms 가 함께 쓰는 글 URL. 저장 경로 → 컬렉션 규칙 → 레거시 순. */\nexport function postPublicUrl(\n post: PublicUrlPost,\n options: PostPublicUrlOptions,\n): string | null {\n const siteUrl = trimSlash(options.siteUrl);\n const stored = storedPostPath(post);\n if (stored) return `${siteUrl}${stored}`;\n if (options.inCollectionMode) {\n return resolvePostUrl(siteUrl, post, options.collections!);\n }\n return `${siteUrl}${trimSlash(options.blogBasePath)}/${post.slug}`;\n}\n","/**\n * `routes` 공통 — config 타입, collections resolve, fetch 헬퍼. feed/sitemap/llms/\n * revalidate/og 모듈이 공유한다(순환 import 회피). 공개 표면은 `routes.ts` 배럴.\n */\n\nimport { fetchPosts, type CmsPostContent } from \"@roottale/cms-client/server\";\nimport type { RouteCollection } from \"@roottale/cms-core\";\n\nexport type { RouteCollection };\n// 소비자 가드용 재export (`/blog/[slug]` 에서 스트림 누출 차단 등) — ADR-0060.\nexport { resolvePostCollection, resolvePostUrl } from \"@roottale/cms-core\";\n\nexport interface RootTaleSiteConfig {\n /** `rtlk_cust_*` — 블로그 조회와 같은 키. 서버에서만(절대 브라우저 금지). */\n apiKey: string;\n /** API base 오버라이드. 기본 `https://api.roottale.com`. */\n apiBase?: string;\n /** 사이트 절대 URL (뒤 슬래시 없이). 예: `https://kjmtax.roottale.app`. */\n siteUrl: string;\n /** 피드/사이트 제목. */\n title: string;\n description?: string;\n /** 기본 \"ko-KR\". */\n language?: string;\n /** RSS의 WebSub discovery link. 미지정이면 기존 RSS 출력 유지. */\n webSubHubUrl?: string;\n /** 블로그 base path. 기본 \"/blog\". 레거시 단일 스트림 — `collections` 미사용 시. */\n blogBasePath?: string;\n /**\n * 카테고리 허브 주소 형식(레거시 단일 스트림용, `collections` 미사용 시).\n * `\"namespaced\"`(기본) = `{blogBasePath}/categories/{slug}`,\n * `\"direct\"` = `{blogBasePath}/{slug}` — RootTale 표준(`/blog/{category}/{slug}` 글 주소와\n * 짝). collection 모드는 각 컬렉션의 `categoryPath` 를 따른다.\n */\n categoryPath?: \"namespaced\" | \"direct\";\n /**\n * 다중 스트림 라우팅 (ADR-0060). **길이 > 0 이면 collection 모드** — feed/sitemap/\n * llms.txt 가 글을 소속 스트림 basePath 로 라우팅하고, 어느 스트림에도 안 속한 글은\n * 제외한다. 비우거나 미전달 = 레거시 단일 `blogBasePath`(/blog) 동작(하위호환).\n * 선언 순서 = 우선순위(first-wins).\n *\n * 배열 또는 **async resolver**(`() => Promise<RouteCollection[]>`)를 받는다 — 후자는\n * 사이트가 `@roottale/cms-client` `fetchCollections()` 로 admin 설정을 가져올 때 쓴다.\n * resolver 가 throw 하면 fail-soft(레거시 단일 /blog).\n */\n collections?: CollectionsInput;\n}\n\n/** collections 입력 — 정적 배열 또는 async resolver(admin 설정 fetch 용). */\nexport type CollectionsInput =\n | RouteCollection[]\n | (() => RouteCollection[] | Promise<RouteCollection[]>);\n\n/** resolve 결과 — `failed` 로 \"resolver throw\" 를 \"collections 미설정\" 과 구분한다. */\nexport async function resolveCollectionsResult(\n input: CollectionsInput | undefined,\n): Promise<{ collections: RouteCollection[] | undefined; failed: boolean }> {\n if (typeof input === \"function\") {\n try {\n return { collections: await input(), failed: false };\n } catch (err) {\n console.error(\n \"[@roottale/cms-renderer-next/routes] collections resolver 실패:\",\n err,\n );\n return { collections: undefined, failed: true };\n }\n }\n return { collections: input, failed: false };\n}\n\n/**\n * resolver 면 호출(실패 시 undefined → 레거시), 배열이면 그대로. GET 팩토리(feed/\n * sitemap/llms)는 always-200 철학상 실패를 레거시 /blog 로 흡수 — revalidate 만\n * `resolveCollectionsResult` 로 실패를 구분해 under-invalidation 을 막는다.\n */\nexport async function resolveCollections(\n input: CollectionsInput | undefined,\n): Promise<RouteCollection[] | undefined> {\n return (await resolveCollectionsResult(input)).collections;\n}\n\nexport function trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nexport async function safeFetchPosts(\n config: RootTaleSiteConfig,\n limit: number,\n): Promise<CmsPostContent[]> {\n try {\n const page = await fetchPosts({\n apiKey: config.apiKey,\n baseUrl: config.apiBase,\n type: \"post\",\n limit,\n });\n return page.items;\n } catch (err) {\n // 빌드/런타임 fetch 실패 시 빈 목록 — 라우트는 항상 정상 응답(404 방지).\n console.error(\"[@roottale/cms-renderer-next/routes] fetchPosts 실패:\", err);\n return [];\n }\n}\n"],"mappings":"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","names":["postsForCollection","resolvePostUrl","resolvePostCollection","resolvePostUrl","trimSlash"]}
|
package/dist/cms-public.css
CHANGED
|
@@ -332,9 +332,22 @@
|
|
|
332
332
|
margin-block-end: 1rem;
|
|
333
333
|
padding: 0.5rem 0.75rem;
|
|
334
334
|
border-radius: 0.5rem;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
/* 테두리·바탕·글자를 경고 색 하나에서 파생한다 — 색은 테넌트 테마(--rt-color-warning)
|
|
336
|
+
→ 플랫폼 상태 토큰(--warning) → 독립 실행 기본값(amber) 순으로 따른다.
|
|
337
|
+
리터럴 amber 를 세 군데 따로 두지 않는 이유: 사이트가 경고 색을 바꾸면 세 색이
|
|
338
|
+
같이 움직여야 하고, 토큰 값 리터럴은 brand-hex 불변식이 막는다. */
|
|
339
|
+
border: 1px solid
|
|
340
|
+
color-mix(in srgb, var(--rt-color-warning, var(--warning, #d97706)) 35%, transparent);
|
|
341
|
+
background: color-mix(
|
|
342
|
+
in srgb,
|
|
343
|
+
var(--rt-color-warning, var(--warning, #d97706)) 12%,
|
|
344
|
+
var(--rt-color-background, #fff)
|
|
345
|
+
);
|
|
346
|
+
color: color-mix(
|
|
347
|
+
in srgb,
|
|
348
|
+
var(--rt-color-warning, var(--warning, #d97706)) 72%,
|
|
349
|
+
var(--rt-color-foreground, #111)
|
|
350
|
+
);
|
|
338
351
|
font-family: var(--rt-font-body, system-ui, sans-serif);
|
|
339
352
|
font-size: 0.875rem;
|
|
340
353
|
font-weight: 600;
|
|
@@ -967,6 +980,15 @@
|
|
|
967
980
|
opacity: 0.92;
|
|
968
981
|
}
|
|
969
982
|
|
|
983
|
+
/* 공통 블록(post_footer) — 본문 바로 아래, 본문과 같은 폭·타이포. 안쪽은 Tiptap 본문 규칙을 그대로 쓴다. */
|
|
984
|
+
[data-roottale-cms] :where(.rt-cms-post-pattern) {
|
|
985
|
+
max-inline-size: var(--rt-cms-measure, 42.5rem);
|
|
986
|
+
margin-inline: auto;
|
|
987
|
+
margin-block-start: var(--rt-spacing-6, 1.5rem);
|
|
988
|
+
padding-block-start: var(--rt-spacing-5, 1.25rem);
|
|
989
|
+
border-block-start: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
|
|
990
|
+
}
|
|
991
|
+
|
|
970
992
|
/* FAQ 블록 (roottale/faq) — <details> 아코디언. 0 JS. */
|
|
971
993
|
[data-roottale-cms] :where(.rt-cms-faq) {
|
|
972
994
|
border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CmsPostContent, RootTaleRedirect, CmsPostTranslationRef } from '@roottale/cms-client/server';
|
|
2
|
-
import { RouteCollection } from '@roottale/cms-core';
|
|
2
|
+
import { RouteCollection, RoutablePost } from '@roottale/cms-core';
|
|
3
3
|
export { RouteCollection, resolvePostCollection, resolvePostUrl } from '@roottale/cms-core';
|
|
4
|
-
import { B as BlogCategoryWithCount } from './blog-filter-
|
|
4
|
+
import { B as BlogCategoryWithCount } from './blog-filter-qE9Lj49C.js';
|
|
5
|
+
export { m as makeCategoryHref } from './blog-filter-qE9Lj49C.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* `routes` 공통 — config 타입, collections resolve, fetch 헬퍼. feed/sitemap/llms/
|
|
@@ -24,6 +25,13 @@ interface RootTaleSiteConfig {
|
|
|
24
25
|
webSubHubUrl?: string;
|
|
25
26
|
/** 블로그 base path. 기본 "/blog". 레거시 단일 스트림 — `collections` 미사용 시. */
|
|
26
27
|
blogBasePath?: string;
|
|
28
|
+
/**
|
|
29
|
+
* 카테고리 허브 주소 형식(레거시 단일 스트림용, `collections` 미사용 시).
|
|
30
|
+
* `"namespaced"`(기본) = `{blogBasePath}/categories/{slug}`,
|
|
31
|
+
* `"direct"` = `{blogBasePath}/{slug}` — RootTale 표준(`/blog/{category}/{slug}` 글 주소와
|
|
32
|
+
* 짝). collection 모드는 각 컬렉션의 `categoryPath` 를 따른다.
|
|
33
|
+
*/
|
|
34
|
+
categoryPath?: "namespaced" | "direct";
|
|
27
35
|
/**
|
|
28
36
|
* 다중 스트림 라우팅 (ADR-0060). **길이 > 0 이면 collection 모드** — feed/sitemap/
|
|
29
37
|
* llms.txt 가 글을 소속 스트림 basePath 로 라우팅하고, 어느 스트림에도 안 속한 글은
|
|
@@ -456,6 +464,14 @@ interface PostMetadataInput {
|
|
|
456
464
|
* 공지/블로그로 구분해 해석한다(`collectionKey:"notice"` → `/notice`).
|
|
457
465
|
*/
|
|
458
466
|
collectionKey?: string | null;
|
|
467
|
+
/** 콘텐츠 모델 key — `path` 가 소속 글의 저장 경로인지 판정하는 데 쓴다. */
|
|
468
|
+
modelKey?: string | null;
|
|
469
|
+
/**
|
|
470
|
+
* ADR-0105 — 플랫폼이 저장한 정규 공개 경로(`CmsPostContent.path`). 원본 post 를
|
|
471
|
+
* 그대로 넘기면 채워진다. `options.path` 를 명시하지 않았을 때 `collections`
|
|
472
|
+
* 계산보다 먼저 canonical 경로가 된다 — 사이트가 주소를 다시 계산하지 않아도 된다.
|
|
473
|
+
*/
|
|
474
|
+
path?: string | null;
|
|
459
475
|
/**
|
|
460
476
|
* ADR-0052 A안 (W4-6 PR C1) — 이 글의 발행된 언어판 목록(자기 자신 포함, PR B
|
|
461
477
|
* 응답 `CmsPostContent.translations`). `options.locales` 와 함께 주면 hreflang
|
|
@@ -486,14 +502,15 @@ interface BuildPostMetadataOptions {
|
|
|
486
502
|
siteUrl?: string;
|
|
487
503
|
/**
|
|
488
504
|
* self-canonical 기본값 경로 (예: `/blog/my-post`). `siteUrl` 과 함께.
|
|
489
|
-
* 명시하면
|
|
505
|
+
* 명시하면 글의 저장 경로·`collections` 파생보다 우선한다(이 화면의 실제 주소를
|
|
506
|
+
* 사이트가 아는 경우). 보통은 주지 않고 `post.path`(ADR-0105 원장)에 맡긴다.
|
|
490
507
|
*/
|
|
491
508
|
path?: string;
|
|
492
509
|
/**
|
|
493
|
-
* 섹션 정의 (ADR-0060). `path`
|
|
494
|
-
* basePath 를 찾아 canonical 경로를 **공지/블로그로
|
|
495
|
-
* (공지 글 → `/notice/{slug}`, 블로그 글 → `/blog/{slug}`).
|
|
496
|
-
* 안 속하면(미설정·미존재 key) canonical 을 생략한다. `post.slug` 필요.
|
|
510
|
+
* 섹션 정의 (ADR-0060). `path` 도 저장 경로(`post.path`)도 없을 때 글의
|
|
511
|
+
* `collectionKey` 로 소속 섹션 basePath 를 찾아 canonical 경로를 **공지/블로그로
|
|
512
|
+
* 구분해 자동 해석**한다(공지 글 → `/notice/{slug}`, 블로그 글 → `/blog/{slug}`).
|
|
513
|
+
* 어느 섹션에도 안 속하면(미설정·미존재 key) canonical 을 생략한다. `post.slug` 필요.
|
|
497
514
|
*/
|
|
498
515
|
collections?: readonly RouteCollection[];
|
|
499
516
|
/**
|
|
@@ -647,6 +664,23 @@ declare function buildPostMetadata(post: PostMetadataInput, options?: BuildPostM
|
|
|
647
664
|
*/
|
|
648
665
|
declare function buildHreflangLanguages(translations: readonly CmsPostTranslationRef[] | undefined, siteUrl: string, locales: PostLocaleOptions): Record<string, string> | undefined;
|
|
649
666
|
|
|
667
|
+
/**
|
|
668
|
+
* 글의 공개 URL — ADR-0105 Amendment 1.
|
|
669
|
+
*
|
|
670
|
+
* 플랫폼이 저장한 정규 공개 경로(`post.path`, 공개 API `path`)가 있으면 그것을 쓴다.
|
|
671
|
+
* 없으면(구 서버·물질화 전) 지금까지처럼 컬렉션 규칙(`resolvePostUrl`)이나 레거시
|
|
672
|
+
* `blogBasePath` 로 계산한다. 어느 유형에도 속하지 않는 레거시 글은 사이트 설정의
|
|
673
|
+
* `blogBasePath` 가 진실이라 저장 경로보다 우선한다(플랫폼 폴백은 `/blog` 고정).
|
|
674
|
+
*/
|
|
675
|
+
|
|
676
|
+
/** 공개 API 글의 구조적 최소형 — 구 cms-client 에는 `path`·`modelKey` 가 없어도 된다. */
|
|
677
|
+
interface PublicUrlPost extends RoutablePost {
|
|
678
|
+
readonly path?: string | null;
|
|
679
|
+
readonly modelKey?: string | null;
|
|
680
|
+
}
|
|
681
|
+
/** 소속(모델·컬렉션)이 있는 글의 저장 경로. 레거시 미소속 글은 사이트 설정을 따른다. */
|
|
682
|
+
declare function storedPostPath(post: Pick<PublicUrlPost, "path" | "collectionKey" | "modelKey">): string | null;
|
|
683
|
+
|
|
650
684
|
/**
|
|
651
685
|
* 아카이브(모음 페이지) 색인 정책 — **단일 SSoT**.
|
|
652
686
|
*
|
|
@@ -989,4 +1023,4 @@ interface FleetInfoRouteConfig {
|
|
|
989
1023
|
*/
|
|
990
1024
|
declare function createFleetInfoRoute(config?: FleetInfoRouteConfig): () => Promise<Response>;
|
|
991
1025
|
|
|
992
|
-
export { ARCHIVE_POST_COUNT_LIMIT, type AlwaysNoindexArchiveKind, type ArchiveCategoriesConfig, type ArchiveCategoriesOptions, type ArchiveIndexDecision, type ArchiveIndexInput, type ArchiveKind, type ArchiveMetadata, type ArchiveMetadataFields, type ArchiveMetadataInput, type ArchivePromotion, type BuildArchiveMetadataOptions, type BuildPostMetadataOptions, type CollectionsInput, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PostLocaleOptions, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type PreviewMetadata, type PromotableArchiveKind, type RedirectMiddlewareConfig, type RevalidateRouteConfig, type RobotsMeta, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapSectionId, buildArchiveMetadata, buildHreflangLanguages, buildPostMetadata, buildPreviewMetadata, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createSitemap, createSitemapIndex, decideArchiveIndex, fetchBlogArchiveCategories, fetchBlogArchivePosts, matchRedirect, normalizeRedirectPath, postRedirectPath, resolveAuthorPromotion, resolveCategoryPromotion, resolveCollections, resolveCollectionsResult, resolveRobotsMeta, safeFetchPosts, trimSlash };
|
|
1026
|
+
export { ARCHIVE_POST_COUNT_LIMIT, type AlwaysNoindexArchiveKind, type ArchiveCategoriesConfig, type ArchiveCategoriesOptions, type ArchiveIndexDecision, type ArchiveIndexInput, type ArchiveKind, type ArchiveMetadata, type ArchiveMetadataFields, type ArchiveMetadataInput, type ArchivePromotion, type BuildArchiveMetadataOptions, type BuildPostMetadataOptions, type CollectionsInput, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PostLocaleOptions, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type PreviewMetadata, type PromotableArchiveKind, type PublicUrlPost, type RedirectMiddlewareConfig, type RevalidateRouteConfig, type RobotsMeta, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapSectionId, buildArchiveMetadata, buildHreflangLanguages, buildPostMetadata, buildPreviewMetadata, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createSitemap, createSitemapIndex, decideArchiveIndex, fetchBlogArchiveCategories, fetchBlogArchivePosts, matchRedirect, normalizeRedirectPath, postRedirectPath, resolveAuthorPromotion, resolveCategoryPromotion, resolveCollections, resolveCollectionsResult, resolveRobotsMeta, safeFetchPosts, storedPostPath, trimSlash };
|
package/dist/routes.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
collectCategoriesWithCounts,
|
|
3
3
|
collectCollectionCategories,
|
|
4
|
+
makeCategoryHref,
|
|
5
|
+
postPublicUrl,
|
|
4
6
|
resolveCollections,
|
|
5
7
|
resolveCollectionsResult,
|
|
6
8
|
resolvePostCollection,
|
|
7
9
|
resolvePostUrl,
|
|
8
10
|
safeFetchPosts,
|
|
11
|
+
storedPostPath,
|
|
9
12
|
trimSlash
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GDP2VGJR.js";
|
|
11
14
|
|
|
12
15
|
// src/routes.ts
|
|
13
16
|
import { CMS_CLIENT_VERSION } from "@roottale/cms-client/server";
|
|
@@ -19,8 +22,7 @@ import {
|
|
|
19
22
|
} from "@roottale/cms-client/server";
|
|
20
23
|
import {
|
|
21
24
|
isInCollectionMode as isInCollectionMode2,
|
|
22
|
-
resolvePostCollection as resolvePostCollection3
|
|
23
|
-
resolvePostUrl as resolvePostUrl3
|
|
25
|
+
resolvePostCollection as resolvePostCollection3
|
|
24
26
|
} from "@roottale/cms-core";
|
|
25
27
|
|
|
26
28
|
// src/routes-metadata.ts
|
|
@@ -44,7 +46,11 @@ function buildPostMetadata(post, options = {}) {
|
|
|
44
46
|
const ogImage = seo?.ogImage || post.image || void 0;
|
|
45
47
|
let canonical = seo?.canonical || void 0;
|
|
46
48
|
if (!canonical && options.siteUrl) {
|
|
47
|
-
let path = options.path
|
|
49
|
+
let path = options.path ?? storedPostPath({
|
|
50
|
+
path: post.path,
|
|
51
|
+
collectionKey: post.collectionKey ?? null,
|
|
52
|
+
modelKey: post.modelKey ?? null
|
|
53
|
+
}) ?? void 0;
|
|
48
54
|
if (!path && options.collections && post.slug) {
|
|
49
55
|
path = resolvePostPath(
|
|
50
56
|
{ slug: post.slug, terms: [], collectionKey: post.collectionKey ?? null },
|
|
@@ -196,7 +202,11 @@ import {
|
|
|
196
202
|
fetchBlogSettings,
|
|
197
203
|
fetchPosts as fetchPosts2
|
|
198
204
|
} from "@roottale/cms-client/server";
|
|
199
|
-
import {
|
|
205
|
+
import {
|
|
206
|
+
isInCollectionMode,
|
|
207
|
+
resolveCategoryPath,
|
|
208
|
+
resolvePostCollection as resolvePostCollection2
|
|
209
|
+
} from "@roottale/cms-core";
|
|
200
210
|
|
|
201
211
|
// src/archive-categories.ts
|
|
202
212
|
import {
|
|
@@ -333,7 +343,12 @@ function postEntries(config, siteUrl, posts, collections, settings, defaultLocal
|
|
|
333
343
|
const inCollection = isInCollectionMode(collections);
|
|
334
344
|
const blogBase = config.blogBasePath ?? "/blog";
|
|
335
345
|
return posts.flatMap((p) => {
|
|
336
|
-
const url =
|
|
346
|
+
const url = postPublicUrl(p, {
|
|
347
|
+
siteUrl,
|
|
348
|
+
collections,
|
|
349
|
+
inCollectionMode: inCollection,
|
|
350
|
+
blogBasePath: blogBase
|
|
351
|
+
});
|
|
337
352
|
if (!url) return [];
|
|
338
353
|
const entry = {
|
|
339
354
|
url,
|
|
@@ -378,20 +393,22 @@ async function categoryEntries(config, siteUrl, collections, promotedSlugs) {
|
|
|
378
393
|
if (!cats.length) return [];
|
|
379
394
|
const base = `${siteUrl}${trimSlash(c.basePath)}`;
|
|
380
395
|
return [
|
|
381
|
-
{ url: `${base}/categories` },
|
|
382
|
-
...cats.filter(inSitemap).
|
|
383
|
-
|
|
384
|
-
|
|
396
|
+
...c.categoryPath === "direct" ? [] : [{ url: `${base}/categories` }],
|
|
397
|
+
...cats.filter(inSitemap).flatMap((category) => {
|
|
398
|
+
const path = resolveCategoryPath(c, category.slug);
|
|
399
|
+
return path ? [{ url: `${siteUrl}${path}` }] : [];
|
|
400
|
+
})
|
|
385
401
|
];
|
|
386
402
|
});
|
|
387
403
|
}
|
|
388
404
|
const categories = await safeBlogArchiveCategories(config);
|
|
389
405
|
if (!categories.length) return [];
|
|
390
406
|
const blogBase = config.blogBasePath ?? "/blog";
|
|
407
|
+
const categoryHref = makeCategoryHref(blogBase, config.categoryPath ?? "namespaced");
|
|
391
408
|
return [
|
|
392
|
-
{ url: `${siteUrl}${blogBase}/categories` },
|
|
409
|
+
...config.categoryPath === "direct" ? [] : [{ url: `${siteUrl}${blogBase}/categories` }],
|
|
393
410
|
...categories.filter(inSitemap).map((category) => ({
|
|
394
|
-
url: `${siteUrl}${
|
|
411
|
+
url: `${siteUrl}${categoryHref(category.slug)}`
|
|
395
412
|
}))
|
|
396
413
|
];
|
|
397
414
|
}
|
|
@@ -432,7 +449,7 @@ function createFeedRoute(config) {
|
|
|
432
449
|
language: config.language,
|
|
433
450
|
hubUrl: config.webSubHubUrl,
|
|
434
451
|
items: rssItemsFromPosts(posts, {
|
|
435
|
-
postUrl: (p) =>
|
|
452
|
+
postUrl: (p) => postPublicUrl(p, { siteUrl, collections, inCollectionMode, blogBasePath: blogBase }) ?? `${siteUrl}${blogBase}/${p.slug}`
|
|
436
453
|
})
|
|
437
454
|
});
|
|
438
455
|
return new Response(xml, {
|
|
@@ -500,7 +517,7 @@ function createSitemapIndex(config, staticRoutes = [], opts = {}) {
|
|
|
500
517
|
}
|
|
501
518
|
|
|
502
519
|
// src/routes-llms.ts
|
|
503
|
-
import { isInCollectionMode as isInCollectionMode3
|
|
520
|
+
import { isInCollectionMode as isInCollectionMode3 } from "@roottale/cms-core";
|
|
504
521
|
function mdLinkSafe(text) {
|
|
505
522
|
return text.replace(/[[\]()]/g, "").trim();
|
|
506
523
|
}
|
|
@@ -528,7 +545,7 @@ function createLlmsTxtRoute(config) {
|
|
|
528
545
|
const inCollectionMode = isInCollectionMode3(collections);
|
|
529
546
|
lines.push("", inCollectionMode ? "## \uCF58\uD150\uCE20" : "## \uBE14\uB85C\uADF8");
|
|
530
547
|
for (const p of posts) {
|
|
531
|
-
const url = inCollectionMode ?
|
|
548
|
+
const url = inCollectionMode ? postPublicUrl(p, { siteUrl, collections, inCollectionMode: true, blogBasePath: blogBase }) : postPublicUrl(p, { siteUrl, collections, inCollectionMode: false, blogBasePath: blogBase });
|
|
532
549
|
if (!url) continue;
|
|
533
550
|
const excerpt = p.excerpt ? `: ${oneLine(p.excerpt)}` : "";
|
|
534
551
|
lines.push(`- [${mdLinkSafe(p.title)}](${url})${excerpt}`);
|
|
@@ -908,7 +925,7 @@ function createFleetInfoRoute(config = {}) {
|
|
|
908
925
|
site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
|
|
909
926
|
framework: "next",
|
|
910
927
|
cms_client: CMS_CLIENT_VERSION,
|
|
911
|
-
cms_renderer: true ? "0.
|
|
928
|
+
cms_renderer: true ? "0.57.0" : "dev",
|
|
912
929
|
build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
|
|
913
930
|
},
|
|
914
931
|
{ headers: { "cache-control": "public, max-age=300" } }
|
|
@@ -934,6 +951,7 @@ export {
|
|
|
934
951
|
decideArchiveIndex,
|
|
935
952
|
fetchBlogArchiveCategories,
|
|
936
953
|
fetchBlogArchivePosts,
|
|
954
|
+
makeCategoryHref,
|
|
937
955
|
matchRedirect,
|
|
938
956
|
normalizeRedirectPath,
|
|
939
957
|
postRedirectPath,
|
|
@@ -945,6 +963,7 @@ export {
|
|
|
945
963
|
resolvePostUrl,
|
|
946
964
|
resolveRobotsMeta,
|
|
947
965
|
safeFetchPosts,
|
|
966
|
+
storedPostPath,
|
|
948
967
|
trimSlash
|
|
949
968
|
};
|
|
950
969
|
//# sourceMappingURL=routes.js.map
|