@roottale/cms-renderer-next 0.62.0 → 0.63.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 +25 -0
- package/README.md +3 -0
- package/dist/routes.js +1 -1
- package/dist/server.d.ts +11 -4
- package/dist/server.js +10 -7
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @roottale/cms-renderer-next
|
|
2
2
|
|
|
3
|
+
## 0.63.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a858d8e: 목차 배치를 사이트 코드의 `RootTaleBlogPost.tocPosition`으로 관리할 수 있습니다.
|
|
8
|
+
명시한 값은 CMS 설정보다 우선하며, 생략한 기존 연동은 CMS 배치를 유지합니다.
|
|
9
|
+
현재 배치를 prop으로 명시하면 실제 화면을 바꾸지 않고 관리 주체를 코드로 옮깁니다.
|
|
10
|
+
|
|
11
|
+
공통 블록 디자인은 `RootTaleBlogPost.footerPatternPresentation`과
|
|
12
|
+
`RootTalePostPattern.presentation`으로 전체 대체할 수 있습니다. `null`은 원격
|
|
13
|
+
디자인 주입을 생략하고 사이트 CSS에 맡기며, 생략한 기존 연동은 원격값을 유지합니다.
|
|
14
|
+
고객 콘텐츠와 제작 디자인을 분리하는 문서·발행/미리보기 예제를 함께 갱신합니다.
|
|
15
|
+
|
|
16
|
+
공개 `RootTaleCssVars` 타입의 비공개 UI 패키지 참조도 제거해 독립 소비자의
|
|
17
|
+
strict 타입 검사를 지원합니다.
|
|
18
|
+
|
|
19
|
+
섹션 구성·디자인 변경 검사 함수를 공개 CMS core에서 공유해 편집기와 저장 단계가
|
|
20
|
+
같은 고객 편집 경계를 적용합니다.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [a858d8e]
|
|
25
|
+
- @roottale/cms-core@0.63.0
|
|
26
|
+
- @roottale/cms-client@0.63.0
|
|
27
|
+
|
|
3
28
|
## 0.62.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -170,6 +170,9 @@ export default async function PostPage({
|
|
|
170
170
|
apiKey={process.env.ROOTTALE_API_KEY!}
|
|
171
171
|
slugOrId={post.id}
|
|
172
172
|
showTableOfContents
|
|
173
|
+
tocPosition="inline"
|
|
174
|
+
theme={null}
|
|
175
|
+
footerPatternPresentation={null}
|
|
173
176
|
tableOfContentsTitle="목차"
|
|
174
177
|
breadcrumb={{ siteUrl: process.env.NEXT_PUBLIC_SITE_URL }}
|
|
175
178
|
relatedPostsCount={3}
|
package/dist/routes.js
CHANGED
|
@@ -964,7 +964,7 @@ function createFleetInfoRoute(config = {}) {
|
|
|
964
964
|
site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
|
|
965
965
|
framework: "next",
|
|
966
966
|
cms_client: CMS_CLIENT_VERSION,
|
|
967
|
-
cms_renderer: true ? "0.
|
|
967
|
+
cms_renderer: true ? "0.63.0" : "dev",
|
|
968
968
|
build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null,
|
|
969
969
|
...config.exposures ? { exposures: config.exposures } : {}
|
|
970
970
|
},
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { ReactElement, ReactNode } from 'react';
|
|
2
|
-
import { CmsPostContent, RootTaleTheme, CmsPostType } from '@roottale/cms-client/server';
|
|
1
|
+
import { ReactElement, CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { CmsPostContent, RootTaleTheme, CmsPostType, CmsSitePatternPresentation } from '@roottale/cms-client/server';
|
|
3
3
|
export { RootTaleTheme } from '@roottale/cms-client/server';
|
|
4
|
-
import { RootTaleThemeStyle } from '@roottale/ui-react/theme';
|
|
5
4
|
import { Block, BindingResolver, RouteCollection, InternalContentLinkResolver } from '@roottale/cms-core';
|
|
6
5
|
export { a as BlogCategory, B as BlogCategoryWithCount, c as collectCategories, b as collectCategoriesWithCounts, d as defaultCategoryHref, f as filterPostsByCategory, m as makeCategoryHref } from './blog-filter-qE9Lj49C.js';
|
|
7
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
@@ -81,7 +80,9 @@ interface RootTaleTableOfContentsProps {
|
|
|
81
80
|
}
|
|
82
81
|
declare function RootTaleTableOfContents(props: RootTaleTableOfContentsProps): ReactElement | null;
|
|
83
82
|
|
|
84
|
-
type RootTaleCssVars =
|
|
83
|
+
type RootTaleCssVars = CSSProperties & {
|
|
84
|
+
[customProperty: `--${string}`]: string | undefined;
|
|
85
|
+
};
|
|
85
86
|
declare function themeToCssVars(theme: RootTaleTheme | null): RootTaleCssVars | null;
|
|
86
87
|
|
|
87
88
|
/**
|
|
@@ -253,6 +254,10 @@ type RootTaleBlogPostProps = {
|
|
|
253
254
|
showTitle?: boolean;
|
|
254
255
|
showTableOfContents?: boolean;
|
|
255
256
|
tableOfContentsTitle?: string;
|
|
257
|
+
/** 사이트 코드가 정하는 목차 배치. 생략하면 기존 CMS 표시 설정을 유지한다. */
|
|
258
|
+
tocPosition?: "inline" | "sidebar";
|
|
259
|
+
/** 글 하단 공통 블록 디자인. 객체는 전체 대체, null은 사이트 CSS 사용, 생략은 기존 CMS 값 유지. */
|
|
260
|
+
footerPatternPresentation?: CmsSitePatternPresentation | null;
|
|
256
261
|
relatedPostsCount?: number;
|
|
257
262
|
relatedPostsTitle?: string;
|
|
258
263
|
postHref?: (post: CmsPostContent) => string;
|
|
@@ -293,6 +298,8 @@ interface RootTalePostPatternProps {
|
|
|
293
298
|
readonly post: Pick<CmsPostContent, "patternSlots">;
|
|
294
299
|
/** 자리 key. 기본 글 하단(`post_footer`). */
|
|
295
300
|
readonly slot?: string;
|
|
301
|
+
/** 객체는 CMS 디자인 전체 대체, null은 사이트 CSS 사용, 생략은 기존 CMS 값 유지. */
|
|
302
|
+
readonly presentation?: CmsSitePatternPresentation | null;
|
|
296
303
|
}
|
|
297
304
|
/**
|
|
298
305
|
* 자체 글 화면을 가진 사이트가 글 하단 공통 블록만 가져다 쓰는 서버 컴포넌트.
|
package/dist/server.js
CHANGED
|
@@ -1331,17 +1331,18 @@ async function resolvePostPatternFailSoft(post, input, slot = POST_FOOTER_PATTER
|
|
|
1331
1331
|
return null;
|
|
1332
1332
|
}
|
|
1333
1333
|
}
|
|
1334
|
-
function renderPostPattern(pattern, slot, input) {
|
|
1334
|
+
function renderPostPattern(pattern, slot, input, presentationOverride) {
|
|
1335
1335
|
if (!pattern) return null;
|
|
1336
|
-
const
|
|
1336
|
+
const presentationValue = presentationOverride === void 0 ? pattern.presentation : presentationOverride;
|
|
1337
|
+
const presentation = presentationValue === null ? null : sitePatternPresentationAttributes(presentationValue);
|
|
1337
1338
|
return /* @__PURE__ */ jsx13(
|
|
1338
1339
|
"aside",
|
|
1339
1340
|
{
|
|
1340
1341
|
"data-roottale-cms": "post-pattern",
|
|
1341
1342
|
"data-pattern-slot": slot,
|
|
1342
1343
|
"data-pattern-key": pattern.key,
|
|
1343
|
-
...presentation
|
|
1344
|
-
style: presentation
|
|
1344
|
+
...presentation?.dataset,
|
|
1345
|
+
style: presentation?.style,
|
|
1345
1346
|
className: "rt-cms-post-pattern",
|
|
1346
1347
|
"aria-label": pattern.name,
|
|
1347
1348
|
children: /* @__PURE__ */ jsx13(RenderTiptap, { doc: pattern.bodyJson, apiKey: input.apiKey, baseUrl: input.baseUrl })
|
|
@@ -1351,7 +1352,7 @@ function renderPostPattern(pattern, slot, input) {
|
|
|
1351
1352
|
async function RootTalePostPattern(props) {
|
|
1352
1353
|
const slot = props.slot ?? POST_FOOTER_PATTERN_SLOT;
|
|
1353
1354
|
const pattern = await resolvePostPatternFailSoft(props.post, props, slot);
|
|
1354
|
-
return renderPostPattern(pattern, slot, props);
|
|
1355
|
+
return renderPostPattern(pattern, slot, props, props.presentation);
|
|
1355
1356
|
}
|
|
1356
1357
|
|
|
1357
1358
|
// src/server/blog-post-archive.tsx
|
|
@@ -1775,6 +1776,8 @@ async function RootTaleBlogPost(props) {
|
|
|
1775
1776
|
showTitle = true,
|
|
1776
1777
|
showTableOfContents,
|
|
1777
1778
|
tableOfContentsTitle,
|
|
1779
|
+
tocPosition,
|
|
1780
|
+
footerPatternPresentation,
|
|
1778
1781
|
relatedPostsCount = 0,
|
|
1779
1782
|
locale,
|
|
1780
1783
|
relatedPostsTitle = t(locale, "relatedPostsTitle"),
|
|
@@ -1916,12 +1919,12 @@ async function RootTaleBlogPost(props) {
|
|
|
1916
1919
|
baseUrl,
|
|
1917
1920
|
toc,
|
|
1918
1921
|
tocTitle: display.tocTitle,
|
|
1919
|
-
tocPosition: display.tocPosition,
|
|
1922
|
+
tocPosition: tocPosition ?? display.tocPosition,
|
|
1920
1923
|
bodyHtml: post.bodyHtml,
|
|
1921
1924
|
internalLinks: internalLinkIndex
|
|
1922
1925
|
}
|
|
1923
1926
|
),
|
|
1924
|
-
renderPostPattern(footerPattern, POST_FOOTER_PATTERN_SLOT2, { apiKey, baseUrl }),
|
|
1927
|
+
renderPostPattern(footerPattern, POST_FOOTER_PATTERN_SLOT2, { apiKey, baseUrl }, footerPatternPresentation),
|
|
1925
1928
|
footerPattern ? null : renderPostCta(blogSettings.postCta),
|
|
1926
1929
|
showAuthorCard && authorName ? /* @__PURE__ */ jsxs13("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
|
|
1927
1930
|
authorImageUrl ? /* @__PURE__ */ jsx18(
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/PostCard.tsx","../../cms-i18n/src/index.ts","../src/TableOfContents.tsx","../../ui-react/dist/theme/index.js","../src/theme-css-vars.ts","../src/FloatingCta.tsx","../src/LeadForm.tsx","../src/block-to-react.tsx","../src/toc.ts","../src/server/blog-list.tsx","../src/server/blog-settings.ts","../src/server/fetch-posts-fail-soft.ts","../src/server/post-links.ts","../src/server/theme.tsx","../src/server/blog-categories.tsx","../src/server/blog-post.tsx","../src/server/site-patterns.tsx","../src/server/render-tiptap.tsx","../src/server/tiptap-node-renderer.tsx","../src/server/tiptap-embed-renderer.tsx","../src/server/tiptap-inline-renderer.tsx","../src/server/tiptap-types.ts","../src/server/blog-post-archive.tsx","../src/server/blog-post-body.tsx","../src/server/post-body-source.ts","../src/server/blog-post-breadcrumb.tsx","../src/server/internal-links.ts","../src/server/blog-post-footer.tsx","../src/server/preview-notice.tsx","../src/server/page.tsx","../src/server/exposure-slot.tsx"],"sourcesContent":["/**\n * `<RootTalePostCard>` — server-only summary card for a single CmsPostContent.\n *\n * Building block for `<RootTaleBlogList>` (and customer-side custom grids).\n * Pure SSR — featured image + category + title + meta + excerpt. Featured\n * image uses `CmsPostContent.featuredImageUrl` (CF Images delivery URL exposed\n * since cms-client 0.12); category is the first `terms` entry whose taxonomy is\n * `category`. Both render only when present, so text-only posts are unchanged.\n */\n\nimport type { ReactElement } from \"react\";\nimport type { CmsPostContent, CmsTermRef } from \"@roottale/cms-client/server\";\n\ntype CmsPostWithAuthor = CmsPostContent & { authorName?: string | null };\n\nexport interface RootTalePostCardProps {\n post: CmsPostContent;\n /** Customer-side URL builder. Defaults to `/blog/${slug}`. */\n href?: (post: CmsPostContent) => string;\n /**\n * 표시 옵션 (플랫폼 blog-settings + 글별 override resolve 결과). 미지정 시\n * 발행일만 표시(작성자 없음) — 직접 사용하는 커스텀 그리드 하위호환.\n * `showImage`/`showCategory` 는 데이터가 있을 때 노출 여부(기본 노출).\n */\n display?: {\n showDate: boolean;\n showAuthor: boolean;\n showImage?: boolean;\n showCategory?: boolean;\n };\n}\n\nfunction firstCategoryName(terms: CmsTermRef[] | undefined | null): string | null {\n if (!terms) return null;\n const cat = terms.find((t) => t.taxonomy === \"category\");\n return cat ? cat.name : null;\n}\n\nexport function RootTalePostCard(props: RootTalePostCardProps): ReactElement {\n const { post, href = defaultHref, display } = props;\n const showDate = display ? display.showDate : true;\n const showAuthor = display ? display.showAuthor : false;\n const showImage = display?.showImage !== false;\n const showCategory = display?.showCategory !== false;\n const authorName = (post as CmsPostWithAuthor).authorName;\n const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : \"\";\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const imageUrl = showImage ? post.featuredImageUrl : null;\n const category = showCategory ? firstCategoryName(post.terms) : null;\n return (\n <article data-roottale-cms=\"card\" className=\"rt-cms-card\">\n <a className=\"rt-cms-card-link\" href={href(post)}>\n {imageUrl ? (\n <img\n className=\"rt-cms-card-image\"\n src={imageUrl}\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n ) : null}\n {category ? (\n <span className=\"rt-cms-card-category\">{category}</span>\n ) : null}\n {publishedDate || (showAuthor && authorName) || views !== null ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"date\">\n {publishedDate}\n </span>\n ) : null}\n {showAuthor && authorName ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자 {authorName}\n </span>\n ) : null}\n {/* ADR-0065 — 조회수는 사이트 공개 토글 on 일 때만 API 가 viewCount 를\n 내려준다. 따라서 값이 있으면 그대로 노출(별도 표시 설정 불필요). */}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n <h2 className=\"rt-cms-title\">{post.title}</h2>\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n <span className=\"rt-cms-card-action\" aria-hidden=\"true\">\n 읽기\n </span>\n </a>\n </article>\n );\n}\n\nfunction defaultHref(post: CmsPostContent): string {\n return `/blog/${post.slug}`;\n}\n\nfunction formatPublishedDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(d);\n}\n","/**\n * `@roottale/cms-i18n` — RootTale CMS 렌더러 UI 문자열 (ADR-0034 §3).\n *\n * **콘텐츠 다국어(ADR-0052 A안, `posts.locale`/`translations[]`)와는 별개 층** —\n * 이 패키지는 렌더러가 소비자에게 보여주는 *고정 UI 카피*(빈 목록 안내, 목차\n * 제목 등)만 다룬다. 글 본문 번역은 사람이 admin 에서 직접 작성한다(§0).\n *\n * Phase 1(W4-6 PR C2) = 최소 실체화: locale 별 사전 + 타입 안전 `t()`.\n * gettext(.po 로딩)·ICU 플러럴 등은 Phase 4 몫 — **과설계 금지**\n * (ADR-0034 §3, `tenant-site-i18n-plan.md` §3 PR C2).\n */\n\n/** 사전이 존재하는 locale. 그 외(`ja`/`zh-hans` 등)는 `t()` 가 ko 로 fail-soft. */\nexport const CMS_I18N_LOCALES = [\"ko\", \"en\"] as const;\nexport type CmsI18nLocale = (typeof CMS_I18N_LOCALES)[number];\n\n/**\n * 사전 키 — `cms-renderer-next` 의 고객 노출 기본 문자열(1차 실체화 실측 범위)만.\n * 새 키를 늘릴 땐 ko/en 양쪽에 반드시 값을 채운다(타입이 강제).\n */\nexport interface CmsI18nDict {\n /** `RootTaleBlogList` — 발행 글 0건. */\n blogListEmpty: string;\n /** `RootTaleBlogCategories` — 카테고리 0건. */\n blogCategoriesEmpty: string;\n /** `RootTaleBlogList` — 카테고리 필터 \"전체\" 칩. */\n allCategoriesLabel: string;\n /** `RootTaleBlogPost` — 관련 글 섹션 제목. */\n relatedPostsTitle: string;\n /** `RootTaleBlogPost` — 글 미발견 기본 안내. */\n postNotFound: string;\n /** `RootTalePage` — 페이지 미발견 기본 안내. */\n pageNotFound: string;\n /** `RootTaleBlogPost` — 미리보기 링크 만료 기본 안내. */\n previewExpired: string;\n /** `RootTalePreviewNotice` — 미리보기 화면 상단 안내. */\n previewNotice: string;\n /** `RootTaleTableOfContents` — 목차 기본 제목. */\n tableOfContentsTitle: string;\n}\n\nconst DICTS: Record<CmsI18nLocale, CmsI18nDict> = {\n ko: {\n blogListEmpty: \"아직 발행된 글이 없습니다.\",\n blogCategoriesEmpty: \"아직 카테고리가 없습니다.\",\n allCategoriesLabel: \"전체\",\n relatedPostsTitle: \"관련 글\",\n postNotFound: \"글을 찾을 수 없습니다.\",\n pageNotFound: \"페이지를 찾을 수 없습니다.\",\n previewExpired: \"미리보기 링크가 만료됐어요. 관리자 편집기에서 미리보기를 다시 열어 주세요.\",\n previewNotice: \"미리보기 — 아직 공개되지 않은 내용이며 검색에 노출되지 않습니다.\",\n tableOfContentsTitle: \"목차\",\n },\n en: {\n blogListEmpty: \"No posts published yet.\",\n blogCategoriesEmpty: \"No categories yet.\",\n allCategoriesLabel: \"All\",\n relatedPostsTitle: \"Related posts\",\n postNotFound: \"Post not found.\",\n pageNotFound: \"Page not found.\",\n previewExpired: \"This preview link has expired. Open the preview again from the admin editor.\",\n previewNotice: \"Preview — this content is not public and is hidden from search engines.\",\n tableOfContentsTitle: \"Table of contents\",\n },\n};\n\n/** 사전 없는 locale(`ja`/`zh-hans`/undefined 등) 은 ko 로 fail-soft — 문자열이 아예 안 나오는 것보다 낫다. */\nfunction resolveLocale(locale: string | undefined): CmsI18nLocale {\n return locale === \"en\" ? \"en\" : \"ko\";\n}\n\n/**\n * 타입 안전 `t()`. `locale` 를 안 주거나(undefined) 사전이 없는 locale 이면 ko\n * 기본값 — **하위호환**: locale 프로퍼티를 모르는 기존 소비자는 지금까지처럼\n * 한글 문자열을 그대로 받는다(diff 0).\n *\n * ```ts\n * import { t } from \"@roottale/cms-i18n\";\n * const emptyMessage = props.emptyMessage ?? t(props.locale, \"blogListEmpty\");\n * ```\n */\nexport function t(locale: string | undefined, key: keyof CmsI18nDict): string {\n return DICTS[resolveLocale(locale)][key];\n}\n\nexport const PACKAGE_NAME = \"@roottale/cms-i18n\";\n","/**\n * `<RootTaleTableOfContents>` — server-only anchor list for blog posts.\n *\n * Pure SSR. Renders `<nav>` + ordered anchors that link to `#<id>` slots\n * emitted by `attachHeadingIds`. No scroll-spy here — that requires an\n * `IntersectionObserver` which only exists in the browser; a client-island\n * variant can layer on top later without changing the data contract.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/TableOfContents.tsx`\n * (had `'use client'` + IntersectionObserver). Stripped to server-safe form per\n * the platform renderer contract (no `'use client'` boundary — ADR-0023 §5.3).\n */\n\nimport type { ReactElement } from \"react\";\n// ADR-0034 §3 (W4-6 PR C2) — 기본 제목의 locale 해석(콘텐츠 다국어와 별개 층).\nimport { t } from \"@roottale/cms-i18n\";\n\nimport type { TocEntry } from \"./toc.js\";\n\nexport interface RootTaleTableOfContentsProps {\n entries: TocEntry[];\n title?: string;\n /** 명시 `title` 없을 때 기본 제목의 locale. 미지정 = ko(\"목차\"). */\n locale?: string;\n}\n\nexport function RootTaleTableOfContents(\n props: RootTaleTableOfContentsProps,\n): ReactElement | null {\n const { entries, locale, title = t(locale, \"tableOfContentsTitle\") } = props;\n if (entries.length === 0) return null;\n return (\n <nav data-roottale-cms=\"toc\" className=\"rt-cms-toc\" aria-label={title}>\n <details className=\"rt-cms-toc-details\" open>\n <summary className=\"rt-cms-toc-summary\">\n {title ? <span className=\"rt-cms-toc-title\">{title}</span> : null}\n <span className=\"rt-cms-toc-toggle\" aria-hidden=\"true\" />\n </summary>\n <ol className=\"rt-cms-toc-list\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className={`rt-cms-toc-item rt-cms-toc-level-${entry.level}`}\n >\n <a href={`#${entry.id}`}>{entry.text}</a>\n </li>\n ))}\n </ol>\n </details>\n </nav>\n );\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nexport function themeToCssVars(theme) {\n return {\n ...(theme.colors?.primary ? { \"--rt-color-primary\": theme.colors.primary } : {}),\n ...(theme.colors?.primaryForeground ? { \"--rt-color-primary-foreground\": theme.colors.primaryForeground } : {}),\n ...(theme.colors?.secondary ? { \"--rt-color-secondary\": theme.colors.secondary } : {}),\n ...(theme.colors?.secondaryForeground ? { \"--rt-color-secondary-foreground\": theme.colors.secondaryForeground } : {}),\n ...(theme.colors?.accent ? { \"--rt-color-accent\": theme.colors.accent } : {}),\n ...(theme.colors?.accentForeground ? { \"--rt-color-accent-foreground\": theme.colors.accentForeground } : {}),\n ...(theme.colors?.background ? { \"--rt-color-background\": theme.colors.background } : {}),\n ...(theme.colors?.foreground ? { \"--rt-color-foreground\": theme.colors.foreground } : {}),\n ...(theme.colors?.muted ? { \"--rt-color-muted\": theme.colors.muted } : {}),\n ...(theme.colors?.mutedForeground ? { \"--rt-color-muted-foreground\": theme.colors.mutedForeground } : {}),\n ...(theme.colors?.destructive ? { \"--rt-color-destructive\": theme.colors.destructive } : {}),\n ...(theme.colors?.destructiveForeground ? { \"--rt-color-destructive-foreground\": theme.colors.destructiveForeground } : {}),\n ...(theme.colors?.border ? { \"--rt-color-border\": theme.colors.border } : {}),\n ...(theme.colors?.input ? { \"--rt-color-input\": theme.colors.input } : {}),\n ...(theme.colors?.ring ? { \"--rt-color-ring\": theme.colors.ring } : {}),\n ...(theme.fonts?.body ? { \"--rt-font-body\": theme.fonts.body } : {}),\n ...(theme.fonts?.display ? { \"--rt-font-display\": theme.fonts.display } : {}),\n ...(theme.radius?.sm ? { \"--rt-radius-sm\": theme.radius.sm } : {}),\n ...(theme.radius?.md ? { \"--rt-radius-md\": theme.radius.md } : {}),\n ...(theme.radius?.lg ? { \"--rt-radius-lg\": theme.radius.lg } : {}),\n ...(theme.radius?.xl ? { \"--rt-radius-xl\": theme.radius.xl } : {}),\n };\n}\nexport function RootTaleThemeProvider({ children, name = \"custom\", theme, style, ...props }) {\n return (_jsx(\"div\", { ...props, \"data-rt-theme\": name, style: { ...style, ...themeToCssVars(theme) }, children: children }));\n}\n","import type { RootTaleTheme } from \"@roottale/cms-client/server\";\nimport {\n themeToCssVars as themeToUiCssVars,\n type RootTaleThemeStyle,\n} from \"@roottale/ui-react/theme\";\n\nexport type RootTaleCssVars = RootTaleThemeStyle;\n\nexport function themeToCssVars(theme: RootTaleTheme | null): RootTaleCssVars | null {\n if (!theme) return null;\n const vars = themeToUiCssVars(theme);\n if (Object.keys(vars).length === 0) return null;\n return vars;\n}\n","/**\n * `<RootTaleFloatingCta>` — fixed-position call-to-action stack.\n *\n * Server-only (anchors only, no state). External targets (`kakao` / `custom`)\n * open in a new tab with `rel=\"noopener noreferrer\"`; tel/contact stay in the\n * same tab so iOS/Android can trigger the dialer / mailto handler.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/FloatingCta.tsx`.\n */\n\nimport type { ReactElement } from \"react\";\n\nexport type CtaButtonType = \"phone\" | \"contact\" | \"kakao\" | \"custom\";\n\nexport interface CtaButton {\n type: CtaButtonType;\n label: string;\n href: string;\n /** Optional override icon (emoji or short text). */\n icon?: string;\n}\n\nexport type FloatingCtaPosition =\n | \"bottom-right\"\n | \"bottom-left\"\n | \"bottom-center\";\n\nexport interface RootTaleFloatingCtaProps {\n buttons: CtaButton[];\n position?: FloatingCtaPosition;\n}\n\nexport function RootTaleFloatingCta(\n props: RootTaleFloatingCtaProps,\n): ReactElement | null {\n const { buttons, position = \"bottom-right\" } = props;\n if (!buttons || buttons.length === 0) return null;\n return (\n <div\n data-roottale-cms=\"floating-cta\"\n className={`rt-cms-floating-cta rt-cms-floating-cta--${position}`}\n >\n {buttons.map((btn, idx) => {\n const isExternal = btn.type === \"kakao\" || btn.type === \"custom\";\n return (\n <a\n key={`${btn.type}-${idx}`}\n href={btn.href}\n className={`rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`}\n target={isExternal ? \"_blank\" : undefined}\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n <span aria-hidden=\"true\" className=\"rt-cms-floating-cta__icon\">\n {btn.icon ?? defaultIcon(btn.type)}\n </span>\n <span className=\"rt-cms-floating-cta__label\">{btn.label}</span>\n </a>\n );\n })}\n </div>\n );\n}\n\nfunction defaultIcon(type: CtaButtonType): string {\n switch (type) {\n case \"phone\":\n return \"☎\";\n case \"contact\":\n return \"✉\";\n case \"kakao\":\n return \"💬\";\n case \"custom\":\n return \"→\";\n }\n}\n","/**\n * `@roottale/cms-renderer-next` — RootTaleLeadForm RSC.\n *\n * 외부 고객 사이트(roottale.com, 고객사 외주)의 진단 신청 폼.\n *\n * 권장 연동(API key 하나): `action` 을 *사이트 자체* 의 Route Handler / 폼\n * action 으로 두고, 그 서버 코드가 `@roottale/cms-client` 의 `submitInquiry()` 로\n * 블로그 조회와 동일한 `ROOTTALE_API_KEY` 를 실어 intake API 로 forward 한다. 키가\n * tenant 를 식별 → 리드가 그 tenant 의 admin /crm 으로 라우팅, `tenant_slug` 불필요.\n * (API key 는 브라우저로 노출 금지 — 직접 api.roottale.com 으로 POST 하지 말 것.)\n *\n * PII 는 server 측에서 암호화 후 `inquiries` insert, 302 `?ok=1` / `?err=*` redirect.\n *\n * vertical prop:\n * - 미지정 = \"분야 선택\" select 노출 (consulting/medical/tax/legal), 필드셋은\n * 기존 B2B 세트 그대로(방문자가 vertical 을 스스로 고르는 범용 폼이라 필드를\n * vertical 별로 분기하지 않음)\n * - 지정 = hidden input + 표시 라벨 + vertical 별 추가 필드\n * (medical = 국외이전 동의 — ADR-0018)\n *\n * vertical=\"medical\" 필드셋 (C5 1차, premium-component-backlog):\n * 환자가 채우는 문의 폼에 B2B 리드 필드(\"사업체명\" 필수·\"현재 사이트 URL\")를\n * 노출하던 정합성 문제를 수정 — 의료는 이름·전화번호(필수) + 상담 희망\n * 분야/증상·문의 내용(선택)만 노출한다. intake 계약(tenant-api `POST\n * /v1/public/inquiries`, `packages/api-core/src/routes/inquiries.ts`)이\n * `business_name` 을 vertical 불문 hard-required 로 검증하므로(신규 폼 백엔드\n * 금지 — F3 계약 고정), 화면에는 안 보이지만 hidden input 으로 자리표시자\n * `\"-\"` 를 전송해 계약을 만족시킨다. \"상담 희망 분야/증상\" 은 기존\n * `consultation_field` 키(어드민 CRM \"상담 분야\" 표시)로, \"문의 내용\" 은 기존\n * `message` 키로 매핑 — 둘 다 이미 intake 가 선택 필드로 수용하던 키라 서버\n * 변경이 필요 없다. \"현재 사이트 URL\" 은 의료에 무의미해 필드 자체를 생략.\n *\n * redirectPath prop:\n * - 외부 사이트가 폼 제출 후 돌아갈 path (API 가 allowlist 검증, ?ok=1 / ?err=*)\n *\n * RSC form. Turnstile를 켠 경우 Cloudflare 위젯 script만 추가한다.\n * customer-facing className 전체 `.rt-cms-*` prefix, scoped via\n * `[data-roottale-cms]` (cms-public.css). customer override 자유.\n */\n\nimport type { ReactElement } from \"react\";\n\nimport { LeadFormMeta } from \"./lead-form-meta.client.js\";\n\nexport type LeadFormVertical = \"consulting\" | \"medical\" | \"tax\" | \"legal\";\n\nexport interface RootTaleLeadFormProps {\n /**\n * 폼 제출 대상 — *사이트 자체* 의 server 엔드포인트(Route Handler / form action).\n * 그 서버 코드가 `cms-client` `submitInquiry()` 로 API key 를 실어 forward 한다.\n * 예: `/api/inquiry`. (API key 노출 위험 때문에 intake API 직접 지정 금지.)\n */\n action: string;\n /**\n * vertical 고정. 미지정 시 select 노출.\n * `medical` 일 때 국외이전 동의 체크박스 추가 (ADR-0018).\n */\n vertical?: LeadFormVertical;\n /**\n * 폼 제출 후 API가 돌려보낼 절대 URL.\n * tenant-api `LEAD_INTAKE_ALLOWED_ORIGINS` allowlist 검증. fail = fallback.\n * 미지정 시 tenant-api env의 LEAD_INTAKE_REDIRECT_BASE 사용.\n */\n redirectUrl?: string;\n heading?: string;\n description?: string;\n submitLabel?: string;\n turnstileSiteKey?: string;\n /** 추가 클래스 (customer 디자인 hook). */\n className?: string;\n}\n\nconst VERTICAL_LABEL: Record<LeadFormVertical, string> = {\n consulting: \"컨설팅\",\n medical: \"의료\",\n tax: \"세무\",\n legal: \"법률\",\n};\n\nconst VERTICAL_OPTIONS: { value: LeadFormVertical; label: string }[] = [\n { value: \"consulting\", label: \"컨설팅 / 일반\" },\n { value: \"medical\", label: \"의료 (병의원·치과·한의원)\" },\n { value: \"tax\", label: \"세무 (세무사·회계사)\" },\n { value: \"legal\", label: \"법률 (법무법인·변호사)\" },\n];\n\nexport function RootTaleLeadForm(\n props: RootTaleLeadFormProps,\n): ReactElement {\n const {\n action,\n vertical,\n redirectUrl,\n heading,\n description,\n submitLabel = \"진단 신청\",\n turnstileSiteKey,\n className,\n } = props;\n\n const formClass = [\"rt-cms-lead-form\", className].filter(Boolean).join(\" \");\n const isMedical = vertical === \"medical\";\n\n return (\n <form\n method=\"post\"\n action={action}\n data-roottale-cms=\"lead-form\"\n className={formClass}\n >\n {/* 어트리뷰션·여정 계측 island(RSC 경계 §5) — 시각 출력 없음. v1 은\n `form_id` 를 \"contact\" 로 고정한다(사이트당 실배치 리드 폼이\n /contact 1개뿐 — 인스턴스별 식별이 필요해지면 공개 prop 신설 검토). */}\n <LeadFormMeta formId=\"contact\" />\n {heading ? (\n <h2 className=\"rt-cms-lead-heading\">{heading}</h2>\n ) : null}\n {description ? (\n <p className=\"rt-cms-lead-description\">{description}</p>\n ) : null}\n\n {redirectUrl ? (\n <input type=\"hidden\" name=\"_redirect_url\" value={redirectUrl} />\n ) : null}\n\n {vertical ? (\n <>\n <input type=\"hidden\" name=\"vertical\" value={vertical} />\n <p className=\"rt-cms-lead-vertical-label\">\n 분야: <strong>{VERTICAL_LABEL[vertical]}</strong>\n </p>\n </>\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 분야 <span aria-hidden=\"true\">*</span>\n </span>\n <select\n name=\"vertical\"\n required\n className=\"rt-cms-field-input rt-cms-field-select\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>\n 선택하세요\n </option>\n {VERTICAL_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n )}\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이름 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"contact_name\"\n required\n autoComplete=\"name\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n {isMedical ? (\n // 환자 문의에는 \"사업체명\" 개념이 없다 — 화면에는 노출하지 않되,\n // intake 가 vertical 불문 hard-required 로 검증하는 계약(위 파일\n // 헤더 코멘트 참조)을 만족시키기 위해 자리표시자만 hidden 전송.\n <input type=\"hidden\" name=\"business_name\" value=\"-\" />\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 사업체명 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"business_name\"\n required\n autoComplete=\"organization\"\n className=\"rt-cms-field-input\"\n />\n </label>\n )}\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 전화번호 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"tel\"\n name=\"phone\"\n required\n autoComplete=\"tel\"\n inputMode=\"tel\"\n pattern={\"0\\\\d{1,2}-?\\\\d{3,4}-?\\\\d{4}\"}\n placeholder=\"010-1234-5678\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이메일 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"email\"\n name=\"email\"\n required\n autoComplete=\"email\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n {isMedical ? (\n <>\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 상담 희망 분야·증상 (선택)\n </span>\n <input\n type=\"text\"\n name=\"consultation_field\"\n placeholder=\"예: 무릎 통증, 정기검진\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">문의 내용 (선택)</span>\n <textarea\n name=\"message\"\n rows={4}\n placeholder=\"증상이 언제부터 있었는지, 궁금하신 점을 자유롭게 적어주세요.\"\n className=\"rt-cms-field-input rt-cms-field-textarea\"\n />\n </label>\n </>\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">현재 사이트 URL (선택)</span>\n <input\n type=\"url\"\n name=\"current_site_url\"\n placeholder=\"https://\"\n className=\"rt-cms-field-input\"\n />\n <span className=\"rt-cms-field-hint\">없으면 비워두세요.</span>\n </label>\n )}\n\n <label className=\"rt-cms-lead-consent\">\n <input type=\"checkbox\" name=\"privacy_consent\" required />\n <span>\n <strong>(필수)</strong> 개인정보 수집·이용에 동의합니다. (수집 항목:{\" \"}\n {isMedical\n ? \"이름·연락처·이메일·상담 내용(작성 시)·사이트 이용 기록(방문 페이지·상호작용)\"\n : \"이름·연락처·이메일·사업체명·사이트 이용 기록(방문 페이지·상호작용)\"}\n . 보관 기간: 문의 종료 후 3년)\n </span>\n </label>\n\n {vertical === \"medical\" ? (\n <label className=\"rt-cms-lead-consent\">\n <input\n type=\"checkbox\"\n name=\"overseas_transfer_consent\"\n required\n />\n <span>\n <strong>(필수, 의료)</strong> 의료 PII 의 국외이전(보관·처리)에\n 동의합니다. (의료법 §21·개인정보보호법 §28 — ADR-0018)\n </span>\n </label>\n ) : null}\n\n {turnstileSiteKey ? (\n <>\n <script\n src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\"\n async\n defer\n />\n <div\n className=\"cf-turnstile\"\n data-sitekey={turnstileSiteKey}\n data-action=\"turnstile-spin-v1\"\n />\n </>\n ) : null}\n\n <div className=\"rt-cms-lead-actions\">\n <button type=\"submit\" className=\"rt-cms-lead-submit\">\n {submitLabel}\n </button>\n </div>\n </form>\n );\n}\n","// ADR-0034 §1.5 amended — Block JSON → React elements\n//\n// Next/RSC public renderer 가 본 함수로 block tree 를 server-side React element\n// 로 변환. cms-renderer-astro 의 `block-to-html.ts` 와 1:1 대응 (같은 7 + α\n// block 핸들러). `BlockDefinition.nextRender` 정합.\n//\n// 알려지지 않은 block 은 rawHtml 출력 (codex v3 verdict #4, opaque atom\n// round-trip 보존).\n\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport {\n type Block,\n type BindingResolver,\n applyBlockBindings,\n createFieldBindingResolver,\n sanitizeHtml,\n} from \"@roottale/cms-core\";\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nfunction htmlContent(html: string): { __html: string } {\n // dangerouslySetInnerHTML 진입 전 단일 chokepoint — 모든 block 의\n // rawHtml/content 는 여기서 sanitize 된다 (cms-core sanitizeHtml). API 키로\n // 글을 작성하는 외부 writer 가 inline `<script>`/`on*`/`javascript:` 를\n // 주입하는 저장형 XSS 방어.\n return { __html: sanitizeHtml(html) };\n}\n\nfunction renderInner(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * 단일 block 을 ReactElement 로 변환. `block-to-html.ts` 와 동일 분기.\n */\nexport function renderBlock(block: Block, key: number = 0): ReactElement {\n const dataBlockId = block._id;\n\n switch (block.name) {\n case \"core/paragraph\": {\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n return (\n <p\n key={key}\n data-block-id={dataBlockId}\n dangerouslySetInnerHTML={htmlContent(text)}\n />\n );\n }\n case \"core/heading\": {\n const levelRaw = block.attributes.level;\n const level =\n typeof levelRaw === \"number\" && levelRaw >= 2 && levelRaw <= 4\n ? levelRaw\n : 2;\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const props = {\n \"data-block-id\": dataBlockId,\n dangerouslySetInnerHTML: htmlContent(text),\n } as const;\n /* eslint-disable jsx-a11y/heading-has-content -- 내용은 props 의\n dangerouslySetInnerHTML 로 들어간다(CMS 본문 HTML). 룰은 JSX children\n 만 보고 \"빈 제목\"으로 판단하는데, 빈 제목은 위에서 걸러진다. */\n if (level === 2) return <h2 key={key} {...props} />;\n if (level === 3) return <h3 key={key} {...props} />;\n return <h4 key={key} {...props} />;\n /* eslint-enable jsx-a11y/heading-has-content */\n }\n case \"core/image\": {\n const src = isString(block.attributes.url) ? block.attributes.url : \"\";\n const alt = isString(block.attributes.alt) ? block.attributes.alt : \"\";\n const caption = isString(block.attributes.caption)\n ? block.attributes.caption\n : null;\n return (\n <figure key={key} data-block-id={dataBlockId}>\n <img src={src} alt={alt} loading=\"lazy\" />\n {caption && <figcaption>{caption}</figcaption>}\n </figure>\n );\n }\n case \"core/list\": {\n const ordered = block.attributes.ordered === true;\n const items = (block.innerBlocks ?? []).map(\n (item, i): ReactNode =>\n item.rawHtml ? (\n <li\n key={i}\n dangerouslySetInnerHTML={htmlContent(item.rawHtml)}\n />\n ) : (\n <li key={i}>{renderInner(item.innerBlocks ?? [])}</li>\n ),\n );\n return ordered ? (\n <ol key={key} data-block-id={dataBlockId}>\n {items}\n </ol>\n ) : (\n <ul key={key} data-block-id={dataBlockId}>\n {items}\n </ul>\n );\n }\n case \"core/quote\": {\n const inner = renderInner(block.innerBlocks ?? []);\n const citation = isString(block.attributes.citation)\n ? block.attributes.citation\n : null;\n return (\n <blockquote key={key} data-block-id={dataBlockId}>\n {inner}\n {citation && <cite>{citation}</cite>}\n </blockquote>\n );\n }\n case \"core/code\": {\n const code = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const lang = isString(block.attributes.language)\n ? block.attributes.language\n : undefined;\n return (\n <pre\n key={key}\n data-block-id={dataBlockId}\n data-language={lang}\n >\n <code>{code}</code>\n </pre>\n );\n }\n case \"core/columns\": {\n return (\n <div\n key={key}\n className=\"rt-columns\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n case \"core/separator\":\n return <hr key={key} data-block-id={dataBlockId} />;\n case \"core/spacer\": {\n const heightRaw = block.attributes.height;\n const height = isString(heightRaw) ? heightRaw : \"32px\";\n return (\n <div\n key={key}\n className=\"rt-spacer\"\n data-block-id={dataBlockId}\n style={{ height }}\n />\n );\n }\n case \"core/group\": {\n return (\n <div\n key={key}\n className=\"rt-group\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n default: {\n // codex review §15 — 알려지지 않은 block 의 rawHtml 을 그대로\n // `dangerouslySetInnerHTML` 로 내보내면 API key 로 글을 작성하는 외부\n // 클라이언트가 임의 HTML/script 를 외부 사이트로 삽입할 수 있다 (XSS).\n // rawHtml 분기 자체를 제거 — 후속 PR 에서 cms-core 의 sanitize 모듈을\n // 도입한 뒤에만 raw 출력을 허용한다.\n const rawHtml = block.rawHtml;\n return (\n <div\n key={key}\n className=\"rt-unknown-block\"\n data-block-id={dataBlockId}\n data-block-name={block.name}\n data-rawhtml-suppressed={rawHtml ? \"true\" : undefined}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n }\n}\n\n/** Block tree 전체를 React element 배열로 직렬화. */\nexport function renderBlocks(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * ADR-0041 §7 — Block Bindings 를 먼저 적용한 뒤 렌더.\n * `metadata.bindings` 의 `roottale/field`/`acf/field` 를 `fields`(acf 값)로\n * 치환. 바인딩 없으면 renderBlocks 와 동일.\n */\nexport function renderBoundBlocks(\n blocks: readonly Block[],\n opts: {\n fields?: Record<string, unknown> | null;\n resolver?: BindingResolver;\n } = {},\n): ReactElement[] {\n const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);\n return renderBlocks(applyBlockBindings(blocks, resolver));\n}\n","/**\n * `@roottale/cms-renderer/toc` — Tiptap doc → TOC entries + heading id injection.\n *\n * Server-only utility. Walks a Tiptap JSON doc, extracts H1-H4 headings, and\n * derives stable kebab-case ids (Korean-safe). The companion `attachHeadingIds`\n * mutates a shallow clone of the doc so renderers can emit heading anchors\n * that match what `<RootTaleTableOfContents>` links to.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/toc.ts`\n * (HTML-string based, vendored snapshot). Ported to Tiptap-JSON because the\n * platform renderer no longer round-trips through HTML before render.\n */\n\nexport interface TocEntry {\n level: 2 | 3 | 4;\n text: string;\n id: string;\n}\n\ntype TiptapNode = {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown>;\n content?: TiptapNode[];\n marks?: { type?: string }[];\n};\n\ntype TiptapDoc = { type?: string; content?: TiptapNode[] };\n\nfunction tocLevel(rawLevel: number): TocEntry[\"level\"] | null {\n if (rawLevel < 1 || rawLevel > 4) return null;\n if (rawLevel < 3) return 2;\n return rawLevel === 3 ? 3 : 4;\n}\n\nexport function headingToId(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction textOf(node: TiptapNode): string {\n if (typeof node.text === \"string\") return node.text;\n if (!Array.isArray(node.content)) return \"\";\n return node.content.map(textOf).join(\"\");\n}\n\nexport function extractToc(doc: Record<string, unknown>): TocEntry[] {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n const entries: TocEntry[] = [];\n const idCounts = new Map<string, number>();\n\n for (const node of content) {\n if (node?.type !== \"heading\") continue;\n const rawLevel = Number(node.attrs?.level ?? 2);\n const level = tocLevel(rawLevel);\n if (level === null) continue;\n const text = textOf(node).trim();\n if (!text) continue;\n\n const base = headingToId(text) || \"heading\";\n const count = idCounts.get(base) ?? 0;\n const id = count === 0 ? base : `${base}-${count}`;\n idCounts.set(base, count + 1);\n\n entries.push({ level, text, id });\n }\n\n return entries;\n}\n\n/**\n * Returns a shallow-cloned Tiptap doc where each H1-H4 carries the matching\n * `attrs.id` from `entries` (same order as `extractToc` returns). Existing\n * `id` attrs are overwritten so renderer + TOC always agree on anchors.\n */\nexport function attachHeadingIds(\n doc: Record<string, unknown>,\n entries: TocEntry[],\n): Record<string, unknown> {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n let cursor = 0;\n const nextContent = content.map((node) => {\n if (node?.type !== \"heading\") return node;\n const rawLevel = Number(node.attrs?.level ?? 2);\n if (rawLevel < 1 || rawLevel > 4) return node;\n if (!textOf(node).trim()) return node;\n const entry = entries[cursor++];\n if (!entry) return node;\n return {\n ...node,\n attrs: { ...(node.attrs ?? {}), id: entry.id },\n };\n });\n return { ...(root as Record<string, unknown>), content: nextContent };\n}\n","import type { ReactElement } from \"react\";\nimport {\n resolvePostDisplay,\n type CmsPostContent,\n type CmsPostType,\n} from \"@roottale/cms-client/server\";\nimport {\n postsForCollection,\n postsNotInAnyCollection,\n type RouteCollection,\n} from \"@roottale/cms-core\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport {\n collectCategories,\n defaultCategoryHref,\n filterPostsByCategory,\n} from \"../blog-filter.js\";\nimport { RootTalePostCard } from \"../PostCard.js\";\nimport { fetchBlogSettingsFailSoft } from \"./blog-settings.js\";\nimport { fetchPostsFailSoft } from \"./fetch-posts-fail-soft.js\";\nimport { defaultPostHref, makeCollectionPostHref } from \"./post-links.js\";\nimport {\n resolveThemeStyleFailSoft,\n type RootTaleThemeInput,\n} from \"./theme.js\";\n\nexport type RootTaleBlogListProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n /** Filter by post type (`post` or `page`). Default = `post` only. */\n type?: CmsPostType;\n postHref?: (post: CmsPostContent) => string;\n emptyMessage?: string;\n collection?: string;\n collections?: readonly RouteCollection[];\n /**\n * `collection`과 배타적이다. `collections` 없이 지정해도 예외를 던진다.\n */\n excludeCollections?: boolean;\n showCategoryFilter?: boolean;\n activeCategory?: string;\n categoryHref?: (slug: string | null) => string;\n allCategoriesLabel?: string;\n author?: string;\n theme?: RootTaleThemeInput;\n locale?: string;\n};\n\ntype ScopePostsInput = {\n readonly posts: CmsPostContent[];\n readonly collection?: string;\n readonly collections?: readonly RouteCollection[];\n readonly excludeCollections: boolean;\n};\n\nfunction validateCollectionScope(props: RootTaleBlogListProps): void {\n if (props.collection && props.excludeCollections) {\n throw new Error(\n \"RootTaleBlogList: `collection` 과 `excludeCollections` 를 동시에 줄 수 없습니다 — 포지티브/네거티브 스코프는 배타적입니다.\",\n );\n }\n if (props.excludeCollections && !props.collections) {\n throw new Error(\n \"RootTaleBlogList: `excludeCollections` 는 `collections` 와 함께 줘야 합니다.\",\n );\n }\n}\n\nfunction scopePosts(input: ScopePostsInput): CmsPostContent[] {\n const { posts, collection, collections, excludeCollections } = input;\n if (collection && collections) {\n return postsForCollection(posts, collections, collection);\n }\n if (excludeCollections && collections) {\n return postsNotInAnyCollection(posts, collections);\n }\n return posts;\n}\n\nexport async function RootTaleBlogList(\n props: RootTaleBlogListProps,\n): Promise<ReactElement> {\n validateCollectionScope(props);\n\n const {\n apiKey,\n baseUrl,\n limit,\n type = \"post\",\n collection,\n collections,\n excludeCollections = false,\n locale,\n emptyMessage = t(locale, \"blogListEmpty\"),\n showCategoryFilter = false,\n activeCategory,\n categoryHref = defaultCategoryHref,\n allCategoriesLabel = t(locale, \"allCategoriesLabel\"),\n author,\n theme: themeProp,\n } = props;\n const postHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n\n const [page, themeStyle, blogSettings] = await Promise.all([\n fetchPostsFailSoft({\n apiKey,\n baseUrl,\n limit,\n type,\n ...(author ? { author } : {}),\n ...(collection && collections ? { collectionKey: collection } : {}),\n ...(excludeCollections && collections ? { excludeCollections: true } : {}),\n ...(locale ? { locale } : {}),\n }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n fetchBlogSettingsFailSoft({ apiKey, baseUrl }),\n ]);\n const items = scopePosts({\n posts: page.items,\n collection,\n collections,\n excludeCollections,\n });\n\n if (items.length === 0) {\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n </div>\n );\n }\n\n const categories = showCategoryFilter ? collectCategories(items) : [];\n const visiblePosts = showCategoryFilter\n ? filterPostsByCategory(items, activeCategory)\n : items;\n const isAllCategoriesActive = !activeCategory;\n\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n {showCategoryFilter && categories.length > 0 ? (\n <nav className=\"rt-cms-filter\" aria-label=\"카테고리 필터\">\n <a\n className=\"rt-cms-filter-chip\"\n href={categoryHref(null)}\n data-active={isAllCategoriesActive ? \"\" : undefined}\n aria-current={isAllCategoriesActive ? \"true\" : undefined}\n >\n {allCategoriesLabel}\n </a>\n {categories.map((category) => (\n <a\n key={category.slug}\n className=\"rt-cms-filter-chip\"\n href={categoryHref(category.slug)}\n data-active={activeCategory === category.slug ? \"\" : undefined}\n aria-current={\n activeCategory === category.slug ? \"true\" : undefined\n }\n >\n {category.name}\n </a>\n ))}\n </nav>\n ) : null}\n {visiblePosts.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-list\">\n {visiblePosts.map((post) => (\n <li key={post.id} className=\"rt-cms-list-item\">\n <RootTalePostCard\n post={post}\n href={postHref}\n display={resolvePostDisplay(post, blogSettings)}\n />\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","import {\n DEFAULT_BLOG_SETTINGS,\n fetchBlogSettings,\n type RootTaleBlogSettings,\n} from \"@roottale/cms-client/server\";\n\ntype FetchBlogSettingsInput = {\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\nexport async function fetchBlogSettingsFailSoft(\n input: FetchBlogSettingsInput,\n): Promise<RootTaleBlogSettings> {\n return fetchBlogSettings(input).catch(() => DEFAULT_BLOG_SETTINGS);\n}\n","import {\n fetchPosts,\n type CmsPostContentPage,\n type FetchPostsOptions,\n} from \"@roottale/cms-client/server\";\n\n/**\n * 공개 목록은 CMS가 잠시 응답하지 않아도 페이지 전체를 500으로 만들지 않는다.\n * ISR 재검증 중이라면 Next가 기존 결과를 계속 제공하고, 최초 빌드라면 호출부가\n * 지정한 빈 상태를 렌더한다. 설정·테마·피드 라우트와 같은 fail-soft 계약이다.\n */\nexport async function fetchPostsFailSoft(\n options: FetchPostsOptions,\n): Promise<CmsPostContentPage> {\n try {\n return await fetchPosts(options);\n } catch {\n return { items: [], hasMore: false, nextCursor: null };\n }\n}\n","import type { CmsPostContent } from \"@roottale/cms-client/server\";\nimport {\n resolvePostCollection,\n resolvePostPath,\n type RouteCollection,\n} from \"@roottale/cms-core\";\n\nimport { storedPostPath } from \"../post-public-url.js\";\n\n/**\n * 글 카드·연관 글·빵부스러기가 쓰는 글 링크 — ADR-0105 Amendment 1.\n *\n * 플랫폼이 저장한 정규 공개 경로(`post.path`)가 있으면 그것을 그대로 쓴다. 없으면\n * (구 서버·물질화 전) 지금까지처럼 컬렉션 규칙 또는 `/blog/{slug}` 로 계산한다.\n */\nexport function defaultPostHref(post: CmsPostContent): string {\n return storedPostPath(post) ?? `/blog/${post.slug}`;\n}\n\nexport function defaultAuthorHref(slug: string): string {\n return `/blog/author/${slug}`;\n}\n\nexport function makeCollectionPostHref(\n collections: readonly RouteCollection[],\n): (post: CmsPostContent) => string {\n return (post) => {\n const stored = storedPostPath(post);\n if (stored) return stored;\n\n const path = resolvePostPath(post, collections);\n if (path) return path;\n\n const collection = resolvePostCollection(post, collections);\n if (collection?.detail === false && collection.basePath) {\n return collection.basePath;\n }\n return `/blog/${post.slug}`;\n };\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport {\n fetchTheme,\n type RootTaleTheme,\n} from \"@roottale/cms-client/server\";\n\nimport { themeToCssVars } from \"../theme-css-vars.js\";\n\nexport type RootTaleThemeInput = RootTaleTheme | null | undefined;\n\ntype ResolveThemeStyleInput = {\n readonly themeProp: RootTaleThemeInput;\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\nexport async function resolveThemeStyleFailSoft(\n input: ResolveThemeStyleInput,\n): Promise<CSSProperties | null> {\n const { themeProp, apiKey, baseUrl } = input;\n if (themeProp === null) return null;\n if (themeProp !== undefined) return themeToCssVars(themeProp);\n\n const fetched = await fetchTheme({ apiKey, baseUrl }).catch(() => null);\n return themeToCssVars(fetched);\n}\n\nexport function RootTaleThemeProvider(props: {\n readonly theme: RootTaleTheme | null | undefined;\n readonly children: ReactNode;\n}): ReactElement {\n const style = themeToCssVars(props.theme ?? null);\n if (!style) return <>{props.children}</>;\n\n return (\n <div data-roottale-cms=\"theme-root\" data-rt-theme=\"\" style={style}>\n {props.children}\n </div>\n );\n}\n","import type { ReactElement } from \"react\";\nimport type { RootTaleTheme } from \"@roottale/cms-client/server\";\nimport type { RouteCollection } from \"@roottale/cms-core\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport {\n collectCategoriesWithCounts,\n collectCollectionCategories,\n defaultCategoryHref,\n} from \"../blog-filter.js\";\nimport { fetchPostsFailSoft } from \"./fetch-posts-fail-soft.js\";\nimport { resolveThemeStyleFailSoft } from \"./theme.js\";\n\nexport type RootTaleBlogCategoriesProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n categoryHref?: (slug: string) => string;\n emptyMessage?: string;\n collection?: string;\n collections?: readonly RouteCollection[];\n theme?: RootTaleTheme | null | undefined;\n locale?: string;\n};\n\nexport async function RootTaleBlogCategories(\n props: RootTaleBlogCategoriesProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n baseUrl,\n limit,\n collection,\n collections,\n categoryHref = defaultCategoryHref,\n locale,\n emptyMessage = t(locale, \"blogCategoriesEmpty\"),\n theme: themeProp,\n } = props;\n const [page, themeStyle] = await Promise.all([\n fetchPostsFailSoft({\n apiKey,\n baseUrl,\n limit,\n type: \"post\",\n ...(locale ? { locale } : {}),\n }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n ]);\n const categories =\n collection && collections\n ? collectCollectionCategories(page.items, collections, collection)\n : collectCategoriesWithCounts(page.items);\n\n return (\n <nav\n data-roottale-cms=\"categories\"\n className=\"rt-cms-categories\"\n aria-label=\"블로그 카테고리\"\n style={themeStyle ?? undefined}\n >\n {categories.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-category-list\">\n {categories.map((category) => (\n <li key={category.slug} className=\"rt-cms-category-list-item\">\n <a\n className=\"rt-cms-category-link\"\n href={categoryHref(category.slug)}\n >\n <span className=\"rt-cms-category-name\">{category.name}</span>\n <span className=\"rt-cms-category-count\">{category.count}</span>\n </a>\n </li>\n ))}\n </ul>\n )}\n </nav>\n );\n}\n","import type { CSSProperties, ReactElement } from \"react\";\nimport {\n POST_FOOTER_PATTERN_SLOT,\n fetchPost,\n fetchPostPreview,\n fetchPosts,\n isPreviewExpiredError,\n resolvePostDisplay,\n selectRelatedPosts,\n type CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport { defaultCategoryHref } from \"../blog-filter.js\";\nimport { attachHeadingIds, extractToc } from \"../toc.js\";\nimport { fetchBlogSettingsFailSoft } from \"./blog-settings.js\";\nimport { renderPostPattern, resolvePostPatternFailSoft } from \"./site-patterns.js\";\nimport { renderArchiveRelated, renderFieldFacts } from \"./blog-post-archive.js\";\nimport { BlogPostBody } from \"./blog-post-body.js\";\nimport { renderBlogPostBreadcrumb } from \"./blog-post-breadcrumb.js\";\nimport {\n bodyHasInternalContentLinkTokens,\n fetchInternalContentPathIndex,\n} from \"./internal-links.js\";\nimport {\n renderFaqJsonLd,\n renderPostCta,\n relatedCardsFromPosts,\n renderRelatedPosts,\n type RelatedPostCard,\n} from \"./blog-post-footer.js\";\nimport type { RootTaleBlogPostProps } from \"./blog-post-types.js\";\nimport {\n defaultAuthorHref,\n defaultPostHref,\n makeCollectionPostHref,\n} from \"./post-links.js\";\nimport type { InternalLinksSource } from \"./render-tiptap.js\";\nimport { resolveThemeStyleFailSoft } from \"./theme.js\";\n\ntype FetchRelatedPostsInput = {\n readonly post: CmsPostContent;\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly count: number;\n readonly locale?: string;\n};\n\n/**\n * 관련 글 — 편집자가 어드민 \"관련 콘텐츠\"에서 고른 글(`post.relatedPosts`, 발행 글만·고른 순서)이\n * 있으면 그것을 쓰고, 없을 때만 같은 카테고리 최신 글을 자동으로 고른다. 편집자 선택은\n * `count` 와 무관하게 고른 만큼 보여준다(편집자가 이미 개수를 정했다).\n */\nfunction editorPickedRelatedCards(post: CmsPostContent): RelatedPostCard[] {\n return (post.relatedPosts ?? []).map((item) => ({\n id: item.id,\n title: item.title,\n href: item.path ?? `/blog/${item.slug}`,\n featuredImageUrl: item.featuredImageUrl,\n }));\n}\n\nasync function fetchRelatedPostsFailSoft(\n input: FetchRelatedPostsInput,\n): Promise<readonly CmsPostContent[]> {\n if (input.count <= 0) return [];\n\n const candidates = await fetchPosts({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n type: \"post\",\n limit: 30,\n ...(input.locale ? { locale: input.locale } : {}),\n }).catch(() => null);\n if (!candidates) return [];\n return selectRelatedPosts(input.post, candidates.items, input.count);\n}\n\nfunction authorInitials(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return \"\";\n const parts = trimmed.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) {\n return `${parts[0]?.[0] ?? \"\"}${parts[1]?.[0] ?? \"\"}`.toUpperCase();\n }\n return [...trimmed].slice(0, 2).join(\"\").toUpperCase();\n}\n\nfunction formatPostDate(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(date);\n}\n\nfunction distinctModifiedDate(\n publishedAt: string,\n updatedAt: string,\n): string | null {\n const published = formatPostDate(publishedAt);\n const updated = formatPostDate(updatedAt);\n if (!updated || updated === published) return null;\n return updated;\n}\n\nfunction readEditorialReview(metaJson: unknown): {\n reviewerName: string;\n reviewedAt: string;\n} | null {\n if (!metaJson || typeof metaJson !== \"object\") return null;\n const raw = (metaJson as Record<string, unknown>).editorialReview;\n if (!raw || typeof raw !== \"object\") return null;\n const value = raw as Record<string, unknown>;\n if (\n typeof value.reviewerName !== \"string\" ||\n typeof value.reviewedAt !== \"string\" ||\n !/^\\d{4}-\\d{2}-\\d{2}$/.test(value.reviewedAt)\n ) {\n return null;\n }\n return { reviewerName: value.reviewerName, reviewedAt: value.reviewedAt };\n}\n\ntype PostLookup =\n | { kind: \"post\"; post: CmsPostContent }\n | { kind: \"missing\" }\n | { kind: \"preview-expired\" };\n\n/**\n * 발행 글(slugOrId) 또는 미리보기(previewToken) 조회. 미리보기는 캐시 없이\n * 스냅샷을 받아 발행 글과 같은 모양(CmsPostContent)으로 돌아오므로 이후 렌더\n * 경로는 하나다 — 그래서 \"미리보기 = 결과물\" 이 구조적으로 보장된다.\n */\nasync function lookupPost(input: {\n apiKey: string;\n baseUrl?: string;\n slugOrId?: string;\n previewToken?: string;\n locale?: string;\n}): Promise<PostLookup> {\n if (input.previewToken) {\n try {\n const preview = await fetchPostPreview({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n token: input.previewToken,\n });\n return preview ? { kind: \"post\", post: preview } : { kind: \"missing\" };\n } catch (error) {\n if (isPreviewExpiredError(error)) return { kind: \"preview-expired\" };\n throw error;\n }\n }\n if (!input.slugOrId) return { kind: \"missing\" };\n const post = await fetchPost({\n apiKey: input.apiKey,\n slugOrId: input.slugOrId,\n baseUrl: input.baseUrl,\n ...(input.locale ? { locale: input.locale } : {}),\n });\n return post ? { kind: \"post\", post } : { kind: \"missing\" };\n}\n\nasync function resolveInternalLinksSource(\n source: RootTaleBlogPostProps[\"internalLinks\"],\n input: {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly locale?: string;\n readonly hasTokens: boolean;\n },\n): Promise<InternalLinksSource> {\n if (source === false) return null;\n if (source !== \"auto\" && source !== undefined) return source;\n if (!input.hasTokens) return null;\n return fetchInternalContentPathIndex({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n locale: input.locale,\n });\n}\n\nexport async function RootTaleBlogPost(\n props: RootTaleBlogPostProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n previewToken,\n previewExpiredElement,\n baseUrl,\n notFoundElement,\n showTitle = true,\n showTableOfContents,\n tableOfContentsTitle,\n relatedPostsCount = 0,\n locale,\n relatedPostsTitle = t(locale, \"relatedPostsTitle\"),\n collections,\n showFieldFacts = false,\n theme: themeProp,\n authorHref = defaultAuthorHref,\n breadcrumb,\n internalLinks = \"auto\",\n } = props;\n const [lookup, themeStyle, blogSettings] = await Promise.all([\n lookupPost({ apiKey, baseUrl, slugOrId, previewToken, locale }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n fetchBlogSettingsFailSoft({ apiKey, baseUrl }),\n ]);\n\n if (lookup.kind === \"preview-expired\") {\n return (\n previewExpiredElement ?? (\n <div data-roottale-cms=\"post-preview-expired\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"previewExpired\")}</p>\n </div>\n )\n );\n }\n if (lookup.kind === \"missing\") {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"post-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"postNotFound\")}</p>\n </div>\n )\n );\n }\n const post = lookup.post;\n\n const display = resolvePostDisplay(post, blogSettings, {\n showTableOfContents,\n tableOfContentsTitle,\n });\n const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];\n const renderDoc =\n toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;\n const editorPicked = editorPickedRelatedCards(post);\n const [autoRelated, internalLinkIndex, footerPattern] = await Promise.all([\n editorPicked.length > 0\n ? Promise.resolve([] as readonly CmsPostContent[])\n : fetchRelatedPostsFailSoft({\n post,\n apiKey,\n baseUrl,\n count: relatedPostsCount,\n locale,\n }),\n resolveInternalLinksSource(internalLinks, {\n apiKey,\n baseUrl,\n locale,\n hasTokens: bodyHasInternalContentLinkTokens(post.bodyJson, post.bodyHtml),\n }),\n resolvePostPatternFailSoft(post, { apiKey, baseUrl }, POST_FOOTER_PATTERN_SLOT),\n ]);\n const relatedHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n\n const authorName = post.authorName;\n const authorSlug = post.authorSlug;\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const publishedDate = display.showDate ? formatPostDate(post.publishedAt) : \"\";\n const modifiedDate = display.showDate\n ? distinctModifiedDate(post.publishedAt, post.updatedAt)\n : null;\n const showTopAuthor = display.showAuthor && Boolean(authorName);\n const categoryHref = breadcrumb?.categoryHref ?? defaultCategoryHref;\n const categories =\n breadcrumb?.showCategory === false\n ? []\n : post.terms.filter((term) => term.taxonomy === \"category\");\n const authorBio = post.authorBio;\n const authorImageUrl = post.authorImageUrl;\n const authorImageAlt = `${authorName ?? \"\"} 프로필 사진`.trim();\n const authorImageStyle: CSSProperties = {\n objectPosition: `${post.authorImagePositionX ?? display.authorProfileImagePositionX}% ${post.authorImagePositionY ?? display.authorProfileImagePositionY}%`,\n };\n const showAuthorCard = display.showAuthorCard && Boolean(authorName);\n const editorialReview = readEditorialReview(post.metaJson);\n const reviewedDate = editorialReview\n ? formatPostDate(editorialReview.reviewedAt)\n : \"\";\n\n return (\n <article\n data-roottale-cms=\"post\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n {renderBlogPostBreadcrumb({\n post,\n collections,\n postHref: relatedHref,\n options: breadcrumb,\n })}\n <header className=\"rt-cms-post-header\">\n {categories.length > 0 ? (\n <nav className=\"rt-cms-post-categories\" aria-label=\"글 카테고리\">\n {categories.map((category) => (\n <a\n key={category.id}\n className=\"rt-cms-post-category\"\n href={categoryHref(category.slug)}\n >\n {category.name}\n </a>\n ))}\n </nav>\n ) : null}\n {showTitle ? <h1 className=\"rt-cms-title\">{post.title}</h1> : null}\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n {publishedDate ||\n modifiedDate ||\n showTopAuthor ||\n views !== null ||\n reviewedDate ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <time\n dateTime={post.publishedAt ?? undefined}\n className=\"rt-cms-meta-item\"\n data-rt-cms-meta=\"date\"\n >\n 발행 {publishedDate}\n </time>\n ) : null}\n {modifiedDate ? (\n <time\n dateTime={post.updatedAt}\n className=\"rt-cms-meta-item\"\n data-rt-cms-meta=\"modified\"\n >\n 수정 {modifiedDate}\n </time>\n ) : null}\n {showTopAuthor ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자{\" \"}\n {authorSlug ? (\n <a className=\"rt-cms-author-link\" href={authorHref(authorSlug)}>\n {authorName}\n </a>\n ) : (\n authorName\n )}\n </span>\n ) : null}\n {editorialReview && reviewedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"reviewed\">\n 내용 검토 {reviewedDate} · {editorialReview.reviewerName}\n </span>\n ) : null}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n </header>\n {renderArchiveRelated(post.archiveRelated, collections)}\n {showFieldFacts ? renderFieldFacts(post) : null}\n <BlogPostBody\n doc={renderDoc}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n toc={toc}\n tocTitle={display.tocTitle}\n tocPosition={display.tocPosition}\n bodyHtml={post.bodyHtml}\n internalLinks={internalLinkIndex}\n />\n {renderPostPattern(footerPattern, POST_FOOTER_PATTERN_SLOT, { apiKey, baseUrl })}\n {footerPattern ? null : renderPostCta(blogSettings.postCta)}\n {showAuthorCard && authorName ? (\n <section className=\"rt-cms-author-card\" aria-label=\"작성자\">\n {authorImageUrl ? (\n <img\n className=\"rt-cms-author-card__image\"\n src={authorImageUrl}\n alt={authorImageAlt}\n loading=\"lazy\"\n style={authorImageStyle}\n />\n ) : (\n <div className=\"rt-cms-author-card__avatar\" aria-hidden=\"true\">\n {authorInitials(authorName)}\n </div>\n )}\n <div className=\"rt-cms-author-card__body\">\n <p className=\"rt-cms-author-card__eyebrow\">\n {display.authorCardEyebrow}\n </p>\n <p className=\"rt-cms-author-card__name\">\n {authorSlug ? (\n <a className=\"rt-cms-author-link\" href={authorHref(authorSlug)}>\n {authorName}\n </a>\n ) : (\n authorName\n )}\n </p>\n {authorBio ? (\n <p className=\"rt-cms-author-card__description\">{authorBio}</p>\n ) : null}\n </div>\n </section>\n ) : null}\n {renderRelatedPosts(\n editorPicked.length > 0 ? editorPicked : relatedCardsFromPosts(autoRelated, relatedHref),\n relatedPostsTitle,\n )}\n {renderFaqJsonLd(post.bodyJson)}\n </article>\n );\n}\n","import type { ReactElement } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport {\n POST_FOOTER_PATTERN_SLOT,\n SITE_PATTERNS_CACHE_TAG,\n fetchSitePatterns,\n selectSitePatternForSlot,\n sitePatternPresentationAttributes,\n type CmsPostContent,\n type CmsSitePattern,\n} from \"@roottale/cms-client/server\";\n\nimport { RenderTiptap } from \"./render-tiptap.js\";\n\ntype FetchInput = {\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\n/**\n * 글의 자리에 놓을 공통 블록을 고른다. 글에 놓을 블록이 없으면(`patternSlots` 가 비었거나\n * 구 서버) 목록을 아예 요청하지 않고, 요청 실패는 null 로 낮춘다 — 블록이 없다고 글이\n * 안 열리면 안 된다. 목록은 `rt-site-patterns` tag 로 캐시돼 `theme.updated` 에 지워진다.\n */\nexport async function resolvePostPatternFailSoft(\n post: Pick<CmsPostContent, \"patternSlots\">,\n input: FetchInput,\n slot: string = POST_FOOTER_PATTERN_SLOT,\n): Promise<CmsSitePattern | null> {\n const key = post.patternSlots?.[slot] ?? null;\n if (!key) return null;\n try {\n const patterns = await fetchSitePatterns({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n tags: [SITE_PATTERNS_CACHE_TAG],\n });\n return selectSitePatternForSlot(post.patternSlots, patterns, slot);\n } catch {\n return null;\n }\n}\n\n/** 공통 블록 1개를 글 본문과 같은 Tiptap 렌더러로 그린다. */\nexport function renderPostPattern(\n pattern: CmsSitePattern | null,\n slot: string,\n input: FetchInput,\n): ReactElement | null {\n if (!pattern) return null;\n // 표시 형태(카드/버튼/색)는 어드민 값이 정한다 — CSS 는 data 속성·변수만 읽는다.\n const presentation = sitePatternPresentationAttributes(pattern.presentation);\n return (\n <aside\n data-roottale-cms=\"post-pattern\"\n data-pattern-slot={slot}\n data-pattern-key={pattern.key}\n {...presentation.dataset}\n style={presentation.style as CSSProperties}\n className=\"rt-cms-post-pattern\"\n aria-label={pattern.name}\n >\n <RenderTiptap doc={pattern.bodyJson} apiKey={input.apiKey} baseUrl={input.baseUrl} />\n </aside>\n );\n}\n\nexport interface RootTalePostPatternProps {\n readonly apiKey: string;\n readonly baseUrl?: string;\n /** `fetchPost`/`fetchPosts` 로 받은 글(또는 `patternSlots` 만 있는 객체). */\n readonly post: Pick<CmsPostContent, \"patternSlots\">;\n /** 자리 key. 기본 글 하단(`post_footer`). */\n readonly slot?: string;\n}\n\n/**\n * 자체 글 화면을 가진 사이트가 글 하단 공통 블록만 가져다 쓰는 서버 컴포넌트.\n * `RootTaleBlogPost` 는 이미 본문 아래에 같은 블록을 그리므로 둘을 함께 쓰지 않는다.\n */\nexport async function RootTalePostPattern(\n props: RootTalePatternProps,\n): Promise<ReactElement | null> {\n const slot = props.slot ?? POST_FOOTER_PATTERN_SLOT;\n const pattern = await resolvePostPatternFailSoft(props.post, props, slot);\n return renderPostPattern(pattern, slot, props);\n}\n\ntype RootTalePatternProps = RootTalePostPatternProps;\n","import type { ReactElement } from \"react\";\n\nimport {\n internalContentLinkResolverFrom,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n type CmsBoundContext,\n type SectionRenderContext,\n} from \"../section-render-context.js\";\nimport { RootTaleTableOfContents } from \"../TableOfContents.js\";\nimport { attachHeadingIds, extractToc } from \"../toc.js\";\nimport { RootTaleBlogList } from \"./blog-list.js\";\nimport {\n renderTiptapNode,\n type RenderNodeContext,\n} from \"./tiptap-node-renderer.js\";\nimport { isRecord, parseTiptapNode } from \"./tiptap-types.js\";\n\nexport type InternalLinksSource =\n | InternalContentLinkResolver\n | ReadonlyMap<string, string>\n | null;\n\ntype RenderTiptapProps = {\n readonly doc: Record<string, unknown>;\n readonly fields?: Record<string, unknown> | null;\n readonly apiKey?: string;\n readonly baseUrl?: string;\n /**\n * 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석 — 키 → 공개 경로.\n * `fetchInternalContentPathIndex` 결과(Map)나 함수. 없으면 토큰을 텍스트 그대로 둔다.\n */\n readonly internalLinks?: InternalLinksSource;\n};\n\nfunction isDevelopmentRuntime(): boolean {\n const processValue = Reflect.get(globalThis, \"process\");\n if (!isRecord(processValue)) return true;\n const env = processValue.env;\n if (!isRecord(env)) return true;\n return env.NODE_ENV !== \"production\";\n}\n\nfunction createSectionContextWithBlogListNetworkFetch(\n apiKey: string | undefined,\n baseUrl: string | undefined,\n): SectionRenderContext {\n const cms: CmsBoundContext | null = apiKey ? { apiKey, baseUrl } : null;\n return {\n cms,\n isDev: isDevelopmentRuntime,\n renderBlogList: (props) => <RootTaleBlogList {...props} />,\n };\n}\n\nfunction contentNodes(doc: Record<string, unknown>): readonly unknown[] {\n return Array.isArray(doc.content) ? doc.content : [];\n}\n\nexport function RenderTiptap({\n doc,\n fields,\n apiKey,\n baseUrl,\n internalLinks,\n}: RenderTiptapProps): ReactElement {\n const content = contentNodes(doc);\n const hasTableOfContents = content.some(\n (value) => parseTiptapNode(value)?.type === \"tableOfContents\",\n );\n const entries = hasTableOfContents ? extractToc(doc) : [];\n const renderDoc =\n hasTableOfContents && entries.length > 0\n ? attachHeadingIds(doc, entries)\n : doc;\n const context: RenderNodeContext = {\n fields: fields ?? null,\n section: createSectionContextWithBlogListNetworkFetch(apiKey, baseUrl),\n internalLinks: internalLinks\n ? internalContentLinkResolverFrom(internalLinks)\n : null,\n };\n\n return (\n <>\n {contentNodes(renderDoc).map((value, index) => {\n const node = parseTiptapNode(value);\n if (node?.type !== \"tableOfContents\") {\n return renderTiptapNode(value, index, context);\n }\n const title = node.attrs?.title;\n return (\n <RootTaleTableOfContents\n key={index}\n entries={entries}\n title={typeof title === \"string\" ? title : \"목차\"}\n />\n );\n })}\n </>\n );\n}\n","import { Fragment, type CSSProperties, type ReactElement } from \"react\";\nimport {\n cmsLineHeight,\n cmsParagraphSpacing,\n cmsImageStyle,\n cmsImageWidth,\n bindingSrcValue,\n bindingTextValue,\n boundFieldValue,\n ensureImportedHtmlDocumentRoots,\n normalizeImageAlt,\n renderInternalContentLinksInHtml,\n scopeImportedCss,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n getSectionNodeRenderer,\n type SectionRenderContext,\n} from \"../section-render-context.js\";\nimport { renderEmbedNode } from \"./tiptap-embed-renderer.js\";\nimport { renderTextNode } from \"./tiptap-inline-renderer.js\";\nimport { parseTiptapNode, type TiptapNode } from \"./tiptap-types.js\";\n\ntype FieldsRecord = Record<string, unknown> | null;\n\nexport type RenderNodeContext = {\n readonly fields: FieldsRecord;\n readonly section: SectionRenderContext;\n /** 본문 예약 내부 링크 키 → 공개 경로. 없으면 토큰을 텍스트 그대로 둔다. */\n readonly internalLinks?: InternalContentLinkResolver | null;\n};\n\nfunction withInternalLinksInHtml(\n html: string,\n internalLinks: InternalContentLinkResolver | null,\n): string {\n return internalLinks\n ? renderInternalContentLinksInHtml(html, internalLinks).html\n : html;\n}\n\nfunction boundContentText(\n node: TiptapNode,\n fields: FieldsRecord,\n): string | undefined {\n return bindingTextValue(\n boundFieldValue(node.attrs?.bindings, \"content\", fields),\n );\n}\n\nfunction alignStyle(node: TiptapNode): CSSProperties | undefined {\n const align = node.attrs?.textAlign;\n const lineHeight = cmsLineHeight(node.attrs?.lineHeight);\n const marginBottom = cmsParagraphSpacing(node.attrs?.paragraphSpacing);\n const style: CSSProperties = {\n ...(align === \"left\" || align === \"center\" || align === \"right\" || align === \"justify\" ? { textAlign: align } : {}),\n ...(lineHeight !== null ? { lineHeight } : {}),\n ...(marginBottom !== null ? { marginBottom } : {}),\n };\n return Object.keys(style).length ? style : undefined;\n}\n\nfunction tableCellSpans(node: TiptapNode): {\n readonly colSpan: number | undefined;\n readonly rowSpan: number | undefined;\n} {\n const colspan = Number(node.attrs?.colspan);\n const rowspan = Number(node.attrs?.rowspan);\n return {\n colSpan: Number.isFinite(colspan) && colspan > 1 ? colspan : undefined,\n rowSpan: Number.isFinite(rowspan) && rowspan > 1 ? rowspan : undefined,\n };\n}\n\nexport function renderTiptapNode(\n value: unknown,\n key: number,\n context: RenderNodeContext,\n): ReactElement | string | null {\n const node = parseTiptapNode(value);\n if (!node) return null;\n\n const children = node.content?.map((child, index) =>\n renderTiptapNode(child, index, context),\n );\n\n switch (node.type) {\n case \"paragraph\": {\n const bound = boundContentText(node, context.fields);\n return (\n <p key={key} style={alignStyle(node)}>\n {bound ?? children}\n </p>\n );\n }\n case \"heading\": {\n const rawLevel = Number(node.attrs?.level ?? 2);\n const finiteLevel = Number.isFinite(rawLevel) ? rawLevel : 2;\n const level = Math.min(Math.max(finiteLevel, 2), 4);\n const rawId = node.attrs?.id;\n const id =\n typeof rawId === \"string\" && rawId.length > 0 ? rawId : undefined;\n const style = alignStyle(node);\n const inner = boundContentText(node, context.fields) ?? children;\n if (level === 2) {\n return (\n <h2 key={key} id={id} style={style}>\n {inner}\n </h2>\n );\n }\n if (level === 3) {\n return (\n <h3 key={key} id={id} style={style}>\n {inner}\n </h3>\n );\n }\n return (\n <h4 key={key} id={id} style={style}>\n {inner}\n </h4>\n );\n }\n case \"bulletList\":\n return <ul key={key}>{children}</ul>;\n case \"orderedList\":\n return <ol key={key}>{children}</ol>;\n case \"listItem\":\n return <li key={key}>{children}</li>;\n case \"blockquote\":\n return <blockquote key={key}>{children}</blockquote>;\n case \"codeBlock\":\n return (\n <pre key={key}>\n <code>\n {node.content?.map((child, index) =>\n renderTiptapNode(child, index, { ...context, internalLinks: null }),\n )}\n </code>\n </pre>\n );\n case \"horizontalRule\":\n return <hr key={key} />;\n case \"hardBreak\":\n return <br key={key} />;\n case \"image\": {\n const boundSrc = bindingSrcValue(\n boundFieldValue(node.attrs?.bindings, \"src\", context.fields),\n );\n const src = boundSrc ?? node.attrs?.src;\n if (typeof src !== \"string\" || src.length === 0) return null;\n\n const alt = normalizeImageAlt(node.attrs?.alt);\n const title =\n typeof node.attrs?.title === \"string\" ? node.attrs.title : undefined;\n const width =\n typeof node.attrs?.width === \"number\" && node.attrs.width > 0\n ? node.attrs.width\n : undefined;\n const height =\n typeof node.attrs?.height === \"number\" && node.attrs.height > 0\n ? node.attrs.height\n : undefined;\n const caption = typeof node.attrs?.caption === \"string\" ? node.attrs.caption : \"\";\n const layout = cmsImageStyle(node.attrs ?? {});\n const image = (\n <img\n key={key}\n src={src}\n alt={alt}\n title={title}\n width={width}\n height={height}\n style={caption ? (cmsImageWidth(node.attrs?.displayWidth) ? { width: \"100%\", maxWidth: \"100%\", height: \"auto\" } : undefined) : layout}\n loading=\"lazy\"\n />\n );\n return caption ? <figure key={key} data-cms-image=\"\" style={layout}>{image}<figcaption>{caption}</figcaption></figure> : image;\n }\n case \"importedHtml\": {\n const rawHtml = node.attrs?.html;\n if (typeof rawHtml !== \"string\" || rawHtml.length === 0) return null;\n const scope = typeof node.attrs?.scope === \"string\" ? node.attrs.scope : \"\";\n const assetUrls = Array.isArray(node.attrs?.assetUrls)\n ? node.attrs.assetUrls.filter(\n (value): value is string =>\n typeof value === \"string\" && /^https:\\/\\//i.test(value),\n )\n : [];\n const scopedCss = scopeImportedCss(\n typeof node.attrs?.css === \"string\" ? node.attrs.css : \"\",\n scope,\n assetUrls,\n );\n return (\n <Fragment key={key}>\n {scopedCss ? (\n <style data-rt-imported-style={scope}>{scopedCss}</style>\n ) : null}\n <div\n className=\"rt-imported-html\"\n data-rt-import-scope={scopedCss ? scope : undefined}\n style={{ position: \"relative\", isolation: \"isolate\" }}\n dangerouslySetInnerHTML={{\n __html: withInternalLinksInHtml(\n ensureImportedHtmlDocumentRoots(rawHtml, assetUrls),\n context.internalLinks ?? null,\n ),\n }}\n />\n </Fragment>\n );\n }\n case \"columns\":\n return (\n <div key={key} className=\"rt-columns\">\n {children}\n </div>\n );\n case \"column\":\n return (\n <div key={key} className=\"rt-column\">\n {children}\n </div>\n );\n case \"table\":\n return (\n <div key={key} className=\"rt-table-wrap\">\n <table className=\"rt-table\">\n <tbody>{children}</tbody>\n </table>\n </div>\n );\n case \"tableRow\":\n return <tr key={key}>{children}</tr>;\n case \"tableHeader\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return (\n <th key={key} colSpan={colSpan} rowSpan={rowSpan}>\n {children}\n </th>\n );\n }\n case \"tableCell\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return (\n <td key={key} colSpan={colSpan} rowSpan={rowSpan}>\n {children}\n </td>\n );\n }\n case \"embed\":\n return renderEmbedNode(node, key);\n case \"faq\":\n return (\n <div key={key} data-roottale-cms=\"faq\" className=\"rt-cms-faq\">\n {children}\n </div>\n );\n case \"faqItem\":\n return (\n <details key={key} className=\"rt-cms-faq-item\">\n {children}\n </details>\n );\n case \"faqQuestion\":\n return (\n <summary key={key} className=\"rt-cms-faq-q\">\n {children}\n </summary>\n );\n case \"faqAnswer\":\n return (\n <div key={key} className=\"rt-cms-faq-a\">\n {children}\n </div>\n );\n case \"references\": {\n const rawTitle = node.attrs?.title;\n const title =\n typeof rawTitle === \"string\" && rawTitle.trim()\n ? rawTitle.trim()\n : \"공식 출처\";\n const titleId = `rt-cms-references-${key}`;\n return (\n <section\n key={key}\n data-roottale-cms=\"references\"\n className=\"rt-cms-references\"\n aria-labelledby={titleId}\n >\n <h2 id={titleId} className=\"rt-cms-references__title\">\n {title}\n </h2>\n {children}\n </section>\n );\n }\n case \"tableOfContents\":\n return null;\n case \"section\": {\n // 섹션 템플릿은 templates 계층이 주입한다(ADR-0040 P2 단방향). 주입 전이면\n // 미지 섹션과 같은 fail-soft — dev 경고 후 skip.\n const renderSection = getSectionNodeRenderer();\n if (!renderSection) {\n if (context.section.isDev()) {\n console.warn(\n \"[cms-renderer-next] section renderer가 등록되지 않아 노드를 건너뜁니다. 공개 CMS runtime은 마케팅 section 구현을 포함하지 않습니다.\",\n );\n }\n return null;\n }\n return renderSection(node, key, context.section);\n }\n case \"text\":\n return renderTextNode(node, key, context.internalLinks ?? null);\n default:\n return children ? <span key={key}>{children}</span> : null;\n }\n}\n","import type { ReactElement } from \"react\";\nimport { isAllowedEmbedSrc } from \"@roottale/cms-core\";\n\nimport type { TiptapNode } from \"./tiptap-types.js\";\n\nfunction isSafeFallbackHref(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n const href = value.trim();\n if (href.length === 0) return false;\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return true;\n }\n return /^(https?:|mailto:|tel:)/i.test(href);\n}\n\nexport function renderEmbedNode(\n node: TiptapNode,\n key: number,\n): ReactElement | null {\n const src = node.attrs?.src;\n if (isAllowedEmbedSrc(src)) {\n const provider =\n typeof node.attrs?.provider === \"string\"\n ? node.attrs.provider\n : undefined;\n return (\n <div\n key={key}\n className=\"rt-embed\"\n data-provider={provider}\n style={{ aspectRatio: \"16 / 9\" }}\n >\n <iframe\n src={src}\n title=\"임베드된 영상\"\n loading=\"lazy\"\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n allow=\"accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share\"\n style={{ width: \"100%\", height: \"100%\", border: 0 }}\n />\n </div>\n );\n }\n\n const originalUrl = node.attrs?.originalUrl;\n if (!isSafeFallbackHref(originalUrl)) return null;\n return (\n <p key={key} className=\"rt-embed-fallback\">\n <a href={originalUrl} rel=\"noopener noreferrer\" target=\"_blank\">\n {originalUrl}\n </a>\n </p>\n );\n}\n","import type { ReactElement, ReactNode } from \"react\";\n\nimport {\n cmsFontSize,\n findInternalContentLinkTokens,\n INTERNAL_CONTENT_LINK_CLASS,\n INTERNAL_CONTENT_LINK_PENDING_ATTR,\n INTERNAL_CONTENT_LINK_PENDING_CLASS,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n parseTiptapMark,\n type TiptapMark,\n type TiptapNode,\n} from \"./tiptap-types.js\";\n\nfunction isSafeHref(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n const href = value.trim();\n if (href.length === 0) return false;\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return true;\n }\n return /^(https?:|mailto:|tel:)/i.test(href);\n}\n\nfunction applyMark(\n mark: TiptapMark,\n child: ReactElement | string,\n): ReactElement {\n switch (mark.type) {\n case \"bold\":\n return <strong>{child}</strong>;\n case \"italic\":\n return <em>{child}</em>;\n case \"underline\":\n return <u>{child}</u>;\n case \"strike\":\n return <s>{child}</s>;\n case \"code\":\n return <code>{child}</code>;\n case \"highlight\": {\n const color = mark.attrs?.color;\n const style =\n typeof color === \"string\" && color.length > 0\n ? { background: color }\n : undefined;\n return <mark style={style}>{child}</mark>;\n }\n case \"textStyle\": {\n const color = mark.attrs?.color;\n const fontSize = cmsFontSize(mark.attrs?.fontSize);\n const style = {\n ...(typeof color === \"string\" && color ? { color } : {}),\n ...(fontSize ? { fontSize } : {}),\n };\n return Object.keys(style).length ? <span style={style}>{child}</span> : <>{child}</>;\n }\n case \"link\": {\n const rawHref = mark.attrs?.href;\n const href = isSafeHref(rawHref) ? rawHref : \"#\";\n return (\n <a href={href} rel=\"noopener noreferrer\" target=\"_blank\">\n {child}\n </a>\n );\n }\n default:\n return <span>{child}</span>;\n }\n}\n\nfunction isSafeInternalPath(path: string | null): path is string {\n return typeof path === \"string\" && path.startsWith(\"/\") && !path.startsWith(\"//\");\n}\n\n/**\n * 본문 예약 내부 링크 토큰(`[[internal:키|문구]]`)을 링크 또는 문구로 바꾼다.\n * 토큰이 없으면 문자열 그대로. 대상이 없는 토큰은 문구만 남기되 data 속성으로\n * 키를 남겨 배포 QA 에서 찾을 수 있게 한다.\n */\nfunction renderInternalLinkText(\n text: string,\n resolve: InternalContentLinkResolver,\n): ReactNode {\n const tokens = findInternalContentLinkTokens(text);\n if (tokens.length === 0) return text;\n const parts: ReactNode[] = [];\n let cursor = 0;\n tokens.forEach((token, index) => {\n if (token.index > cursor) parts.push(text.slice(cursor, token.index));\n const path = resolve(token.key);\n parts.push(\n isSafeInternalPath(path) ? (\n <a key={index} href={path} className={INTERNAL_CONTENT_LINK_CLASS}>\n {token.label}\n </a>\n ) : (\n <span\n key={index}\n className={`${INTERNAL_CONTENT_LINK_CLASS} ${INTERNAL_CONTENT_LINK_PENDING_CLASS}`}\n {...{ [INTERNAL_CONTENT_LINK_PENDING_ATTR]: token.key }}\n >\n {token.label}\n </span>\n ),\n );\n cursor = token.index + token.raw.length;\n });\n if (cursor < text.length) parts.push(text.slice(cursor));\n return <>{parts}</>;\n}\n\nexport function renderTextNode(\n node: TiptapNode,\n key: number,\n internalLinks: InternalContentLinkResolver | null = null,\n): ReactElement | string {\n const text = node.text ?? \"\";\n const marks = (node.marks ?? []).flatMap((value) => {\n const mark = parseTiptapMark(value);\n return mark ? [mark] : [];\n });\n // 코드·기존 링크 안의 토큰은 렌더러 계약대로 손대지 않는다.\n const canLink =\n internalLinks !== null &&\n !marks.some((mark) => mark.type === \"code\" || mark.type === \"link\");\n const content: ReactNode =\n canLink && internalLinks ? renderInternalLinkText(text, internalLinks) : text;\n if (marks.length === 0) {\n return typeof content === \"string\" ? content : <span key={key}>{content}</span>;\n }\n\n let element: ReactElement | string =\n typeof content === \"string\" ? content : <>{content}</>;\n for (const mark of marks) {\n element = applyMark(mark, element);\n }\n return <span key={key}>{element}</span>;\n}\n","export type TiptapMark = {\n readonly type?: string;\n readonly attrs?: Record<string, unknown>;\n};\n\nexport type TiptapNode = {\n readonly type?: string;\n readonly text?: string;\n readonly attrs?: Record<string, unknown>;\n readonly content?: readonly unknown[];\n readonly marks?: readonly unknown[];\n};\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function parseTiptapNode(value: unknown): TiptapNode | null {\n if (!isRecord(value)) return null;\n return {\n type: typeof value.type === \"string\" ? value.type : undefined,\n text: typeof value.text === \"string\" ? value.text : undefined,\n attrs: isRecord(value.attrs) ? value.attrs : undefined,\n content: Array.isArray(value.content) ? value.content : undefined,\n marks: Array.isArray(value.marks) ? value.marks : undefined,\n };\n}\n\nexport function parseTiptapMark(value: unknown): TiptapMark | null {\n if (!isRecord(value)) return null;\n return {\n type: typeof value.type === \"string\" ? value.type : undefined,\n attrs: isRecord(value.attrs) ? value.attrs : undefined,\n };\n}\n","import type { ReactElement } from \"react\";\nimport type {\n CmsArchiveRelatedGroup,\n CmsFieldMetaEntry,\n CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport type { RouteCollection } from \"@roottale/cms-core\";\n\nconst FIELD_FACT_TYPES = new Set([\n \"text\",\n \"textarea\",\n \"email\",\n \"url\",\n \"select\",\n \"radio\",\n \"button_group\",\n \"number\",\n \"range\",\n]);\n\nfunction archiveGroupTitle(group: CmsArchiveRelatedGroup): string {\n if (group.collectionKey === \"conditions\") {\n return `이 증상과 관련된 질환 ${group.items.length}`;\n }\n if (group.collectionKey === \"treatments\") {\n return `관련 치료법 ${group.items.length}`;\n }\n if (group.collectionKey === \"symptoms\") {\n return `관련 증상 ${group.items.length}`;\n }\n return `${group.label} ${group.items.length}`;\n}\n\nfunction defaultArchiveBasePath(collectionKey: string): string {\n if (collectionKey === \"symptoms\") return \"/symptoms\";\n if (collectionKey === \"conditions\") return \"/conditions\";\n if (collectionKey === \"treatments\") return \"/treatments\";\n return \"/blog\";\n}\n\nfunction archivePostHref(\n collectionKey: string,\n slug: string,\n collections: readonly RouteCollection[] | undefined,\n): string {\n const collection = collections?.find((item) => item.key === collectionKey);\n const basePath =\n collection?.basePath || defaultArchiveBasePath(collectionKey);\n const trimmedBase = basePath.replace(/\\/+$/, \"\");\n if (collection?.detail === false) return trimmedBase;\n return `${trimmedBase}/${slug}`;\n}\n\nexport function renderArchiveRelated(\n groups: readonly CmsArchiveRelatedGroup[],\n collections: readonly RouteCollection[] | undefined,\n): ReactElement | null {\n const visibleGroups = groups.filter((group) => group.items.length > 0);\n if (visibleGroups.length === 0) return null;\n\n return (\n <aside className=\"rt-archive-related\" aria-label=\"연관 정보\">\n {visibleGroups.map((group) => (\n <section key={group.collectionKey} className=\"rt-archive-related__group\">\n <h2 className=\"rt-archive-related__title\">\n {archiveGroupTitle(group)}\n </h2>\n <ul className=\"rt-archive-related__list\">\n {group.items.map((item) => (\n <li key={item.id} className=\"rt-archive-related__item\">\n <a\n className=\"rt-archive-related__link\"\n href={archivePostHref(\n item.collectionKey,\n item.slug,\n collections,\n )}\n >\n <span className=\"rt-archive-related__item-title\">\n {item.title}\n </span>\n {item.excerpt ? (\n <span className=\"rt-archive-related__item-excerpt\">\n {item.excerpt}\n </span>\n ) : null}\n </a>\n </li>\n ))}\n </ul>\n </section>\n ))}\n </aside>\n );\n}\n\nfunction fieldFactValue(\n raw: unknown,\n meta: CmsFieldMetaEntry,\n): string | null {\n if (raw === null || raw === undefined) return null;\n if (typeof raw === \"number\") return raw.toLocaleString(\"ko-KR\");\n if (typeof raw !== \"string\" || raw.trim() === \"\") return null;\n if (!meta.choices) return raw;\n\n const choice = meta.choices.find((item) => item.value === raw);\n return choice ? choice.label : raw;\n}\n\nexport function renderFieldFacts(\n post: CmsPostContent,\n): ReactElement | null {\n if (!post.fields) return null;\n\n const rows = post.fieldsMeta\n .filter((meta) => FIELD_FACT_TYPES.has(meta.type))\n .flatMap((meta) => {\n const value = fieldFactValue(post.fields?.[meta.name], meta);\n return value === null ? [] : [{ meta, value }];\n });\n if (rows.length === 0) return null;\n\n return (\n <aside className=\"rt-archive-facts\" aria-label=\"상세 정보\">\n <dl className=\"rt-archive-facts__list\">\n {rows.map(({ meta, value }) => (\n <div key={meta.name} className=\"rt-archive-facts__row\">\n <dt className=\"rt-archive-facts__label\">{meta.label}</dt>\n <dd\n className={\n meta.type === \"textarea\"\n ? \"rt-archive-facts__value rt-archive-facts__value--multiline\"\n : \"rt-archive-facts__value\"\n }\n >\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </aside>\n );\n}\n","import type { ReactElement } from \"react\";\nimport {\n internalContentLinkResolverFrom,\n renderInternalContentLinksInHtml,\n} from \"@roottale/cms-core\";\n\nimport { RootTaleTableOfContents } from \"../TableOfContents.js\";\nimport type { TocEntry } from \"../toc.js\";\nimport { resolvePostBodySource } from \"./post-body-source.js\";\nimport { RenderTiptap, type InternalLinksSource } from \"./render-tiptap.js\";\n\ntype BlogPostBodyProps = {\n readonly doc: Record<string, unknown>;\n readonly fields: Record<string, unknown> | null;\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly toc: TocEntry[];\n readonly tocTitle: string;\n readonly tocPosition: string;\n /** 원문 HTML 정본. 있으면 sanitize 후 이것을 렌더하고, 없으면 doc 으로 폴백. */\n readonly bodyHtml?: string | null;\n /** 본문 예약 내부 링크 키 → 공개 경로. 없으면 토큰을 텍스트 그대로 둔다. */\n readonly internalLinks?: InternalLinksSource;\n};\n\nexport function BlogPostBody(props: BlogPostBodyProps): ReactElement {\n const {\n doc,\n fields,\n apiKey,\n baseUrl,\n toc,\n tocTitle,\n tocPosition,\n bodyHtml,\n internalLinks = null,\n } = props;\n const tableOfContents =\n toc.length > 0 ? (\n <RootTaleTableOfContents entries={toc} title={tocTitle} />\n ) : null;\n const source = resolvePostBodySource(bodyHtml);\n // 원문 HTML 정본도 토큰을 링크로 — 텍스트 구간만, <a>·<code>·<pre> 안은 그대로.\n const sourceHtml =\n source.kind === \"html\" && internalLinks\n ? renderInternalContentLinksInHtml(\n source.html,\n internalContentLinkResolverFrom(internalLinks),\n ).html\n : source.kind === \"html\"\n ? source.html\n : \"\";\n const body = (\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n {source.kind === \"html\" ? (\n // source.html 은 resolvePostBodySource 가 공용 sanitizeHtml 로 정제한 값이다\n // (이 컴포넌트에서 한 번 더 정제하지 않는 이유 — sink 가 한 곳이어야 눈으로\n // 추적 가능하고, 이중 정제는 안전을 더 만들지 않으면서 비용만 든다).\n <div dangerouslySetInnerHTML={{ __html: sourceHtml }} />\n ) : (\n <RenderTiptap\n doc={doc}\n fields={fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n internalLinks={internalLinks}\n />\n )}\n </div>\n );\n\n if (tocPosition === \"sidebar\" && tableOfContents) {\n return (\n <div className=\"rt-cms-layout rt-cms-layout--toc-side\">\n {body}\n <aside className=\"rt-cms-toc-side\">{tableOfContents}</aside>\n </div>\n );\n }\n return (\n <>\n {tableOfContents}\n {body}\n </>\n );\n}\n","/**\n * 본문 렌더 소스 결정 — HTML 정본 우선, 없으면 Block JSON 폴백.\n *\n * 배경: 외부에서 이관한 글은 **HTML 이 정본**이고 body_json 은 그 파생/대체본이다.\n * 파생본은 표·중첩 목록 같은 구조에서 원문과 미묘하게 달라질 수 있으므로, 원문이\n * 남아 있으면 그것을 보여주는 편이 정확하다. 반대로 에디터에서 편집된 글은\n * body_html 이 비워지므로(저장 경로가 무효화) 자동으로 JSON 경로로 돌아온다.\n *\n * 안전성: HTML 은 신뢰하지 않는다. 최종 sink 는 공용 `sanitizeHtml` 하나뿐이며\n * (cms-core), 렌더 직전 여기서 통과시킨다 — 저장 시점에 정제됐더라도 다시 한다\n * (저장 경로가 여럿이고 sanitizer 규칙은 갱신되므로, 방어는 렌더 시점 기준).\n *\n * sanitize 결과가 실질적으로 비면(태그만 남고 내용이 없음) HTML 을 쓰지 않고\n * JSON 으로 폴백한다 — 빈 화면보다 파생본이 낫다.\n */\n\nimport { sanitizeHtml } from \"@roottale/cms-core\";\n\nexport type PostBodySource =\n | { readonly kind: \"html\"; readonly html: string }\n | { readonly kind: \"json\" };\n\n/** 태그를 걷어낸 뒤 실제 보이는 내용이 있는지 (이미지 등 void 요소도 내용으로 침). */\nfunction hasVisibleContent(html: string): boolean {\n if (/<(?:img|hr|br|iframe|video|audio)\\b/i.test(html)) return true;\n return html.replace(/<[^>]*>/g, \"\").replace(/ /gi, \" \").trim().length > 0;\n}\n\n/**\n * 렌더에 쓸 본문 소스를 고른다.\n *\n * @param bodyHtml 원문 HTML 정본 (null/미포함 = 구 서버이거나 JSON 이 정본).\n * @returns `kind: \"html\"` 이면 `html` 은 **이미 sanitize 된** 값이라 그대로 주입 가능.\n */\nexport function resolvePostBodySource(\n bodyHtml: string | null | undefined,\n): PostBodySource {\n if (typeof bodyHtml !== \"string\") return { kind: \"json\" };\n const trimmed = bodyHtml.trim();\n if (trimmed.length === 0) return { kind: \"json\" };\n\n const sanitized = sanitizeHtml(trimmed);\n if (!hasVisibleContent(sanitized)) return { kind: \"json\" };\n\n return { kind: \"html\", html: sanitized };\n}\n","import type { ReactElement } from \"react\";\nimport {\n breadcrumbSchema,\n jsonLdJson,\n type CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport {\n resolvePostCollection,\n type RouteCollection,\n} from \"@roottale/cms-core\";\n\nimport { defaultCategoryHref } from \"../blog-filter.js\";\nimport { trimSlash } from \"../routes-shared.js\";\nimport type { BlogPostBreadcrumbOptions } from \"./blog-post-types.js\";\n\ntype RenderBlogPostBreadcrumbInput = {\n readonly post: CmsPostContent;\n readonly collections: readonly RouteCollection[] | undefined;\n readonly postHref: (post: CmsPostContent) => string;\n readonly options: BlogPostBreadcrumbOptions | undefined;\n};\n\nfunction absoluteUrl(siteUrl: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return `${trimSlash(siteUrl)}${normalizedPath}`;\n}\n\nexport function renderBlogPostBreadcrumb(\n input: RenderBlogPostBreadcrumbInput,\n): ReactElement | null {\n const { post, collections, postHref, options } = input;\n if (!options) return null;\n\n const {\n siteUrl,\n homeLabel = \"홈\",\n homeHref = \"/\",\n listLabel = \"블로그\",\n showCategory = true,\n categoryHref = defaultCategoryHref,\n } = options;\n const listHref =\n options.listHref ??\n (collections\n ? resolvePostCollection(post, collections)?.basePath || \"/blog\"\n : \"/blog\");\n const categoryTerm = showCategory\n ? post.terms.find((term) => term.taxonomy === \"category\")\n : undefined;\n const crumbs: { label: string; href: string }[] = [\n { label: homeLabel, href: homeHref },\n { label: listLabel, href: listHref },\n ];\n if (categoryTerm) {\n crumbs.push({\n label: categoryTerm.name,\n href: categoryHref(categoryTerm.slug),\n });\n }\n crumbs.push({ label: post.title, href: postHref(post) });\n\n const jsonLd = siteUrl\n ? breadcrumbSchema(\n crumbs.map((crumb) => ({\n name: crumb.label,\n url: absoluteUrl(siteUrl, crumb.href),\n })),\n )\n : null;\n\n return (\n <>\n <nav aria-label=\"브레드크럼\" className=\"rt-cms-breadcrumb\">\n <ol>\n {crumbs.map((crumb, index) =>\n index === crumbs.length - 1 ? (\n <li key={index} aria-current=\"page\">\n {crumb.label}\n </li>\n ) : (\n <li key={index}>\n <a href={crumb.href}>{crumb.label}</a>\n </li>\n ),\n )}\n </ol>\n </nav>\n {jsonLd ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLdJson(jsonLd) }}\n />\n ) : null}\n </>\n );\n}\n","// 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석 색인.\n//\n// 키 → 현재 공개 경로. 발행 글의 `path`(플랫폼이 저장한 정규 경로)와 `previousSlugs`\n// 로 만든다. FRONT 는 이 색인을 RenderTiptap / RootTaleBlogPost 의 `internalLinks`\n// 로 넘기면 되고, RootTaleBlogPost 는 본문에 토큰이 있을 때만 스스로 만든다.\n\nimport { internalContentPathIndex } from \"@roottale/cms-core\";\nimport { fetchPosts } from \"@roottale/cms-client/server\";\n\nexport interface FetchInternalContentPathIndexOptions {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly locale?: string;\n /** ISR revalidate 초. */\n readonly revalidate?: number;\n /** 안전장치 — 이 이상 페이지는 읽지 않는다(100건/페이지). 기본 20 = 2,000건. */\n readonly maxPages?: number;\n}\n\n/**\n * 발행 글 전부의 경로를 모아 키 색인을 만든다. 한 페이지라도 실패하면 그때까지\n * 모은 것으로 답한다(fail-soft — 링크가 몇 개 문구로 남는 편이 500 보다 낫다).\n */\nexport async function fetchInternalContentPathIndex(\n options: FetchInternalContentPathIndexOptions,\n): Promise<Map<string, string>> {\n const entries: { path?: string | null; previousSlugs?: string[] }[] = [];\n const maxPages = options.maxPages ?? 20;\n let cursor: string | undefined;\n for (let page = 0; page < maxPages; page += 1) {\n try {\n const result = await fetchPosts({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n type: \"post\",\n limit: 100,\n cursor,\n ...(options.locale ? { locale: options.locale } : {}),\n ...(options.revalidate !== undefined\n ? { revalidate: options.revalidate }\n : {}),\n });\n for (const post of result.items) {\n entries.push({ path: post.path, previousSlugs: post.previousSlugs });\n }\n if (!result.hasMore || !result.nextCursor) break;\n cursor = result.nextCursor;\n } catch {\n break;\n }\n }\n return internalContentPathIndex(entries);\n}\n\n/** 본문(JSON 또는 HTML)에 토큰이 있는지 — 색인을 만들 필요가 있는지 미리 본다. */\nexport function bodyHasInternalContentLinkTokens(\n doc: Record<string, unknown> | null | undefined,\n html?: string | null,\n): boolean {\n if (html && html.includes(\"[[internal:\")) return true;\n if (!doc) return false;\n try {\n return JSON.stringify(doc).includes(\"[[internal:\");\n } catch {\n return false;\n }\n}\n","import type { ReactElement } from \"react\";\nimport {\n jsonLdJson,\n type CmsPostContent,\n type RootTaleBlogSettings,\n} from \"@roottale/cms-client/server\";\nimport { extractFaqEntries, faqPageJsonLd } from \"@roottale/cms-core\";\n\n/** 공통 블록으로 옮기기 전 사이트의 CTA를 안전한 링크일 때만 렌더한다. */\nexport function renderPostCta(\n cta: RootTaleBlogSettings[\"postCta\"],\n): ReactElement | null {\n if (!cta) return null;\n\n const href = cta.buttonHref.trim();\n const isSafeHref =\n href.startsWith(\"/\") ||\n href.startsWith(\"#\") ||\n /^(https?:|mailto:|tel:)/i.test(href);\n if (!isSafeHref) return null;\n\n const isExternal = /^https?:/i.test(href);\n return (\n <aside data-roottale-cms=\"post-cta\" className=\"rt-cms-post-cta\">\n {cta.title ? <p className=\"rt-cms-post-cta__title\">{cta.title}</p> : null}\n {cta.description ? (\n <p className=\"rt-cms-post-cta__desc\">{cta.description}</p>\n ) : null}\n <a\n className=\"rt-cms-post-cta__btn\"\n href={href}\n target={isExternal ? \"_blank\" : undefined}\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n {cta.buttonLabel}\n </a>\n </aside>\n );\n}\n\n/** 관련 글 카드 1장에 필요한 것 — 편집자 선택(`relatedPosts`)과 자동 추천이 같은 모양으로 들어온다. */\nexport type RelatedPostCard = {\n readonly id: string;\n readonly title: string;\n readonly href: string;\n readonly featuredImageUrl: string | null;\n};\n\nexport function relatedCardsFromPosts(\n posts: readonly CmsPostContent[],\n href: (post: CmsPostContent) => string,\n): RelatedPostCard[] {\n return posts.map((post) => ({\n id: post.id,\n title: post.title,\n href: href(post),\n featuredImageUrl: post.featuredImageUrl,\n }));\n}\n\nexport function renderRelatedPosts(\n posts: readonly RelatedPostCard[],\n title: string,\n): ReactElement | null {\n if (posts.length === 0) return null;\n\n return (\n <nav data-roottale-cms=\"related\" className=\"rt-cms-related\" aria-label={title}>\n <h2 className=\"rt-cms-related-title\">{title}</h2>\n <ul className=\"rt-cms-related-list\">\n {posts.map((post) => (\n <li key={post.id} className=\"rt-cms-related-item\">\n <a className=\"rt-cms-related-link\" href={post.href}>\n {post.featuredImageUrl ? (\n <img\n className=\"rt-cms-related-thumb\"\n src={post.featuredImageUrl}\n alt=\"\"\n loading=\"lazy\"\n />\n ) : null}\n <span className=\"rt-cms-related-post-title\">{post.title}</span>\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\nfunction renderFaqJsonLd(doc: unknown): ReactElement | null {\n const jsonLd = faqPageJsonLd(extractFaqEntries(doc));\n if (!jsonLd) return null;\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLdJson(jsonLd) }}\n />\n );\n}\n\nexport function RootTaleFaqJsonLd({\n doc,\n}: {\n readonly doc: unknown;\n}): ReactElement | null {\n return renderFaqJsonLd(doc);\n}\n\nexport { renderFaqJsonLd };\n","import type { ReactElement } from \"react\";\nimport { t } from \"@roottale/cms-i18n\";\n\nexport interface RootTalePreviewNoticeProps {\n /** `ko`(기본) · `en`. */\n locale?: string;\n /** 기본 안내 문구를 바꾸고 싶을 때. */\n message?: string;\n /** 토큰 만료 시각(ISO) — 주면 \"HH:MM까지\" 를 덧붙인다. */\n expiresAt?: string;\n}\n\nfunction formatExpiry(locale: string | undefined, iso: string): string | null {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return null;\n const time = new Intl.DateTimeFormat(locale === \"en\" ? \"en-US\" : \"ko-KR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n }).format(date);\n return locale === \"en\" ? `Valid until ${time}.` : `${time}까지 볼 수 있어요.`;\n}\n\n/**\n * 미리보기 화면 상단 안내 띠. 본문 템플릿은 발행 화면과 완전히 같게 두고, \"이건\n * 미리보기다\" 라는 신호만 이 띠 하나로 준다. 검색엔진 색인 차단은 metadata\n * (`buildPreviewMetadata`) 몫이며, 이 컴포넌트는 사람용 안내다.\n */\nexport function RootTalePreviewNotice({\n locale,\n message,\n expiresAt,\n}: RootTalePreviewNoticeProps): ReactElement {\n const expiry = expiresAt ? formatExpiry(locale, expiresAt) : null;\n return (\n <div data-roottale-cms=\"preview-notice\" className=\"rt-cms-preview-notice\" role=\"status\">\n <span>{message ?? t(locale, \"previewNotice\")}</span>\n {expiry ? <span className=\"rt-cms-preview-notice__expiry\">{expiry}</span> : null}\n </div>\n );\n}\n","import type { ReactElement } from \"react\";\nimport { fetchPost } from \"@roottale/cms-client/server\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport { resolvePostBodySource } from \"./post-body-source.js\";\nimport { RenderTiptap } from \"./render-tiptap.js\";\nimport {\n resolveThemeStyleFailSoft,\n type RootTaleThemeInput,\n} from \"./theme.js\";\n\nexport type RootTalePageProps = {\n apiKey: string;\n slugOrId: string;\n baseUrl?: string;\n notFoundElement?: ReactElement;\n showTitle?: boolean;\n theme?: RootTaleThemeInput;\n locale?: string;\n /** 페이지 JSON을 렌더하기 직전 콘텐츠 참조를 실제 섹션 props로 바꾸는 서버 함수. */\n prepareBody?: (\n document: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n};\n\nexport async function RootTalePage(\n props: RootTalePageProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n baseUrl,\n notFoundElement,\n showTitle = true,\n theme: themeProp,\n locale,\n prepareBody,\n } = props;\n const [post, themeStyle] = await Promise.all([\n fetchPost({ apiKey, slugOrId, baseUrl, ...(locale ? { locale } : {}) }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n ]);\n\n if (!post) {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"page-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"pageNotFound\")}</p>\n </div>\n )\n );\n }\n\n const pageBodySource = resolvePostBodySource(post.bodyHtml);\n const bodyJson =\n pageBodySource.kind === \"json\" && prepareBody\n ? await prepareBody(post.bodyJson)\n : post.bodyJson;\n\n return (\n <article\n data-roottale-cms=\"page\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n {showTitle ? (\n <header className=\"rt-cms-post-header\">\n <h1 className=\"rt-cms-title\">{post.title}</h1>\n </header>\n ) : null}\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n {/* 글과 동일 — HTML 정본이 있으면 sanitize 후 우선, 없으면 Block JSON 폴백. */}\n {pageBodySource.kind === \"html\" ? (\n <div dangerouslySetInnerHTML={{ __html: pageBodySource.html }} />\n ) : (\n <RenderTiptap\n doc={bodyJson}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n />\n )}\n </div>\n </article>\n );\n}\n","import { fetchExposure } from \"@roottale/cms-client/server\";\n\nimport { RootTaleExposure } from \"../exposure-client.js\";\n\nexport interface RootTaleExposureSlotProps {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly siteId?: string;\n readonly slotKey: string;\n readonly path: string;\n readonly allowedVariants: readonly string[];\n readonly revalidate?: number;\n}\n\n/** FRONT가 코드로 만든 슬롯 안에서만 활성 배너·팝업을 렌더한다. */\nexport async function RootTaleExposureSlot(props: RootTaleExposureSlotProps) {\n const campaign = await fetchExposure({\n apiKey: props.apiKey,\n baseUrl: props.baseUrl,\n siteId: props.siteId,\n slotKey: props.slotKey,\n path: props.path,\n revalidate: props.revalidate,\n }).catch(() => null);\n if (!campaign || !props.allowedVariants.includes(campaign.content.variant)) return null;\n return <RootTaleExposure campaign={campaign} allowedVariants={props.allowedVariants} />;\n}\n"],"mappings":"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","names":["t","jsx","jsxs","themeToCssVars","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","themeToCssVars","jsx","jsxs","jsx","jsxs","POST_FOOTER_PATTERN_SLOT","fetchPosts","resolvePostDisplay","Fragment","jsx","Fragment","jsx","jsx","jsxs","value","Fragment","Fragment","jsx","jsx","jsx","jsxs","internalContentLinkResolverFrom","renderInternalContentLinksInHtml","sanitizeHtml","Fragment","jsx","jsxs","renderInternalContentLinksInHtml","internalContentLinkResolverFrom","resolvePostCollection","Fragment","jsx","jsxs","resolvePostCollection","fetchPosts","jsonLdJson","jsx","jsxs","isSafeHref","jsx","jsxs","fetchPosts","resolvePostDisplay","POST_FOOTER_PATTERN_SLOT","jsx","jsxs","fetchPost","jsx","jsxs","fetchPost","jsx"]}
|
|
1
|
+
{"version":3,"sources":["../src/PostCard.tsx","../../cms-i18n/src/index.ts","../src/TableOfContents.tsx","../../ui-react/dist/theme/index.js","../src/theme-css-vars.ts","../src/FloatingCta.tsx","../src/LeadForm.tsx","../src/block-to-react.tsx","../src/toc.ts","../src/server/blog-list.tsx","../src/server/blog-settings.ts","../src/server/fetch-posts-fail-soft.ts","../src/server/post-links.ts","../src/server/theme.tsx","../src/server/blog-categories.tsx","../src/server/blog-post.tsx","../src/server/site-patterns.tsx","../src/server/render-tiptap.tsx","../src/server/tiptap-node-renderer.tsx","../src/server/tiptap-embed-renderer.tsx","../src/server/tiptap-inline-renderer.tsx","../src/server/tiptap-types.ts","../src/server/blog-post-archive.tsx","../src/server/blog-post-body.tsx","../src/server/post-body-source.ts","../src/server/blog-post-breadcrumb.tsx","../src/server/internal-links.ts","../src/server/blog-post-footer.tsx","../src/server/preview-notice.tsx","../src/server/page.tsx","../src/server/exposure-slot.tsx"],"sourcesContent":["/**\n * `<RootTalePostCard>` — server-only summary card for a single CmsPostContent.\n *\n * Building block for `<RootTaleBlogList>` (and customer-side custom grids).\n * Pure SSR — featured image + category + title + meta + excerpt. Featured\n * image uses `CmsPostContent.featuredImageUrl` (CF Images delivery URL exposed\n * since cms-client 0.12); category is the first `terms` entry whose taxonomy is\n * `category`. Both render only when present, so text-only posts are unchanged.\n */\n\nimport type { ReactElement } from \"react\";\nimport type { CmsPostContent, CmsTermRef } from \"@roottale/cms-client/server\";\n\ntype CmsPostWithAuthor = CmsPostContent & { authorName?: string | null };\n\nexport interface RootTalePostCardProps {\n post: CmsPostContent;\n /** Customer-side URL builder. Defaults to `/blog/${slug}`. */\n href?: (post: CmsPostContent) => string;\n /**\n * 표시 옵션 (플랫폼 blog-settings + 글별 override resolve 결과). 미지정 시\n * 발행일만 표시(작성자 없음) — 직접 사용하는 커스텀 그리드 하위호환.\n * `showImage`/`showCategory` 는 데이터가 있을 때 노출 여부(기본 노출).\n */\n display?: {\n showDate: boolean;\n showAuthor: boolean;\n showImage?: boolean;\n showCategory?: boolean;\n };\n}\n\nfunction firstCategoryName(terms: CmsTermRef[] | undefined | null): string | null {\n if (!terms) return null;\n const cat = terms.find((t) => t.taxonomy === \"category\");\n return cat ? cat.name : null;\n}\n\nexport function RootTalePostCard(props: RootTalePostCardProps): ReactElement {\n const { post, href = defaultHref, display } = props;\n const showDate = display ? display.showDate : true;\n const showAuthor = display ? display.showAuthor : false;\n const showImage = display?.showImage !== false;\n const showCategory = display?.showCategory !== false;\n const authorName = (post as CmsPostWithAuthor).authorName;\n const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : \"\";\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const imageUrl = showImage ? post.featuredImageUrl : null;\n const category = showCategory ? firstCategoryName(post.terms) : null;\n return (\n <article data-roottale-cms=\"card\" className=\"rt-cms-card\">\n <a className=\"rt-cms-card-link\" href={href(post)}>\n {imageUrl ? (\n <img\n className=\"rt-cms-card-image\"\n src={imageUrl}\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n ) : null}\n {category ? (\n <span className=\"rt-cms-card-category\">{category}</span>\n ) : null}\n {publishedDate || (showAuthor && authorName) || views !== null ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"date\">\n {publishedDate}\n </span>\n ) : null}\n {showAuthor && authorName ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자 {authorName}\n </span>\n ) : null}\n {/* ADR-0065 — 조회수는 사이트 공개 토글 on 일 때만 API 가 viewCount 를\n 내려준다. 따라서 값이 있으면 그대로 노출(별도 표시 설정 불필요). */}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n <h2 className=\"rt-cms-title\">{post.title}</h2>\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n <span className=\"rt-cms-card-action\" aria-hidden=\"true\">\n 읽기\n </span>\n </a>\n </article>\n );\n}\n\nfunction defaultHref(post: CmsPostContent): string {\n return `/blog/${post.slug}`;\n}\n\nfunction formatPublishedDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(d);\n}\n","/**\n * `@roottale/cms-i18n` — RootTale CMS 렌더러 UI 문자열 (ADR-0034 §3).\n *\n * **콘텐츠 다국어(ADR-0052 A안, `posts.locale`/`translations[]`)와는 별개 층** —\n * 이 패키지는 렌더러가 소비자에게 보여주는 *고정 UI 카피*(빈 목록 안내, 목차\n * 제목 등)만 다룬다. 글 본문 번역은 사람이 admin 에서 직접 작성한다(§0).\n *\n * Phase 1(W4-6 PR C2) = 최소 실체화: locale 별 사전 + 타입 안전 `t()`.\n * gettext(.po 로딩)·ICU 플러럴 등은 Phase 4 몫 — **과설계 금지**\n * (ADR-0034 §3, `tenant-site-i18n-plan.md` §3 PR C2).\n */\n\n/** 사전이 존재하는 locale. 그 외(`ja`/`zh-hans` 등)는 `t()` 가 ko 로 fail-soft. */\nexport const CMS_I18N_LOCALES = [\"ko\", \"en\"] as const;\nexport type CmsI18nLocale = (typeof CMS_I18N_LOCALES)[number];\n\n/**\n * 사전 키 — `cms-renderer-next` 의 고객 노출 기본 문자열(1차 실체화 실측 범위)만.\n * 새 키를 늘릴 땐 ko/en 양쪽에 반드시 값을 채운다(타입이 강제).\n */\nexport interface CmsI18nDict {\n /** `RootTaleBlogList` — 발행 글 0건. */\n blogListEmpty: string;\n /** `RootTaleBlogCategories` — 카테고리 0건. */\n blogCategoriesEmpty: string;\n /** `RootTaleBlogList` — 카테고리 필터 \"전체\" 칩. */\n allCategoriesLabel: string;\n /** `RootTaleBlogPost` — 관련 글 섹션 제목. */\n relatedPostsTitle: string;\n /** `RootTaleBlogPost` — 글 미발견 기본 안내. */\n postNotFound: string;\n /** `RootTalePage` — 페이지 미발견 기본 안내. */\n pageNotFound: string;\n /** `RootTaleBlogPost` — 미리보기 링크 만료 기본 안내. */\n previewExpired: string;\n /** `RootTalePreviewNotice` — 미리보기 화면 상단 안내. */\n previewNotice: string;\n /** `RootTaleTableOfContents` — 목차 기본 제목. */\n tableOfContentsTitle: string;\n}\n\nconst DICTS: Record<CmsI18nLocale, CmsI18nDict> = {\n ko: {\n blogListEmpty: \"아직 발행된 글이 없습니다.\",\n blogCategoriesEmpty: \"아직 카테고리가 없습니다.\",\n allCategoriesLabel: \"전체\",\n relatedPostsTitle: \"관련 글\",\n postNotFound: \"글을 찾을 수 없습니다.\",\n pageNotFound: \"페이지를 찾을 수 없습니다.\",\n previewExpired: \"미리보기 링크가 만료됐어요. 관리자 편집기에서 미리보기를 다시 열어 주세요.\",\n previewNotice: \"미리보기 — 아직 공개되지 않은 내용이며 검색에 노출되지 않습니다.\",\n tableOfContentsTitle: \"목차\",\n },\n en: {\n blogListEmpty: \"No posts published yet.\",\n blogCategoriesEmpty: \"No categories yet.\",\n allCategoriesLabel: \"All\",\n relatedPostsTitle: \"Related posts\",\n postNotFound: \"Post not found.\",\n pageNotFound: \"Page not found.\",\n previewExpired: \"This preview link has expired. Open the preview again from the admin editor.\",\n previewNotice: \"Preview — this content is not public and is hidden from search engines.\",\n tableOfContentsTitle: \"Table of contents\",\n },\n};\n\n/** 사전 없는 locale(`ja`/`zh-hans`/undefined 등) 은 ko 로 fail-soft — 문자열이 아예 안 나오는 것보다 낫다. */\nfunction resolveLocale(locale: string | undefined): CmsI18nLocale {\n return locale === \"en\" ? \"en\" : \"ko\";\n}\n\n/**\n * 타입 안전 `t()`. `locale` 를 안 주거나(undefined) 사전이 없는 locale 이면 ko\n * 기본값 — **하위호환**: locale 프로퍼티를 모르는 기존 소비자는 지금까지처럼\n * 한글 문자열을 그대로 받는다(diff 0).\n *\n * ```ts\n * import { t } from \"@roottale/cms-i18n\";\n * const emptyMessage = props.emptyMessage ?? t(props.locale, \"blogListEmpty\");\n * ```\n */\nexport function t(locale: string | undefined, key: keyof CmsI18nDict): string {\n return DICTS[resolveLocale(locale)][key];\n}\n\nexport const PACKAGE_NAME = \"@roottale/cms-i18n\";\n","/**\n * `<RootTaleTableOfContents>` — server-only anchor list for blog posts.\n *\n * Pure SSR. Renders `<nav>` + ordered anchors that link to `#<id>` slots\n * emitted by `attachHeadingIds`. No scroll-spy here — that requires an\n * `IntersectionObserver` which only exists in the browser; a client-island\n * variant can layer on top later without changing the data contract.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/TableOfContents.tsx`\n * (had `'use client'` + IntersectionObserver). Stripped to server-safe form per\n * the platform renderer contract (no `'use client'` boundary — ADR-0023 §5.3).\n */\n\nimport type { ReactElement } from \"react\";\n// ADR-0034 §3 (W4-6 PR C2) — 기본 제목의 locale 해석(콘텐츠 다국어와 별개 층).\nimport { t } from \"@roottale/cms-i18n\";\n\nimport type { TocEntry } from \"./toc.js\";\n\nexport interface RootTaleTableOfContentsProps {\n entries: TocEntry[];\n title?: string;\n /** 명시 `title` 없을 때 기본 제목의 locale. 미지정 = ko(\"목차\"). */\n locale?: string;\n}\n\nexport function RootTaleTableOfContents(\n props: RootTaleTableOfContentsProps,\n): ReactElement | null {\n const { entries, locale, title = t(locale, \"tableOfContentsTitle\") } = props;\n if (entries.length === 0) return null;\n return (\n <nav data-roottale-cms=\"toc\" className=\"rt-cms-toc\" aria-label={title}>\n <details className=\"rt-cms-toc-details\" open>\n <summary className=\"rt-cms-toc-summary\">\n {title ? <span className=\"rt-cms-toc-title\">{title}</span> : null}\n <span className=\"rt-cms-toc-toggle\" aria-hidden=\"true\" />\n </summary>\n <ol className=\"rt-cms-toc-list\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className={`rt-cms-toc-item rt-cms-toc-level-${entry.level}`}\n >\n <a href={`#${entry.id}`}>{entry.text}</a>\n </li>\n ))}\n </ol>\n </details>\n </nav>\n );\n}\n","import { jsx as _jsx } from \"react/jsx-runtime\";\nexport function themeToCssVars(theme) {\n return {\n ...(theme.colors?.primary ? { \"--rt-color-primary\": theme.colors.primary } : {}),\n ...(theme.colors?.primaryForeground ? { \"--rt-color-primary-foreground\": theme.colors.primaryForeground } : {}),\n ...(theme.colors?.secondary ? { \"--rt-color-secondary\": theme.colors.secondary } : {}),\n ...(theme.colors?.secondaryForeground ? { \"--rt-color-secondary-foreground\": theme.colors.secondaryForeground } : {}),\n ...(theme.colors?.accent ? { \"--rt-color-accent\": theme.colors.accent } : {}),\n ...(theme.colors?.accentForeground ? { \"--rt-color-accent-foreground\": theme.colors.accentForeground } : {}),\n ...(theme.colors?.background ? { \"--rt-color-background\": theme.colors.background } : {}),\n ...(theme.colors?.foreground ? { \"--rt-color-foreground\": theme.colors.foreground } : {}),\n ...(theme.colors?.muted ? { \"--rt-color-muted\": theme.colors.muted } : {}),\n ...(theme.colors?.mutedForeground ? { \"--rt-color-muted-foreground\": theme.colors.mutedForeground } : {}),\n ...(theme.colors?.destructive ? { \"--rt-color-destructive\": theme.colors.destructive } : {}),\n ...(theme.colors?.destructiveForeground ? { \"--rt-color-destructive-foreground\": theme.colors.destructiveForeground } : {}),\n ...(theme.colors?.border ? { \"--rt-color-border\": theme.colors.border } : {}),\n ...(theme.colors?.input ? { \"--rt-color-input\": theme.colors.input } : {}),\n ...(theme.colors?.ring ? { \"--rt-color-ring\": theme.colors.ring } : {}),\n ...(theme.fonts?.body ? { \"--rt-font-body\": theme.fonts.body } : {}),\n ...(theme.fonts?.display ? { \"--rt-font-display\": theme.fonts.display } : {}),\n ...(theme.radius?.sm ? { \"--rt-radius-sm\": theme.radius.sm } : {}),\n ...(theme.radius?.md ? { \"--rt-radius-md\": theme.radius.md } : {}),\n ...(theme.radius?.lg ? { \"--rt-radius-lg\": theme.radius.lg } : {}),\n ...(theme.radius?.xl ? { \"--rt-radius-xl\": theme.radius.xl } : {}),\n };\n}\nexport function RootTaleThemeProvider({ children, name = \"custom\", theme, style, ...props }) {\n return (_jsx(\"div\", { ...props, \"data-rt-theme\": name, style: { ...style, ...themeToCssVars(theme) }, children: children }));\n}\n","import type { RootTaleTheme } from \"@roottale/cms-client/server\";\nimport type { CSSProperties } from \"react\";\nimport { themeToCssVars as themeToUiCssVars } from \"@roottale/ui-react/theme\";\n\n// 공개 선언은 비공개 UI 패키지 없이 소비할 수 있어야 한다.\nexport type RootTaleCssVars = CSSProperties & {\n [customProperty: `--${string}`]: string | undefined;\n};\n\nexport function themeToCssVars(theme: RootTaleTheme | null): RootTaleCssVars | null {\n if (!theme) return null;\n const vars = themeToUiCssVars(theme);\n if (Object.keys(vars).length === 0) return null;\n return vars;\n}\n","/**\n * `<RootTaleFloatingCta>` — fixed-position call-to-action stack.\n *\n * Server-only (anchors only, no state). External targets (`kakao` / `custom`)\n * open in a new tab with `rel=\"noopener noreferrer\"`; tel/contact stay in the\n * same tab so iOS/Android can trigger the dialer / mailto handler.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/FloatingCta.tsx`.\n */\n\nimport type { ReactElement } from \"react\";\n\nexport type CtaButtonType = \"phone\" | \"contact\" | \"kakao\" | \"custom\";\n\nexport interface CtaButton {\n type: CtaButtonType;\n label: string;\n href: string;\n /** Optional override icon (emoji or short text). */\n icon?: string;\n}\n\nexport type FloatingCtaPosition =\n | \"bottom-right\"\n | \"bottom-left\"\n | \"bottom-center\";\n\nexport interface RootTaleFloatingCtaProps {\n buttons: CtaButton[];\n position?: FloatingCtaPosition;\n}\n\nexport function RootTaleFloatingCta(\n props: RootTaleFloatingCtaProps,\n): ReactElement | null {\n const { buttons, position = \"bottom-right\" } = props;\n if (!buttons || buttons.length === 0) return null;\n return (\n <div\n data-roottale-cms=\"floating-cta\"\n className={`rt-cms-floating-cta rt-cms-floating-cta--${position}`}\n >\n {buttons.map((btn, idx) => {\n const isExternal = btn.type === \"kakao\" || btn.type === \"custom\";\n return (\n <a\n key={`${btn.type}-${idx}`}\n href={btn.href}\n className={`rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`}\n target={isExternal ? \"_blank\" : undefined}\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n <span aria-hidden=\"true\" className=\"rt-cms-floating-cta__icon\">\n {btn.icon ?? defaultIcon(btn.type)}\n </span>\n <span className=\"rt-cms-floating-cta__label\">{btn.label}</span>\n </a>\n );\n })}\n </div>\n );\n}\n\nfunction defaultIcon(type: CtaButtonType): string {\n switch (type) {\n case \"phone\":\n return \"☎\";\n case \"contact\":\n return \"✉\";\n case \"kakao\":\n return \"💬\";\n case \"custom\":\n return \"→\";\n }\n}\n","/**\n * `@roottale/cms-renderer-next` — RootTaleLeadForm RSC.\n *\n * 외부 고객 사이트(roottale.com, 고객사 외주)의 진단 신청 폼.\n *\n * 권장 연동(API key 하나): `action` 을 *사이트 자체* 의 Route Handler / 폼\n * action 으로 두고, 그 서버 코드가 `@roottale/cms-client` 의 `submitInquiry()` 로\n * 블로그 조회와 동일한 `ROOTTALE_API_KEY` 를 실어 intake API 로 forward 한다. 키가\n * tenant 를 식별 → 리드가 그 tenant 의 admin /crm 으로 라우팅, `tenant_slug` 불필요.\n * (API key 는 브라우저로 노출 금지 — 직접 api.roottale.com 으로 POST 하지 말 것.)\n *\n * PII 는 server 측에서 암호화 후 `inquiries` insert, 302 `?ok=1` / `?err=*` redirect.\n *\n * vertical prop:\n * - 미지정 = \"분야 선택\" select 노출 (consulting/medical/tax/legal), 필드셋은\n * 기존 B2B 세트 그대로(방문자가 vertical 을 스스로 고르는 범용 폼이라 필드를\n * vertical 별로 분기하지 않음)\n * - 지정 = hidden input + 표시 라벨 + vertical 별 추가 필드\n * (medical = 국외이전 동의 — ADR-0018)\n *\n * vertical=\"medical\" 필드셋 (C5 1차, premium-component-backlog):\n * 환자가 채우는 문의 폼에 B2B 리드 필드(\"사업체명\" 필수·\"현재 사이트 URL\")를\n * 노출하던 정합성 문제를 수정 — 의료는 이름·전화번호(필수) + 상담 희망\n * 분야/증상·문의 내용(선택)만 노출한다. intake 계약(tenant-api `POST\n * /v1/public/inquiries`, `packages/api-core/src/routes/inquiries.ts`)이\n * `business_name` 을 vertical 불문 hard-required 로 검증하므로(신규 폼 백엔드\n * 금지 — F3 계약 고정), 화면에는 안 보이지만 hidden input 으로 자리표시자\n * `\"-\"` 를 전송해 계약을 만족시킨다. \"상담 희망 분야/증상\" 은 기존\n * `consultation_field` 키(어드민 CRM \"상담 분야\" 표시)로, \"문의 내용\" 은 기존\n * `message` 키로 매핑 — 둘 다 이미 intake 가 선택 필드로 수용하던 키라 서버\n * 변경이 필요 없다. \"현재 사이트 URL\" 은 의료에 무의미해 필드 자체를 생략.\n *\n * redirectPath prop:\n * - 외부 사이트가 폼 제출 후 돌아갈 path (API 가 allowlist 검증, ?ok=1 / ?err=*)\n *\n * RSC form. Turnstile를 켠 경우 Cloudflare 위젯 script만 추가한다.\n * customer-facing className 전체 `.rt-cms-*` prefix, scoped via\n * `[data-roottale-cms]` (cms-public.css). customer override 자유.\n */\n\nimport type { ReactElement } from \"react\";\n\nimport { LeadFormMeta } from \"./lead-form-meta.client.js\";\n\nexport type LeadFormVertical = \"consulting\" | \"medical\" | \"tax\" | \"legal\";\n\nexport interface RootTaleLeadFormProps {\n /**\n * 폼 제출 대상 — *사이트 자체* 의 server 엔드포인트(Route Handler / form action).\n * 그 서버 코드가 `cms-client` `submitInquiry()` 로 API key 를 실어 forward 한다.\n * 예: `/api/inquiry`. (API key 노출 위험 때문에 intake API 직접 지정 금지.)\n */\n action: string;\n /**\n * vertical 고정. 미지정 시 select 노출.\n * `medical` 일 때 국외이전 동의 체크박스 추가 (ADR-0018).\n */\n vertical?: LeadFormVertical;\n /**\n * 폼 제출 후 API가 돌려보낼 절대 URL.\n * tenant-api `LEAD_INTAKE_ALLOWED_ORIGINS` allowlist 검증. fail = fallback.\n * 미지정 시 tenant-api env의 LEAD_INTAKE_REDIRECT_BASE 사용.\n */\n redirectUrl?: string;\n heading?: string;\n description?: string;\n submitLabel?: string;\n turnstileSiteKey?: string;\n /** 추가 클래스 (customer 디자인 hook). */\n className?: string;\n}\n\nconst VERTICAL_LABEL: Record<LeadFormVertical, string> = {\n consulting: \"컨설팅\",\n medical: \"의료\",\n tax: \"세무\",\n legal: \"법률\",\n};\n\nconst VERTICAL_OPTIONS: { value: LeadFormVertical; label: string }[] = [\n { value: \"consulting\", label: \"컨설팅 / 일반\" },\n { value: \"medical\", label: \"의료 (병의원·치과·한의원)\" },\n { value: \"tax\", label: \"세무 (세무사·회계사)\" },\n { value: \"legal\", label: \"법률 (법무법인·변호사)\" },\n];\n\nexport function RootTaleLeadForm(\n props: RootTaleLeadFormProps,\n): ReactElement {\n const {\n action,\n vertical,\n redirectUrl,\n heading,\n description,\n submitLabel = \"진단 신청\",\n turnstileSiteKey,\n className,\n } = props;\n\n const formClass = [\"rt-cms-lead-form\", className].filter(Boolean).join(\" \");\n const isMedical = vertical === \"medical\";\n\n return (\n <form\n method=\"post\"\n action={action}\n data-roottale-cms=\"lead-form\"\n className={formClass}\n >\n {/* 어트리뷰션·여정 계측 island(RSC 경계 §5) — 시각 출력 없음. v1 은\n `form_id` 를 \"contact\" 로 고정한다(사이트당 실배치 리드 폼이\n /contact 1개뿐 — 인스턴스별 식별이 필요해지면 공개 prop 신설 검토). */}\n <LeadFormMeta formId=\"contact\" />\n {heading ? (\n <h2 className=\"rt-cms-lead-heading\">{heading}</h2>\n ) : null}\n {description ? (\n <p className=\"rt-cms-lead-description\">{description}</p>\n ) : null}\n\n {redirectUrl ? (\n <input type=\"hidden\" name=\"_redirect_url\" value={redirectUrl} />\n ) : null}\n\n {vertical ? (\n <>\n <input type=\"hidden\" name=\"vertical\" value={vertical} />\n <p className=\"rt-cms-lead-vertical-label\">\n 분야: <strong>{VERTICAL_LABEL[vertical]}</strong>\n </p>\n </>\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 분야 <span aria-hidden=\"true\">*</span>\n </span>\n <select\n name=\"vertical\"\n required\n className=\"rt-cms-field-input rt-cms-field-select\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>\n 선택하세요\n </option>\n {VERTICAL_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n )}\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이름 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"contact_name\"\n required\n autoComplete=\"name\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n {isMedical ? (\n // 환자 문의에는 \"사업체명\" 개념이 없다 — 화면에는 노출하지 않되,\n // intake 가 vertical 불문 hard-required 로 검증하는 계약(위 파일\n // 헤더 코멘트 참조)을 만족시키기 위해 자리표시자만 hidden 전송.\n <input type=\"hidden\" name=\"business_name\" value=\"-\" />\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 사업체명 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"business_name\"\n required\n autoComplete=\"organization\"\n className=\"rt-cms-field-input\"\n />\n </label>\n )}\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 전화번호 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"tel\"\n name=\"phone\"\n required\n autoComplete=\"tel\"\n inputMode=\"tel\"\n pattern={\"0\\\\d{1,2}-?\\\\d{3,4}-?\\\\d{4}\"}\n placeholder=\"010-1234-5678\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이메일 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"email\"\n name=\"email\"\n required\n autoComplete=\"email\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n {isMedical ? (\n <>\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 상담 희망 분야·증상 (선택)\n </span>\n <input\n type=\"text\"\n name=\"consultation_field\"\n placeholder=\"예: 무릎 통증, 정기검진\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">문의 내용 (선택)</span>\n <textarea\n name=\"message\"\n rows={4}\n placeholder=\"증상이 언제부터 있었는지, 궁금하신 점을 자유롭게 적어주세요.\"\n className=\"rt-cms-field-input rt-cms-field-textarea\"\n />\n </label>\n </>\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">현재 사이트 URL (선택)</span>\n <input\n type=\"url\"\n name=\"current_site_url\"\n placeholder=\"https://\"\n className=\"rt-cms-field-input\"\n />\n <span className=\"rt-cms-field-hint\">없으면 비워두세요.</span>\n </label>\n )}\n\n <label className=\"rt-cms-lead-consent\">\n <input type=\"checkbox\" name=\"privacy_consent\" required />\n <span>\n <strong>(필수)</strong> 개인정보 수집·이용에 동의합니다. (수집 항목:{\" \"}\n {isMedical\n ? \"이름·연락처·이메일·상담 내용(작성 시)·사이트 이용 기록(방문 페이지·상호작용)\"\n : \"이름·연락처·이메일·사업체명·사이트 이용 기록(방문 페이지·상호작용)\"}\n . 보관 기간: 문의 종료 후 3년)\n </span>\n </label>\n\n {vertical === \"medical\" ? (\n <label className=\"rt-cms-lead-consent\">\n <input\n type=\"checkbox\"\n name=\"overseas_transfer_consent\"\n required\n />\n <span>\n <strong>(필수, 의료)</strong> 의료 PII 의 국외이전(보관·처리)에\n 동의합니다. (의료법 §21·개인정보보호법 §28 — ADR-0018)\n </span>\n </label>\n ) : null}\n\n {turnstileSiteKey ? (\n <>\n <script\n src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\"\n async\n defer\n />\n <div\n className=\"cf-turnstile\"\n data-sitekey={turnstileSiteKey}\n data-action=\"turnstile-spin-v1\"\n />\n </>\n ) : null}\n\n <div className=\"rt-cms-lead-actions\">\n <button type=\"submit\" className=\"rt-cms-lead-submit\">\n {submitLabel}\n </button>\n </div>\n </form>\n );\n}\n","// ADR-0034 §1.5 amended — Block JSON → React elements\n//\n// Next/RSC public renderer 가 본 함수로 block tree 를 server-side React element\n// 로 변환. cms-renderer-astro 의 `block-to-html.ts` 와 1:1 대응 (같은 7 + α\n// block 핸들러). `BlockDefinition.nextRender` 정합.\n//\n// 알려지지 않은 block 은 rawHtml 출력 (codex v3 verdict #4, opaque atom\n// round-trip 보존).\n\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport {\n type Block,\n type BindingResolver,\n applyBlockBindings,\n createFieldBindingResolver,\n sanitizeHtml,\n} from \"@roottale/cms-core\";\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nfunction htmlContent(html: string): { __html: string } {\n // dangerouslySetInnerHTML 진입 전 단일 chokepoint — 모든 block 의\n // rawHtml/content 는 여기서 sanitize 된다 (cms-core sanitizeHtml). API 키로\n // 글을 작성하는 외부 writer 가 inline `<script>`/`on*`/`javascript:` 를\n // 주입하는 저장형 XSS 방어.\n return { __html: sanitizeHtml(html) };\n}\n\nfunction renderInner(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * 단일 block 을 ReactElement 로 변환. `block-to-html.ts` 와 동일 분기.\n */\nexport function renderBlock(block: Block, key: number = 0): ReactElement {\n const dataBlockId = block._id;\n\n switch (block.name) {\n case \"core/paragraph\": {\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n return (\n <p\n key={key}\n data-block-id={dataBlockId}\n dangerouslySetInnerHTML={htmlContent(text)}\n />\n );\n }\n case \"core/heading\": {\n const levelRaw = block.attributes.level;\n const level =\n typeof levelRaw === \"number\" && levelRaw >= 2 && levelRaw <= 4\n ? levelRaw\n : 2;\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const props = {\n \"data-block-id\": dataBlockId,\n dangerouslySetInnerHTML: htmlContent(text),\n } as const;\n /* eslint-disable jsx-a11y/heading-has-content -- 내용은 props 의\n dangerouslySetInnerHTML 로 들어간다(CMS 본문 HTML). 룰은 JSX children\n 만 보고 \"빈 제목\"으로 판단하는데, 빈 제목은 위에서 걸러진다. */\n if (level === 2) return <h2 key={key} {...props} />;\n if (level === 3) return <h3 key={key} {...props} />;\n return <h4 key={key} {...props} />;\n /* eslint-enable jsx-a11y/heading-has-content */\n }\n case \"core/image\": {\n const src = isString(block.attributes.url) ? block.attributes.url : \"\";\n const alt = isString(block.attributes.alt) ? block.attributes.alt : \"\";\n const caption = isString(block.attributes.caption)\n ? block.attributes.caption\n : null;\n return (\n <figure key={key} data-block-id={dataBlockId}>\n <img src={src} alt={alt} loading=\"lazy\" />\n {caption && <figcaption>{caption}</figcaption>}\n </figure>\n );\n }\n case \"core/list\": {\n const ordered = block.attributes.ordered === true;\n const items = (block.innerBlocks ?? []).map(\n (item, i): ReactNode =>\n item.rawHtml ? (\n <li\n key={i}\n dangerouslySetInnerHTML={htmlContent(item.rawHtml)}\n />\n ) : (\n <li key={i}>{renderInner(item.innerBlocks ?? [])}</li>\n ),\n );\n return ordered ? (\n <ol key={key} data-block-id={dataBlockId}>\n {items}\n </ol>\n ) : (\n <ul key={key} data-block-id={dataBlockId}>\n {items}\n </ul>\n );\n }\n case \"core/quote\": {\n const inner = renderInner(block.innerBlocks ?? []);\n const citation = isString(block.attributes.citation)\n ? block.attributes.citation\n : null;\n return (\n <blockquote key={key} data-block-id={dataBlockId}>\n {inner}\n {citation && <cite>{citation}</cite>}\n </blockquote>\n );\n }\n case \"core/code\": {\n const code = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const lang = isString(block.attributes.language)\n ? block.attributes.language\n : undefined;\n return (\n <pre\n key={key}\n data-block-id={dataBlockId}\n data-language={lang}\n >\n <code>{code}</code>\n </pre>\n );\n }\n case \"core/columns\": {\n return (\n <div\n key={key}\n className=\"rt-columns\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n case \"core/separator\":\n return <hr key={key} data-block-id={dataBlockId} />;\n case \"core/spacer\": {\n const heightRaw = block.attributes.height;\n const height = isString(heightRaw) ? heightRaw : \"32px\";\n return (\n <div\n key={key}\n className=\"rt-spacer\"\n data-block-id={dataBlockId}\n style={{ height }}\n />\n );\n }\n case \"core/group\": {\n return (\n <div\n key={key}\n className=\"rt-group\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n default: {\n // codex review §15 — 알려지지 않은 block 의 rawHtml 을 그대로\n // `dangerouslySetInnerHTML` 로 내보내면 API key 로 글을 작성하는 외부\n // 클라이언트가 임의 HTML/script 를 외부 사이트로 삽입할 수 있다 (XSS).\n // rawHtml 분기 자체를 제거 — 후속 PR 에서 cms-core 의 sanitize 모듈을\n // 도입한 뒤에만 raw 출력을 허용한다.\n const rawHtml = block.rawHtml;\n return (\n <div\n key={key}\n className=\"rt-unknown-block\"\n data-block-id={dataBlockId}\n data-block-name={block.name}\n data-rawhtml-suppressed={rawHtml ? \"true\" : undefined}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n }\n}\n\n/** Block tree 전체를 React element 배열로 직렬화. */\nexport function renderBlocks(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * ADR-0041 §7 — Block Bindings 를 먼저 적용한 뒤 렌더.\n * `metadata.bindings` 의 `roottale/field`/`acf/field` 를 `fields`(acf 값)로\n * 치환. 바인딩 없으면 renderBlocks 와 동일.\n */\nexport function renderBoundBlocks(\n blocks: readonly Block[],\n opts: {\n fields?: Record<string, unknown> | null;\n resolver?: BindingResolver;\n } = {},\n): ReactElement[] {\n const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);\n return renderBlocks(applyBlockBindings(blocks, resolver));\n}\n","/**\n * `@roottale/cms-renderer/toc` — Tiptap doc → TOC entries + heading id injection.\n *\n * Server-only utility. Walks a Tiptap JSON doc, extracts H1-H4 headings, and\n * derives stable kebab-case ids (Korean-safe). The companion `attachHeadingIds`\n * mutates a shallow clone of the doc so renderers can emit heading anchors\n * that match what `<RootTaleTableOfContents>` links to.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/toc.ts`\n * (HTML-string based, vendored snapshot). Ported to Tiptap-JSON because the\n * platform renderer no longer round-trips through HTML before render.\n */\n\nexport interface TocEntry {\n level: 2 | 3 | 4;\n text: string;\n id: string;\n}\n\ntype TiptapNode = {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown>;\n content?: TiptapNode[];\n marks?: { type?: string }[];\n};\n\ntype TiptapDoc = { type?: string; content?: TiptapNode[] };\n\nfunction tocLevel(rawLevel: number): TocEntry[\"level\"] | null {\n if (rawLevel < 1 || rawLevel > 4) return null;\n if (rawLevel < 3) return 2;\n return rawLevel === 3 ? 3 : 4;\n}\n\nexport function headingToId(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction textOf(node: TiptapNode): string {\n if (typeof node.text === \"string\") return node.text;\n if (!Array.isArray(node.content)) return \"\";\n return node.content.map(textOf).join(\"\");\n}\n\nexport function extractToc(doc: Record<string, unknown>): TocEntry[] {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n const entries: TocEntry[] = [];\n const idCounts = new Map<string, number>();\n\n for (const node of content) {\n if (node?.type !== \"heading\") continue;\n const rawLevel = Number(node.attrs?.level ?? 2);\n const level = tocLevel(rawLevel);\n if (level === null) continue;\n const text = textOf(node).trim();\n if (!text) continue;\n\n const base = headingToId(text) || \"heading\";\n const count = idCounts.get(base) ?? 0;\n const id = count === 0 ? base : `${base}-${count}`;\n idCounts.set(base, count + 1);\n\n entries.push({ level, text, id });\n }\n\n return entries;\n}\n\n/**\n * Returns a shallow-cloned Tiptap doc where each H1-H4 carries the matching\n * `attrs.id` from `entries` (same order as `extractToc` returns). Existing\n * `id` attrs are overwritten so renderer + TOC always agree on anchors.\n */\nexport function attachHeadingIds(\n doc: Record<string, unknown>,\n entries: TocEntry[],\n): Record<string, unknown> {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n let cursor = 0;\n const nextContent = content.map((node) => {\n if (node?.type !== \"heading\") return node;\n const rawLevel = Number(node.attrs?.level ?? 2);\n if (rawLevel < 1 || rawLevel > 4) return node;\n if (!textOf(node).trim()) return node;\n const entry = entries[cursor++];\n if (!entry) return node;\n return {\n ...node,\n attrs: { ...(node.attrs ?? {}), id: entry.id },\n };\n });\n return { ...(root as Record<string, unknown>), content: nextContent };\n}\n","import type { ReactElement } from \"react\";\nimport {\n resolvePostDisplay,\n type CmsPostContent,\n type CmsPostType,\n} from \"@roottale/cms-client/server\";\nimport {\n postsForCollection,\n postsNotInAnyCollection,\n type RouteCollection,\n} from \"@roottale/cms-core\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport {\n collectCategories,\n defaultCategoryHref,\n filterPostsByCategory,\n} from \"../blog-filter.js\";\nimport { RootTalePostCard } from \"../PostCard.js\";\nimport { fetchBlogSettingsFailSoft } from \"./blog-settings.js\";\nimport { fetchPostsFailSoft } from \"./fetch-posts-fail-soft.js\";\nimport { defaultPostHref, makeCollectionPostHref } from \"./post-links.js\";\nimport {\n resolveThemeStyleFailSoft,\n type RootTaleThemeInput,\n} from \"./theme.js\";\n\nexport type RootTaleBlogListProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n /** Filter by post type (`post` or `page`). Default = `post` only. */\n type?: CmsPostType;\n postHref?: (post: CmsPostContent) => string;\n emptyMessage?: string;\n collection?: string;\n collections?: readonly RouteCollection[];\n /**\n * `collection`과 배타적이다. `collections` 없이 지정해도 예외를 던진다.\n */\n excludeCollections?: boolean;\n showCategoryFilter?: boolean;\n activeCategory?: string;\n categoryHref?: (slug: string | null) => string;\n allCategoriesLabel?: string;\n author?: string;\n theme?: RootTaleThemeInput;\n locale?: string;\n};\n\ntype ScopePostsInput = {\n readonly posts: CmsPostContent[];\n readonly collection?: string;\n readonly collections?: readonly RouteCollection[];\n readonly excludeCollections: boolean;\n};\n\nfunction validateCollectionScope(props: RootTaleBlogListProps): void {\n if (props.collection && props.excludeCollections) {\n throw new Error(\n \"RootTaleBlogList: `collection` 과 `excludeCollections` 를 동시에 줄 수 없습니다 — 포지티브/네거티브 스코프는 배타적입니다.\",\n );\n }\n if (props.excludeCollections && !props.collections) {\n throw new Error(\n \"RootTaleBlogList: `excludeCollections` 는 `collections` 와 함께 줘야 합니다.\",\n );\n }\n}\n\nfunction scopePosts(input: ScopePostsInput): CmsPostContent[] {\n const { posts, collection, collections, excludeCollections } = input;\n if (collection && collections) {\n return postsForCollection(posts, collections, collection);\n }\n if (excludeCollections && collections) {\n return postsNotInAnyCollection(posts, collections);\n }\n return posts;\n}\n\nexport async function RootTaleBlogList(\n props: RootTaleBlogListProps,\n): Promise<ReactElement> {\n validateCollectionScope(props);\n\n const {\n apiKey,\n baseUrl,\n limit,\n type = \"post\",\n collection,\n collections,\n excludeCollections = false,\n locale,\n emptyMessage = t(locale, \"blogListEmpty\"),\n showCategoryFilter = false,\n activeCategory,\n categoryHref = defaultCategoryHref,\n allCategoriesLabel = t(locale, \"allCategoriesLabel\"),\n author,\n theme: themeProp,\n } = props;\n const postHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n\n const [page, themeStyle, blogSettings] = await Promise.all([\n fetchPostsFailSoft({\n apiKey,\n baseUrl,\n limit,\n type,\n ...(author ? { author } : {}),\n ...(collection && collections ? { collectionKey: collection } : {}),\n ...(excludeCollections && collections ? { excludeCollections: true } : {}),\n ...(locale ? { locale } : {}),\n }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n fetchBlogSettingsFailSoft({ apiKey, baseUrl }),\n ]);\n const items = scopePosts({\n posts: page.items,\n collection,\n collections,\n excludeCollections,\n });\n\n if (items.length === 0) {\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n </div>\n );\n }\n\n const categories = showCategoryFilter ? collectCategories(items) : [];\n const visiblePosts = showCategoryFilter\n ? filterPostsByCategory(items, activeCategory)\n : items;\n const isAllCategoriesActive = !activeCategory;\n\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n {showCategoryFilter && categories.length > 0 ? (\n <nav className=\"rt-cms-filter\" aria-label=\"카테고리 필터\">\n <a\n className=\"rt-cms-filter-chip\"\n href={categoryHref(null)}\n data-active={isAllCategoriesActive ? \"\" : undefined}\n aria-current={isAllCategoriesActive ? \"true\" : undefined}\n >\n {allCategoriesLabel}\n </a>\n {categories.map((category) => (\n <a\n key={category.slug}\n className=\"rt-cms-filter-chip\"\n href={categoryHref(category.slug)}\n data-active={activeCategory === category.slug ? \"\" : undefined}\n aria-current={\n activeCategory === category.slug ? \"true\" : undefined\n }\n >\n {category.name}\n </a>\n ))}\n </nav>\n ) : null}\n {visiblePosts.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-list\">\n {visiblePosts.map((post) => (\n <li key={post.id} className=\"rt-cms-list-item\">\n <RootTalePostCard\n post={post}\n href={postHref}\n display={resolvePostDisplay(post, blogSettings)}\n />\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","import {\n DEFAULT_BLOG_SETTINGS,\n fetchBlogSettings,\n type RootTaleBlogSettings,\n} from \"@roottale/cms-client/server\";\n\ntype FetchBlogSettingsInput = {\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\nexport async function fetchBlogSettingsFailSoft(\n input: FetchBlogSettingsInput,\n): Promise<RootTaleBlogSettings> {\n return fetchBlogSettings(input).catch(() => DEFAULT_BLOG_SETTINGS);\n}\n","import {\n fetchPosts,\n type CmsPostContentPage,\n type FetchPostsOptions,\n} from \"@roottale/cms-client/server\";\n\n/**\n * 공개 목록은 CMS가 잠시 응답하지 않아도 페이지 전체를 500으로 만들지 않는다.\n * ISR 재검증 중이라면 Next가 기존 결과를 계속 제공하고, 최초 빌드라면 호출부가\n * 지정한 빈 상태를 렌더한다. 설정·테마·피드 라우트와 같은 fail-soft 계약이다.\n */\nexport async function fetchPostsFailSoft(\n options: FetchPostsOptions,\n): Promise<CmsPostContentPage> {\n try {\n return await fetchPosts(options);\n } catch {\n return { items: [], hasMore: false, nextCursor: null };\n }\n}\n","import type { CmsPostContent } from \"@roottale/cms-client/server\";\nimport {\n resolvePostCollection,\n resolvePostPath,\n type RouteCollection,\n} from \"@roottale/cms-core\";\n\nimport { storedPostPath } from \"../post-public-url.js\";\n\n/**\n * 글 카드·연관 글·빵부스러기가 쓰는 글 링크 — ADR-0105 Amendment 1.\n *\n * 플랫폼이 저장한 정규 공개 경로(`post.path`)가 있으면 그것을 그대로 쓴다. 없으면\n * (구 서버·물질화 전) 지금까지처럼 컬렉션 규칙 또는 `/blog/{slug}` 로 계산한다.\n */\nexport function defaultPostHref(post: CmsPostContent): string {\n return storedPostPath(post) ?? `/blog/${post.slug}`;\n}\n\nexport function defaultAuthorHref(slug: string): string {\n return `/blog/author/${slug}`;\n}\n\nexport function makeCollectionPostHref(\n collections: readonly RouteCollection[],\n): (post: CmsPostContent) => string {\n return (post) => {\n const stored = storedPostPath(post);\n if (stored) return stored;\n\n const path = resolvePostPath(post, collections);\n if (path) return path;\n\n const collection = resolvePostCollection(post, collections);\n if (collection?.detail === false && collection.basePath) {\n return collection.basePath;\n }\n return `/blog/${post.slug}`;\n };\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport {\n fetchTheme,\n type RootTaleTheme,\n} from \"@roottale/cms-client/server\";\n\nimport { themeToCssVars } from \"../theme-css-vars.js\";\n\nexport type RootTaleThemeInput = RootTaleTheme | null | undefined;\n\ntype ResolveThemeStyleInput = {\n readonly themeProp: RootTaleThemeInput;\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\nexport async function resolveThemeStyleFailSoft(\n input: ResolveThemeStyleInput,\n): Promise<CSSProperties | null> {\n const { themeProp, apiKey, baseUrl } = input;\n if (themeProp === null) return null;\n if (themeProp !== undefined) return themeToCssVars(themeProp);\n\n const fetched = await fetchTheme({ apiKey, baseUrl }).catch(() => null);\n return themeToCssVars(fetched);\n}\n\nexport function RootTaleThemeProvider(props: {\n readonly theme: RootTaleTheme | null | undefined;\n readonly children: ReactNode;\n}): ReactElement {\n const style = themeToCssVars(props.theme ?? null);\n if (!style) return <>{props.children}</>;\n\n return (\n <div data-roottale-cms=\"theme-root\" data-rt-theme=\"\" style={style}>\n {props.children}\n </div>\n );\n}\n","import type { ReactElement } from \"react\";\nimport type { RootTaleTheme } from \"@roottale/cms-client/server\";\nimport type { RouteCollection } from \"@roottale/cms-core\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport {\n collectCategoriesWithCounts,\n collectCollectionCategories,\n defaultCategoryHref,\n} from \"../blog-filter.js\";\nimport { fetchPostsFailSoft } from \"./fetch-posts-fail-soft.js\";\nimport { resolveThemeStyleFailSoft } from \"./theme.js\";\n\nexport type RootTaleBlogCategoriesProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n categoryHref?: (slug: string) => string;\n emptyMessage?: string;\n collection?: string;\n collections?: readonly RouteCollection[];\n theme?: RootTaleTheme | null | undefined;\n locale?: string;\n};\n\nexport async function RootTaleBlogCategories(\n props: RootTaleBlogCategoriesProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n baseUrl,\n limit,\n collection,\n collections,\n categoryHref = defaultCategoryHref,\n locale,\n emptyMessage = t(locale, \"blogCategoriesEmpty\"),\n theme: themeProp,\n } = props;\n const [page, themeStyle] = await Promise.all([\n fetchPostsFailSoft({\n apiKey,\n baseUrl,\n limit,\n type: \"post\",\n ...(locale ? { locale } : {}),\n }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n ]);\n const categories =\n collection && collections\n ? collectCollectionCategories(page.items, collections, collection)\n : collectCategoriesWithCounts(page.items);\n\n return (\n <nav\n data-roottale-cms=\"categories\"\n className=\"rt-cms-categories\"\n aria-label=\"블로그 카테고리\"\n style={themeStyle ?? undefined}\n >\n {categories.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-category-list\">\n {categories.map((category) => (\n <li key={category.slug} className=\"rt-cms-category-list-item\">\n <a\n className=\"rt-cms-category-link\"\n href={categoryHref(category.slug)}\n >\n <span className=\"rt-cms-category-name\">{category.name}</span>\n <span className=\"rt-cms-category-count\">{category.count}</span>\n </a>\n </li>\n ))}\n </ul>\n )}\n </nav>\n );\n}\n","import type { CSSProperties, ReactElement } from \"react\";\nimport {\n POST_FOOTER_PATTERN_SLOT,\n fetchPost,\n fetchPostPreview,\n fetchPosts,\n isPreviewExpiredError,\n resolvePostDisplay,\n selectRelatedPosts,\n type CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport { defaultCategoryHref } from \"../blog-filter.js\";\nimport { attachHeadingIds, extractToc } from \"../toc.js\";\nimport { fetchBlogSettingsFailSoft } from \"./blog-settings.js\";\nimport { renderPostPattern, resolvePostPatternFailSoft } from \"./site-patterns.js\";\nimport { renderArchiveRelated, renderFieldFacts } from \"./blog-post-archive.js\";\nimport { BlogPostBody } from \"./blog-post-body.js\";\nimport { renderBlogPostBreadcrumb } from \"./blog-post-breadcrumb.js\";\nimport {\n bodyHasInternalContentLinkTokens,\n fetchInternalContentPathIndex,\n} from \"./internal-links.js\";\nimport {\n renderFaqJsonLd,\n renderPostCta,\n relatedCardsFromPosts,\n renderRelatedPosts,\n type RelatedPostCard,\n} from \"./blog-post-footer.js\";\nimport type { RootTaleBlogPostProps } from \"./blog-post-types.js\";\nimport {\n defaultAuthorHref,\n defaultPostHref,\n makeCollectionPostHref,\n} from \"./post-links.js\";\nimport type { InternalLinksSource } from \"./render-tiptap.js\";\nimport { resolveThemeStyleFailSoft } from \"./theme.js\";\n\ntype FetchRelatedPostsInput = {\n readonly post: CmsPostContent;\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly count: number;\n readonly locale?: string;\n};\n\n/**\n * 관련 글 — 편집자가 어드민 \"관련 콘텐츠\"에서 고른 글(`post.relatedPosts`, 발행 글만·고른 순서)이\n * 있으면 그것을 쓰고, 없을 때만 같은 카테고리 최신 글을 자동으로 고른다. 편집자 선택은\n * `count` 와 무관하게 고른 만큼 보여준다(편집자가 이미 개수를 정했다).\n */\nfunction editorPickedRelatedCards(post: CmsPostContent): RelatedPostCard[] {\n return (post.relatedPosts ?? []).map((item) => ({\n id: item.id,\n title: item.title,\n href: item.path ?? `/blog/${item.slug}`,\n featuredImageUrl: item.featuredImageUrl,\n }));\n}\n\nasync function fetchRelatedPostsFailSoft(\n input: FetchRelatedPostsInput,\n): Promise<readonly CmsPostContent[]> {\n if (input.count <= 0) return [];\n\n const candidates = await fetchPosts({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n type: \"post\",\n limit: 30,\n ...(input.locale ? { locale: input.locale } : {}),\n }).catch(() => null);\n if (!candidates) return [];\n return selectRelatedPosts(input.post, candidates.items, input.count);\n}\n\nfunction authorInitials(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return \"\";\n const parts = trimmed.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) {\n return `${parts[0]?.[0] ?? \"\"}${parts[1]?.[0] ?? \"\"}`.toUpperCase();\n }\n return [...trimmed].slice(0, 2).join(\"\").toUpperCase();\n}\n\nfunction formatPostDate(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(date);\n}\n\nfunction distinctModifiedDate(\n publishedAt: string,\n updatedAt: string,\n): string | null {\n const published = formatPostDate(publishedAt);\n const updated = formatPostDate(updatedAt);\n if (!updated || updated === published) return null;\n return updated;\n}\n\nfunction readEditorialReview(metaJson: unknown): {\n reviewerName: string;\n reviewedAt: string;\n} | null {\n if (!metaJson || typeof metaJson !== \"object\") return null;\n const raw = (metaJson as Record<string, unknown>).editorialReview;\n if (!raw || typeof raw !== \"object\") return null;\n const value = raw as Record<string, unknown>;\n if (\n typeof value.reviewerName !== \"string\" ||\n typeof value.reviewedAt !== \"string\" ||\n !/^\\d{4}-\\d{2}-\\d{2}$/.test(value.reviewedAt)\n ) {\n return null;\n }\n return { reviewerName: value.reviewerName, reviewedAt: value.reviewedAt };\n}\n\ntype PostLookup =\n | { kind: \"post\"; post: CmsPostContent }\n | { kind: \"missing\" }\n | { kind: \"preview-expired\" };\n\n/**\n * 발행 글(slugOrId) 또는 미리보기(previewToken) 조회. 미리보기는 캐시 없이\n * 스냅샷을 받아 발행 글과 같은 모양(CmsPostContent)으로 돌아오므로 이후 렌더\n * 경로는 하나다 — 그래서 \"미리보기 = 결과물\" 이 구조적으로 보장된다.\n */\nasync function lookupPost(input: {\n apiKey: string;\n baseUrl?: string;\n slugOrId?: string;\n previewToken?: string;\n locale?: string;\n}): Promise<PostLookup> {\n if (input.previewToken) {\n try {\n const preview = await fetchPostPreview({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n token: input.previewToken,\n });\n return preview ? { kind: \"post\", post: preview } : { kind: \"missing\" };\n } catch (error) {\n if (isPreviewExpiredError(error)) return { kind: \"preview-expired\" };\n throw error;\n }\n }\n if (!input.slugOrId) return { kind: \"missing\" };\n const post = await fetchPost({\n apiKey: input.apiKey,\n slugOrId: input.slugOrId,\n baseUrl: input.baseUrl,\n ...(input.locale ? { locale: input.locale } : {}),\n });\n return post ? { kind: \"post\", post } : { kind: \"missing\" };\n}\n\nasync function resolveInternalLinksSource(\n source: RootTaleBlogPostProps[\"internalLinks\"],\n input: {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly locale?: string;\n readonly hasTokens: boolean;\n },\n): Promise<InternalLinksSource> {\n if (source === false) return null;\n if (source !== \"auto\" && source !== undefined) return source;\n if (!input.hasTokens) return null;\n return fetchInternalContentPathIndex({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n locale: input.locale,\n });\n}\n\nexport async function RootTaleBlogPost(\n props: RootTaleBlogPostProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n previewToken,\n previewExpiredElement,\n baseUrl,\n notFoundElement,\n showTitle = true,\n showTableOfContents,\n tableOfContentsTitle,\n tocPosition,\n footerPatternPresentation,\n relatedPostsCount = 0,\n locale,\n relatedPostsTitle = t(locale, \"relatedPostsTitle\"),\n collections,\n showFieldFacts = false,\n theme: themeProp,\n authorHref = defaultAuthorHref,\n breadcrumb,\n internalLinks = \"auto\",\n } = props;\n const [lookup, themeStyle, blogSettings] = await Promise.all([\n lookupPost({ apiKey, baseUrl, slugOrId, previewToken, locale }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n fetchBlogSettingsFailSoft({ apiKey, baseUrl }),\n ]);\n\n if (lookup.kind === \"preview-expired\") {\n return (\n previewExpiredElement ?? (\n <div data-roottale-cms=\"post-preview-expired\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"previewExpired\")}</p>\n </div>\n )\n );\n }\n if (lookup.kind === \"missing\") {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"post-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"postNotFound\")}</p>\n </div>\n )\n );\n }\n const post = lookup.post;\n\n const display = resolvePostDisplay(post, blogSettings, {\n showTableOfContents,\n tableOfContentsTitle,\n });\n const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];\n const renderDoc =\n toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;\n const editorPicked = editorPickedRelatedCards(post);\n const [autoRelated, internalLinkIndex, footerPattern] = await Promise.all([\n editorPicked.length > 0\n ? Promise.resolve([] as readonly CmsPostContent[])\n : fetchRelatedPostsFailSoft({\n post,\n apiKey,\n baseUrl,\n count: relatedPostsCount,\n locale,\n }),\n resolveInternalLinksSource(internalLinks, {\n apiKey,\n baseUrl,\n locale,\n hasTokens: bodyHasInternalContentLinkTokens(post.bodyJson, post.bodyHtml),\n }),\n resolvePostPatternFailSoft(post, { apiKey, baseUrl }, POST_FOOTER_PATTERN_SLOT),\n ]);\n const relatedHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n\n const authorName = post.authorName;\n const authorSlug = post.authorSlug;\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const publishedDate = display.showDate ? formatPostDate(post.publishedAt) : \"\";\n const modifiedDate = display.showDate\n ? distinctModifiedDate(post.publishedAt, post.updatedAt)\n : null;\n const showTopAuthor = display.showAuthor && Boolean(authorName);\n const categoryHref = breadcrumb?.categoryHref ?? defaultCategoryHref;\n const categories =\n breadcrumb?.showCategory === false\n ? []\n : post.terms.filter((term) => term.taxonomy === \"category\");\n const authorBio = post.authorBio;\n const authorImageUrl = post.authorImageUrl;\n const authorImageAlt = `${authorName ?? \"\"} 프로필 사진`.trim();\n const authorImageStyle: CSSProperties = {\n objectPosition: `${post.authorImagePositionX ?? display.authorProfileImagePositionX}% ${post.authorImagePositionY ?? display.authorProfileImagePositionY}%`,\n };\n const showAuthorCard = display.showAuthorCard && Boolean(authorName);\n const editorialReview = readEditorialReview(post.metaJson);\n const reviewedDate = editorialReview\n ? formatPostDate(editorialReview.reviewedAt)\n : \"\";\n\n return (\n <article\n data-roottale-cms=\"post\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n {renderBlogPostBreadcrumb({\n post,\n collections,\n postHref: relatedHref,\n options: breadcrumb,\n })}\n <header className=\"rt-cms-post-header\">\n {categories.length > 0 ? (\n <nav className=\"rt-cms-post-categories\" aria-label=\"글 카테고리\">\n {categories.map((category) => (\n <a\n key={category.id}\n className=\"rt-cms-post-category\"\n href={categoryHref(category.slug)}\n >\n {category.name}\n </a>\n ))}\n </nav>\n ) : null}\n {showTitle ? <h1 className=\"rt-cms-title\">{post.title}</h1> : null}\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n {publishedDate ||\n modifiedDate ||\n showTopAuthor ||\n views !== null ||\n reviewedDate ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <time\n dateTime={post.publishedAt ?? undefined}\n className=\"rt-cms-meta-item\"\n data-rt-cms-meta=\"date\"\n >\n 발행 {publishedDate}\n </time>\n ) : null}\n {modifiedDate ? (\n <time\n dateTime={post.updatedAt}\n className=\"rt-cms-meta-item\"\n data-rt-cms-meta=\"modified\"\n >\n 수정 {modifiedDate}\n </time>\n ) : null}\n {showTopAuthor ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자{\" \"}\n {authorSlug ? (\n <a className=\"rt-cms-author-link\" href={authorHref(authorSlug)}>\n {authorName}\n </a>\n ) : (\n authorName\n )}\n </span>\n ) : null}\n {editorialReview && reviewedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"reviewed\">\n 내용 검토 {reviewedDate} · {editorialReview.reviewerName}\n </span>\n ) : null}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n </header>\n {renderArchiveRelated(post.archiveRelated, collections)}\n {showFieldFacts ? renderFieldFacts(post) : null}\n <BlogPostBody\n doc={renderDoc}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n toc={toc}\n tocTitle={display.tocTitle}\n tocPosition={tocPosition ?? display.tocPosition}\n bodyHtml={post.bodyHtml}\n internalLinks={internalLinkIndex}\n />\n {renderPostPattern(footerPattern, POST_FOOTER_PATTERN_SLOT, { apiKey, baseUrl }, footerPatternPresentation)}\n {footerPattern ? null : renderPostCta(blogSettings.postCta)}\n {showAuthorCard && authorName ? (\n <section className=\"rt-cms-author-card\" aria-label=\"작성자\">\n {authorImageUrl ? (\n <img\n className=\"rt-cms-author-card__image\"\n src={authorImageUrl}\n alt={authorImageAlt}\n loading=\"lazy\"\n style={authorImageStyle}\n />\n ) : (\n <div className=\"rt-cms-author-card__avatar\" aria-hidden=\"true\">\n {authorInitials(authorName)}\n </div>\n )}\n <div className=\"rt-cms-author-card__body\">\n <p className=\"rt-cms-author-card__eyebrow\">\n {display.authorCardEyebrow}\n </p>\n <p className=\"rt-cms-author-card__name\">\n {authorSlug ? (\n <a className=\"rt-cms-author-link\" href={authorHref(authorSlug)}>\n {authorName}\n </a>\n ) : (\n authorName\n )}\n </p>\n {authorBio ? (\n <p className=\"rt-cms-author-card__description\">{authorBio}</p>\n ) : null}\n </div>\n </section>\n ) : null}\n {renderRelatedPosts(\n editorPicked.length > 0 ? editorPicked : relatedCardsFromPosts(autoRelated, relatedHref),\n relatedPostsTitle,\n )}\n {renderFaqJsonLd(post.bodyJson)}\n </article>\n );\n}\n","import type { ReactElement } from \"react\";\nimport type { CSSProperties } from \"react\";\nimport {\n POST_FOOTER_PATTERN_SLOT,\n SITE_PATTERNS_CACHE_TAG,\n fetchSitePatterns,\n selectSitePatternForSlot,\n sitePatternPresentationAttributes,\n type CmsPostContent,\n type CmsSitePattern,\n type CmsSitePatternPresentation,\n} from \"@roottale/cms-client/server\";\n\nimport { RenderTiptap } from \"./render-tiptap.js\";\n\ntype FetchInput = {\n readonly apiKey: string;\n readonly baseUrl?: string;\n};\n\n/**\n * 글의 자리에 놓을 공통 블록을 고른다. 글에 놓을 블록이 없으면(`patternSlots` 가 비었거나\n * 구 서버) 목록을 아예 요청하지 않고, 요청 실패는 null 로 낮춘다 — 블록이 없다고 글이\n * 안 열리면 안 된다. 목록은 `rt-site-patterns` tag 로 캐시돼 `theme.updated` 에 지워진다.\n */\nexport async function resolvePostPatternFailSoft(\n post: Pick<CmsPostContent, \"patternSlots\">,\n input: FetchInput,\n slot: string = POST_FOOTER_PATTERN_SLOT,\n): Promise<CmsSitePattern | null> {\n const key = post.patternSlots?.[slot] ?? null;\n if (!key) return null;\n try {\n const patterns = await fetchSitePatterns({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n tags: [SITE_PATTERNS_CACHE_TAG],\n });\n return selectSitePatternForSlot(post.patternSlots, patterns, slot);\n } catch {\n return null;\n }\n}\n\n/** 공통 블록 1개를 글 본문과 같은 Tiptap 렌더러로 그린다. */\nexport function renderPostPattern(\n pattern: CmsSitePattern | null,\n slot: string,\n input: FetchInput,\n presentationOverride?: CmsSitePatternPresentation | null,\n): ReactElement | null {\n if (!pattern) return null;\n const presentationValue = presentationOverride === undefined\n ? pattern.presentation\n : presentationOverride;\n const presentation = presentationValue === null\n ? null\n : sitePatternPresentationAttributes(presentationValue);\n return (\n <aside\n data-roottale-cms=\"post-pattern\"\n data-pattern-slot={slot}\n data-pattern-key={pattern.key}\n {...presentation?.dataset}\n style={presentation?.style as CSSProperties | undefined}\n className=\"rt-cms-post-pattern\"\n aria-label={pattern.name}\n >\n <RenderTiptap doc={pattern.bodyJson} apiKey={input.apiKey} baseUrl={input.baseUrl} />\n </aside>\n );\n}\n\nexport interface RootTalePostPatternProps {\n readonly apiKey: string;\n readonly baseUrl?: string;\n /** `fetchPost`/`fetchPosts` 로 받은 글(또는 `patternSlots` 만 있는 객체). */\n readonly post: Pick<CmsPostContent, \"patternSlots\">;\n /** 자리 key. 기본 글 하단(`post_footer`). */\n readonly slot?: string;\n /** 객체는 CMS 디자인 전체 대체, null은 사이트 CSS 사용, 생략은 기존 CMS 값 유지. */\n readonly presentation?: CmsSitePatternPresentation | null;\n}\n\n/**\n * 자체 글 화면을 가진 사이트가 글 하단 공통 블록만 가져다 쓰는 서버 컴포넌트.\n * `RootTaleBlogPost` 는 이미 본문 아래에 같은 블록을 그리므로 둘을 함께 쓰지 않는다.\n */\nexport async function RootTalePostPattern(\n props: RootTalePatternProps,\n): Promise<ReactElement | null> {\n const slot = props.slot ?? POST_FOOTER_PATTERN_SLOT;\n const pattern = await resolvePostPatternFailSoft(props.post, props, slot);\n return renderPostPattern(pattern, slot, props, props.presentation);\n}\n\ntype RootTalePatternProps = RootTalePostPatternProps;\n","import type { ReactElement } from \"react\";\n\nimport {\n internalContentLinkResolverFrom,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n type CmsBoundContext,\n type SectionRenderContext,\n} from \"../section-render-context.js\";\nimport { RootTaleTableOfContents } from \"../TableOfContents.js\";\nimport { attachHeadingIds, extractToc } from \"../toc.js\";\nimport { RootTaleBlogList } from \"./blog-list.js\";\nimport {\n renderTiptapNode,\n type RenderNodeContext,\n} from \"./tiptap-node-renderer.js\";\nimport { isRecord, parseTiptapNode } from \"./tiptap-types.js\";\n\nexport type InternalLinksSource =\n | InternalContentLinkResolver\n | ReadonlyMap<string, string>\n | null;\n\ntype RenderTiptapProps = {\n readonly doc: Record<string, unknown>;\n readonly fields?: Record<string, unknown> | null;\n readonly apiKey?: string;\n readonly baseUrl?: string;\n /**\n * 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석 — 키 → 공개 경로.\n * `fetchInternalContentPathIndex` 결과(Map)나 함수. 없으면 토큰을 텍스트 그대로 둔다.\n */\n readonly internalLinks?: InternalLinksSource;\n};\n\nfunction isDevelopmentRuntime(): boolean {\n const processValue = Reflect.get(globalThis, \"process\");\n if (!isRecord(processValue)) return true;\n const env = processValue.env;\n if (!isRecord(env)) return true;\n return env.NODE_ENV !== \"production\";\n}\n\nfunction createSectionContextWithBlogListNetworkFetch(\n apiKey: string | undefined,\n baseUrl: string | undefined,\n): SectionRenderContext {\n const cms: CmsBoundContext | null = apiKey ? { apiKey, baseUrl } : null;\n return {\n cms,\n isDev: isDevelopmentRuntime,\n renderBlogList: (props) => <RootTaleBlogList {...props} />,\n };\n}\n\nfunction contentNodes(doc: Record<string, unknown>): readonly unknown[] {\n return Array.isArray(doc.content) ? doc.content : [];\n}\n\nexport function RenderTiptap({\n doc,\n fields,\n apiKey,\n baseUrl,\n internalLinks,\n}: RenderTiptapProps): ReactElement {\n const content = contentNodes(doc);\n const hasTableOfContents = content.some(\n (value) => parseTiptapNode(value)?.type === \"tableOfContents\",\n );\n const entries = hasTableOfContents ? extractToc(doc) : [];\n const renderDoc =\n hasTableOfContents && entries.length > 0\n ? attachHeadingIds(doc, entries)\n : doc;\n const context: RenderNodeContext = {\n fields: fields ?? null,\n section: createSectionContextWithBlogListNetworkFetch(apiKey, baseUrl),\n internalLinks: internalLinks\n ? internalContentLinkResolverFrom(internalLinks)\n : null,\n };\n\n return (\n <>\n {contentNodes(renderDoc).map((value, index) => {\n const node = parseTiptapNode(value);\n if (node?.type !== \"tableOfContents\") {\n return renderTiptapNode(value, index, context);\n }\n const title = node.attrs?.title;\n return (\n <RootTaleTableOfContents\n key={index}\n entries={entries}\n title={typeof title === \"string\" ? title : \"목차\"}\n />\n );\n })}\n </>\n );\n}\n","import { Fragment, type CSSProperties, type ReactElement } from \"react\";\nimport {\n cmsLineHeight,\n cmsParagraphSpacing,\n cmsImageStyle,\n cmsImageWidth,\n bindingSrcValue,\n bindingTextValue,\n boundFieldValue,\n ensureImportedHtmlDocumentRoots,\n normalizeImageAlt,\n renderInternalContentLinksInHtml,\n scopeImportedCss,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n getSectionNodeRenderer,\n type SectionRenderContext,\n} from \"../section-render-context.js\";\nimport { renderEmbedNode } from \"./tiptap-embed-renderer.js\";\nimport { renderTextNode } from \"./tiptap-inline-renderer.js\";\nimport { parseTiptapNode, type TiptapNode } from \"./tiptap-types.js\";\n\ntype FieldsRecord = Record<string, unknown> | null;\n\nexport type RenderNodeContext = {\n readonly fields: FieldsRecord;\n readonly section: SectionRenderContext;\n /** 본문 예약 내부 링크 키 → 공개 경로. 없으면 토큰을 텍스트 그대로 둔다. */\n readonly internalLinks?: InternalContentLinkResolver | null;\n};\n\nfunction withInternalLinksInHtml(\n html: string,\n internalLinks: InternalContentLinkResolver | null,\n): string {\n return internalLinks\n ? renderInternalContentLinksInHtml(html, internalLinks).html\n : html;\n}\n\nfunction boundContentText(\n node: TiptapNode,\n fields: FieldsRecord,\n): string | undefined {\n return bindingTextValue(\n boundFieldValue(node.attrs?.bindings, \"content\", fields),\n );\n}\n\nfunction alignStyle(node: TiptapNode): CSSProperties | undefined {\n const align = node.attrs?.textAlign;\n const lineHeight = cmsLineHeight(node.attrs?.lineHeight);\n const marginBottom = cmsParagraphSpacing(node.attrs?.paragraphSpacing);\n const style: CSSProperties = {\n ...(align === \"left\" || align === \"center\" || align === \"right\" || align === \"justify\" ? { textAlign: align } : {}),\n ...(lineHeight !== null ? { lineHeight } : {}),\n ...(marginBottom !== null ? { marginBottom } : {}),\n };\n return Object.keys(style).length ? style : undefined;\n}\n\nfunction tableCellSpans(node: TiptapNode): {\n readonly colSpan: number | undefined;\n readonly rowSpan: number | undefined;\n} {\n const colspan = Number(node.attrs?.colspan);\n const rowspan = Number(node.attrs?.rowspan);\n return {\n colSpan: Number.isFinite(colspan) && colspan > 1 ? colspan : undefined,\n rowSpan: Number.isFinite(rowspan) && rowspan > 1 ? rowspan : undefined,\n };\n}\n\nexport function renderTiptapNode(\n value: unknown,\n key: number,\n context: RenderNodeContext,\n): ReactElement | string | null {\n const node = parseTiptapNode(value);\n if (!node) return null;\n\n const children = node.content?.map((child, index) =>\n renderTiptapNode(child, index, context),\n );\n\n switch (node.type) {\n case \"paragraph\": {\n const bound = boundContentText(node, context.fields);\n return (\n <p key={key} style={alignStyle(node)}>\n {bound ?? children}\n </p>\n );\n }\n case \"heading\": {\n const rawLevel = Number(node.attrs?.level ?? 2);\n const finiteLevel = Number.isFinite(rawLevel) ? rawLevel : 2;\n const level = Math.min(Math.max(finiteLevel, 2), 4);\n const rawId = node.attrs?.id;\n const id =\n typeof rawId === \"string\" && rawId.length > 0 ? rawId : undefined;\n const style = alignStyle(node);\n const inner = boundContentText(node, context.fields) ?? children;\n if (level === 2) {\n return (\n <h2 key={key} id={id} style={style}>\n {inner}\n </h2>\n );\n }\n if (level === 3) {\n return (\n <h3 key={key} id={id} style={style}>\n {inner}\n </h3>\n );\n }\n return (\n <h4 key={key} id={id} style={style}>\n {inner}\n </h4>\n );\n }\n case \"bulletList\":\n return <ul key={key}>{children}</ul>;\n case \"orderedList\":\n return <ol key={key}>{children}</ol>;\n case \"listItem\":\n return <li key={key}>{children}</li>;\n case \"blockquote\":\n return <blockquote key={key}>{children}</blockquote>;\n case \"codeBlock\":\n return (\n <pre key={key}>\n <code>\n {node.content?.map((child, index) =>\n renderTiptapNode(child, index, { ...context, internalLinks: null }),\n )}\n </code>\n </pre>\n );\n case \"horizontalRule\":\n return <hr key={key} />;\n case \"hardBreak\":\n return <br key={key} />;\n case \"image\": {\n const boundSrc = bindingSrcValue(\n boundFieldValue(node.attrs?.bindings, \"src\", context.fields),\n );\n const src = boundSrc ?? node.attrs?.src;\n if (typeof src !== \"string\" || src.length === 0) return null;\n\n const alt = normalizeImageAlt(node.attrs?.alt);\n const title =\n typeof node.attrs?.title === \"string\" ? node.attrs.title : undefined;\n const width =\n typeof node.attrs?.width === \"number\" && node.attrs.width > 0\n ? node.attrs.width\n : undefined;\n const height =\n typeof node.attrs?.height === \"number\" && node.attrs.height > 0\n ? node.attrs.height\n : undefined;\n const caption = typeof node.attrs?.caption === \"string\" ? node.attrs.caption : \"\";\n const layout = cmsImageStyle(node.attrs ?? {});\n const image = (\n <img\n key={key}\n src={src}\n alt={alt}\n title={title}\n width={width}\n height={height}\n style={caption ? (cmsImageWidth(node.attrs?.displayWidth) ? { width: \"100%\", maxWidth: \"100%\", height: \"auto\" } : undefined) : layout}\n loading=\"lazy\"\n />\n );\n return caption ? <figure key={key} data-cms-image=\"\" style={layout}>{image}<figcaption>{caption}</figcaption></figure> : image;\n }\n case \"importedHtml\": {\n const rawHtml = node.attrs?.html;\n if (typeof rawHtml !== \"string\" || rawHtml.length === 0) return null;\n const scope = typeof node.attrs?.scope === \"string\" ? node.attrs.scope : \"\";\n const assetUrls = Array.isArray(node.attrs?.assetUrls)\n ? node.attrs.assetUrls.filter(\n (value): value is string =>\n typeof value === \"string\" && /^https:\\/\\//i.test(value),\n )\n : [];\n const scopedCss = scopeImportedCss(\n typeof node.attrs?.css === \"string\" ? node.attrs.css : \"\",\n scope,\n assetUrls,\n );\n return (\n <Fragment key={key}>\n {scopedCss ? (\n <style data-rt-imported-style={scope}>{scopedCss}</style>\n ) : null}\n <div\n className=\"rt-imported-html\"\n data-rt-import-scope={scopedCss ? scope : undefined}\n style={{ position: \"relative\", isolation: \"isolate\" }}\n dangerouslySetInnerHTML={{\n __html: withInternalLinksInHtml(\n ensureImportedHtmlDocumentRoots(rawHtml, assetUrls),\n context.internalLinks ?? null,\n ),\n }}\n />\n </Fragment>\n );\n }\n case \"columns\":\n return (\n <div key={key} className=\"rt-columns\">\n {children}\n </div>\n );\n case \"column\":\n return (\n <div key={key} className=\"rt-column\">\n {children}\n </div>\n );\n case \"table\":\n return (\n <div key={key} className=\"rt-table-wrap\">\n <table className=\"rt-table\">\n <tbody>{children}</tbody>\n </table>\n </div>\n );\n case \"tableRow\":\n return <tr key={key}>{children}</tr>;\n case \"tableHeader\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return (\n <th key={key} colSpan={colSpan} rowSpan={rowSpan}>\n {children}\n </th>\n );\n }\n case \"tableCell\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return (\n <td key={key} colSpan={colSpan} rowSpan={rowSpan}>\n {children}\n </td>\n );\n }\n case \"embed\":\n return renderEmbedNode(node, key);\n case \"faq\":\n return (\n <div key={key} data-roottale-cms=\"faq\" className=\"rt-cms-faq\">\n {children}\n </div>\n );\n case \"faqItem\":\n return (\n <details key={key} className=\"rt-cms-faq-item\">\n {children}\n </details>\n );\n case \"faqQuestion\":\n return (\n <summary key={key} className=\"rt-cms-faq-q\">\n {children}\n </summary>\n );\n case \"faqAnswer\":\n return (\n <div key={key} className=\"rt-cms-faq-a\">\n {children}\n </div>\n );\n case \"references\": {\n const rawTitle = node.attrs?.title;\n const title =\n typeof rawTitle === \"string\" && rawTitle.trim()\n ? rawTitle.trim()\n : \"공식 출처\";\n const titleId = `rt-cms-references-${key}`;\n return (\n <section\n key={key}\n data-roottale-cms=\"references\"\n className=\"rt-cms-references\"\n aria-labelledby={titleId}\n >\n <h2 id={titleId} className=\"rt-cms-references__title\">\n {title}\n </h2>\n {children}\n </section>\n );\n }\n case \"tableOfContents\":\n return null;\n case \"section\": {\n // 섹션 템플릿은 templates 계층이 주입한다(ADR-0040 P2 단방향). 주입 전이면\n // 미지 섹션과 같은 fail-soft — dev 경고 후 skip.\n const renderSection = getSectionNodeRenderer();\n if (!renderSection) {\n if (context.section.isDev()) {\n console.warn(\n \"[cms-renderer-next] section renderer가 등록되지 않아 노드를 건너뜁니다. 공개 CMS runtime은 마케팅 section 구현을 포함하지 않습니다.\",\n );\n }\n return null;\n }\n return renderSection(node, key, context.section);\n }\n case \"text\":\n return renderTextNode(node, key, context.internalLinks ?? null);\n default:\n return children ? <span key={key}>{children}</span> : null;\n }\n}\n","import type { ReactElement } from \"react\";\nimport { isAllowedEmbedSrc } from \"@roottale/cms-core\";\n\nimport type { TiptapNode } from \"./tiptap-types.js\";\n\nfunction isSafeFallbackHref(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n const href = value.trim();\n if (href.length === 0) return false;\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return true;\n }\n return /^(https?:|mailto:|tel:)/i.test(href);\n}\n\nexport function renderEmbedNode(\n node: TiptapNode,\n key: number,\n): ReactElement | null {\n const src = node.attrs?.src;\n if (isAllowedEmbedSrc(src)) {\n const provider =\n typeof node.attrs?.provider === \"string\"\n ? node.attrs.provider\n : undefined;\n return (\n <div\n key={key}\n className=\"rt-embed\"\n data-provider={provider}\n style={{ aspectRatio: \"16 / 9\" }}\n >\n <iframe\n src={src}\n title=\"임베드된 영상\"\n loading=\"lazy\"\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n allow=\"accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share\"\n style={{ width: \"100%\", height: \"100%\", border: 0 }}\n />\n </div>\n );\n }\n\n const originalUrl = node.attrs?.originalUrl;\n if (!isSafeFallbackHref(originalUrl)) return null;\n return (\n <p key={key} className=\"rt-embed-fallback\">\n <a href={originalUrl} rel=\"noopener noreferrer\" target=\"_blank\">\n {originalUrl}\n </a>\n </p>\n );\n}\n","import type { ReactElement, ReactNode } from \"react\";\n\nimport {\n cmsFontSize,\n findInternalContentLinkTokens,\n INTERNAL_CONTENT_LINK_CLASS,\n INTERNAL_CONTENT_LINK_PENDING_ATTR,\n INTERNAL_CONTENT_LINK_PENDING_CLASS,\n type InternalContentLinkResolver,\n} from \"@roottale/cms-core\";\n\nimport {\n parseTiptapMark,\n type TiptapMark,\n type TiptapNode,\n} from \"./tiptap-types.js\";\n\nfunction isSafeHref(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n const href = value.trim();\n if (href.length === 0) return false;\n if (href.startsWith(\"/\") || href.startsWith(\"#\") || href.startsWith(\"?\")) {\n return true;\n }\n return /^(https?:|mailto:|tel:)/i.test(href);\n}\n\nfunction applyMark(\n mark: TiptapMark,\n child: ReactElement | string,\n): ReactElement {\n switch (mark.type) {\n case \"bold\":\n return <strong>{child}</strong>;\n case \"italic\":\n return <em>{child}</em>;\n case \"underline\":\n return <u>{child}</u>;\n case \"strike\":\n return <s>{child}</s>;\n case \"code\":\n return <code>{child}</code>;\n case \"highlight\": {\n const color = mark.attrs?.color;\n const style =\n typeof color === \"string\" && color.length > 0\n ? { background: color }\n : undefined;\n return <mark style={style}>{child}</mark>;\n }\n case \"textStyle\": {\n const color = mark.attrs?.color;\n const fontSize = cmsFontSize(mark.attrs?.fontSize);\n const style = {\n ...(typeof color === \"string\" && color ? { color } : {}),\n ...(fontSize ? { fontSize } : {}),\n };\n return Object.keys(style).length ? <span style={style}>{child}</span> : <>{child}</>;\n }\n case \"link\": {\n const rawHref = mark.attrs?.href;\n const href = isSafeHref(rawHref) ? rawHref : \"#\";\n return (\n <a href={href} rel=\"noopener noreferrer\" target=\"_blank\">\n {child}\n </a>\n );\n }\n default:\n return <span>{child}</span>;\n }\n}\n\nfunction isSafeInternalPath(path: string | null): path is string {\n return typeof path === \"string\" && path.startsWith(\"/\") && !path.startsWith(\"//\");\n}\n\n/**\n * 본문 예약 내부 링크 토큰(`[[internal:키|문구]]`)을 링크 또는 문구로 바꾼다.\n * 토큰이 없으면 문자열 그대로. 대상이 없는 토큰은 문구만 남기되 data 속성으로\n * 키를 남겨 배포 QA 에서 찾을 수 있게 한다.\n */\nfunction renderInternalLinkText(\n text: string,\n resolve: InternalContentLinkResolver,\n): ReactNode {\n const tokens = findInternalContentLinkTokens(text);\n if (tokens.length === 0) return text;\n const parts: ReactNode[] = [];\n let cursor = 0;\n tokens.forEach((token, index) => {\n if (token.index > cursor) parts.push(text.slice(cursor, token.index));\n const path = resolve(token.key);\n parts.push(\n isSafeInternalPath(path) ? (\n <a key={index} href={path} className={INTERNAL_CONTENT_LINK_CLASS}>\n {token.label}\n </a>\n ) : (\n <span\n key={index}\n className={`${INTERNAL_CONTENT_LINK_CLASS} ${INTERNAL_CONTENT_LINK_PENDING_CLASS}`}\n {...{ [INTERNAL_CONTENT_LINK_PENDING_ATTR]: token.key }}\n >\n {token.label}\n </span>\n ),\n );\n cursor = token.index + token.raw.length;\n });\n if (cursor < text.length) parts.push(text.slice(cursor));\n return <>{parts}</>;\n}\n\nexport function renderTextNode(\n node: TiptapNode,\n key: number,\n internalLinks: InternalContentLinkResolver | null = null,\n): ReactElement | string {\n const text = node.text ?? \"\";\n const marks = (node.marks ?? []).flatMap((value) => {\n const mark = parseTiptapMark(value);\n return mark ? [mark] : [];\n });\n // 코드·기존 링크 안의 토큰은 렌더러 계약대로 손대지 않는다.\n const canLink =\n internalLinks !== null &&\n !marks.some((mark) => mark.type === \"code\" || mark.type === \"link\");\n const content: ReactNode =\n canLink && internalLinks ? renderInternalLinkText(text, internalLinks) : text;\n if (marks.length === 0) {\n return typeof content === \"string\" ? content : <span key={key}>{content}</span>;\n }\n\n let element: ReactElement | string =\n typeof content === \"string\" ? content : <>{content}</>;\n for (const mark of marks) {\n element = applyMark(mark, element);\n }\n return <span key={key}>{element}</span>;\n}\n","export type TiptapMark = {\n readonly type?: string;\n readonly attrs?: Record<string, unknown>;\n};\n\nexport type TiptapNode = {\n readonly type?: string;\n readonly text?: string;\n readonly attrs?: Record<string, unknown>;\n readonly content?: readonly unknown[];\n readonly marks?: readonly unknown[];\n};\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function parseTiptapNode(value: unknown): TiptapNode | null {\n if (!isRecord(value)) return null;\n return {\n type: typeof value.type === \"string\" ? value.type : undefined,\n text: typeof value.text === \"string\" ? value.text : undefined,\n attrs: isRecord(value.attrs) ? value.attrs : undefined,\n content: Array.isArray(value.content) ? value.content : undefined,\n marks: Array.isArray(value.marks) ? value.marks : undefined,\n };\n}\n\nexport function parseTiptapMark(value: unknown): TiptapMark | null {\n if (!isRecord(value)) return null;\n return {\n type: typeof value.type === \"string\" ? value.type : undefined,\n attrs: isRecord(value.attrs) ? value.attrs : undefined,\n };\n}\n","import type { ReactElement } from \"react\";\nimport type {\n CmsArchiveRelatedGroup,\n CmsFieldMetaEntry,\n CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport type { RouteCollection } from \"@roottale/cms-core\";\n\nconst FIELD_FACT_TYPES = new Set([\n \"text\",\n \"textarea\",\n \"email\",\n \"url\",\n \"select\",\n \"radio\",\n \"button_group\",\n \"number\",\n \"range\",\n]);\n\nfunction archiveGroupTitle(group: CmsArchiveRelatedGroup): string {\n if (group.collectionKey === \"conditions\") {\n return `이 증상과 관련된 질환 ${group.items.length}`;\n }\n if (group.collectionKey === \"treatments\") {\n return `관련 치료법 ${group.items.length}`;\n }\n if (group.collectionKey === \"symptoms\") {\n return `관련 증상 ${group.items.length}`;\n }\n return `${group.label} ${group.items.length}`;\n}\n\nfunction defaultArchiveBasePath(collectionKey: string): string {\n if (collectionKey === \"symptoms\") return \"/symptoms\";\n if (collectionKey === \"conditions\") return \"/conditions\";\n if (collectionKey === \"treatments\") return \"/treatments\";\n return \"/blog\";\n}\n\nfunction archivePostHref(\n collectionKey: string,\n slug: string,\n collections: readonly RouteCollection[] | undefined,\n): string {\n const collection = collections?.find((item) => item.key === collectionKey);\n const basePath =\n collection?.basePath || defaultArchiveBasePath(collectionKey);\n const trimmedBase = basePath.replace(/\\/+$/, \"\");\n if (collection?.detail === false) return trimmedBase;\n return `${trimmedBase}/${slug}`;\n}\n\nexport function renderArchiveRelated(\n groups: readonly CmsArchiveRelatedGroup[],\n collections: readonly RouteCollection[] | undefined,\n): ReactElement | null {\n const visibleGroups = groups.filter((group) => group.items.length > 0);\n if (visibleGroups.length === 0) return null;\n\n return (\n <aside className=\"rt-archive-related\" aria-label=\"연관 정보\">\n {visibleGroups.map((group) => (\n <section key={group.collectionKey} className=\"rt-archive-related__group\">\n <h2 className=\"rt-archive-related__title\">\n {archiveGroupTitle(group)}\n </h2>\n <ul className=\"rt-archive-related__list\">\n {group.items.map((item) => (\n <li key={item.id} className=\"rt-archive-related__item\">\n <a\n className=\"rt-archive-related__link\"\n href={archivePostHref(\n item.collectionKey,\n item.slug,\n collections,\n )}\n >\n <span className=\"rt-archive-related__item-title\">\n {item.title}\n </span>\n {item.excerpt ? (\n <span className=\"rt-archive-related__item-excerpt\">\n {item.excerpt}\n </span>\n ) : null}\n </a>\n </li>\n ))}\n </ul>\n </section>\n ))}\n </aside>\n );\n}\n\nfunction fieldFactValue(\n raw: unknown,\n meta: CmsFieldMetaEntry,\n): string | null {\n if (raw === null || raw === undefined) return null;\n if (typeof raw === \"number\") return raw.toLocaleString(\"ko-KR\");\n if (typeof raw !== \"string\" || raw.trim() === \"\") return null;\n if (!meta.choices) return raw;\n\n const choice = meta.choices.find((item) => item.value === raw);\n return choice ? choice.label : raw;\n}\n\nexport function renderFieldFacts(\n post: CmsPostContent,\n): ReactElement | null {\n if (!post.fields) return null;\n\n const rows = post.fieldsMeta\n .filter((meta) => FIELD_FACT_TYPES.has(meta.type))\n .flatMap((meta) => {\n const value = fieldFactValue(post.fields?.[meta.name], meta);\n return value === null ? [] : [{ meta, value }];\n });\n if (rows.length === 0) return null;\n\n return (\n <aside className=\"rt-archive-facts\" aria-label=\"상세 정보\">\n <dl className=\"rt-archive-facts__list\">\n {rows.map(({ meta, value }) => (\n <div key={meta.name} className=\"rt-archive-facts__row\">\n <dt className=\"rt-archive-facts__label\">{meta.label}</dt>\n <dd\n className={\n meta.type === \"textarea\"\n ? \"rt-archive-facts__value rt-archive-facts__value--multiline\"\n : \"rt-archive-facts__value\"\n }\n >\n {value}\n </dd>\n </div>\n ))}\n </dl>\n </aside>\n );\n}\n","import type { ReactElement } from \"react\";\nimport {\n internalContentLinkResolverFrom,\n renderInternalContentLinksInHtml,\n} from \"@roottale/cms-core\";\n\nimport { RootTaleTableOfContents } from \"../TableOfContents.js\";\nimport type { TocEntry } from \"../toc.js\";\nimport { resolvePostBodySource } from \"./post-body-source.js\";\nimport { RenderTiptap, type InternalLinksSource } from \"./render-tiptap.js\";\n\ntype BlogPostBodyProps = {\n readonly doc: Record<string, unknown>;\n readonly fields: Record<string, unknown> | null;\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly toc: TocEntry[];\n readonly tocTitle: string;\n readonly tocPosition: string;\n /** 원문 HTML 정본. 있으면 sanitize 후 이것을 렌더하고, 없으면 doc 으로 폴백. */\n readonly bodyHtml?: string | null;\n /** 본문 예약 내부 링크 키 → 공개 경로. 없으면 토큰을 텍스트 그대로 둔다. */\n readonly internalLinks?: InternalLinksSource;\n};\n\nexport function BlogPostBody(props: BlogPostBodyProps): ReactElement {\n const {\n doc,\n fields,\n apiKey,\n baseUrl,\n toc,\n tocTitle,\n tocPosition,\n bodyHtml,\n internalLinks = null,\n } = props;\n const tableOfContents =\n toc.length > 0 ? (\n <RootTaleTableOfContents entries={toc} title={tocTitle} />\n ) : null;\n const source = resolvePostBodySource(bodyHtml);\n // 원문 HTML 정본도 토큰을 링크로 — 텍스트 구간만, <a>·<code>·<pre> 안은 그대로.\n const sourceHtml =\n source.kind === \"html\" && internalLinks\n ? renderInternalContentLinksInHtml(\n source.html,\n internalContentLinkResolverFrom(internalLinks),\n ).html\n : source.kind === \"html\"\n ? source.html\n : \"\";\n const body = (\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n {source.kind === \"html\" ? (\n // source.html 은 resolvePostBodySource 가 공용 sanitizeHtml 로 정제한 값이다\n // (이 컴포넌트에서 한 번 더 정제하지 않는 이유 — sink 가 한 곳이어야 눈으로\n // 추적 가능하고, 이중 정제는 안전을 더 만들지 않으면서 비용만 든다).\n <div dangerouslySetInnerHTML={{ __html: sourceHtml }} />\n ) : (\n <RenderTiptap\n doc={doc}\n fields={fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n internalLinks={internalLinks}\n />\n )}\n </div>\n );\n\n if (tocPosition === \"sidebar\" && tableOfContents) {\n return (\n <div className=\"rt-cms-layout rt-cms-layout--toc-side\">\n {body}\n <aside className=\"rt-cms-toc-side\">{tableOfContents}</aside>\n </div>\n );\n }\n return (\n <>\n {tableOfContents}\n {body}\n </>\n );\n}\n","/**\n * 본문 렌더 소스 결정 — HTML 정본 우선, 없으면 Block JSON 폴백.\n *\n * 배경: 외부에서 이관한 글은 **HTML 이 정본**이고 body_json 은 그 파생/대체본이다.\n * 파생본은 표·중첩 목록 같은 구조에서 원문과 미묘하게 달라질 수 있으므로, 원문이\n * 남아 있으면 그것을 보여주는 편이 정확하다. 반대로 에디터에서 편집된 글은\n * body_html 이 비워지므로(저장 경로가 무효화) 자동으로 JSON 경로로 돌아온다.\n *\n * 안전성: HTML 은 신뢰하지 않는다. 최종 sink 는 공용 `sanitizeHtml` 하나뿐이며\n * (cms-core), 렌더 직전 여기서 통과시킨다 — 저장 시점에 정제됐더라도 다시 한다\n * (저장 경로가 여럿이고 sanitizer 규칙은 갱신되므로, 방어는 렌더 시점 기준).\n *\n * sanitize 결과가 실질적으로 비면(태그만 남고 내용이 없음) HTML 을 쓰지 않고\n * JSON 으로 폴백한다 — 빈 화면보다 파생본이 낫다.\n */\n\nimport { sanitizeHtml } from \"@roottale/cms-core\";\n\nexport type PostBodySource =\n | { readonly kind: \"html\"; readonly html: string }\n | { readonly kind: \"json\" };\n\n/** 태그를 걷어낸 뒤 실제 보이는 내용이 있는지 (이미지 등 void 요소도 내용으로 침). */\nfunction hasVisibleContent(html: string): boolean {\n if (/<(?:img|hr|br|iframe|video|audio)\\b/i.test(html)) return true;\n return html.replace(/<[^>]*>/g, \"\").replace(/ /gi, \" \").trim().length > 0;\n}\n\n/**\n * 렌더에 쓸 본문 소스를 고른다.\n *\n * @param bodyHtml 원문 HTML 정본 (null/미포함 = 구 서버이거나 JSON 이 정본).\n * @returns `kind: \"html\"` 이면 `html` 은 **이미 sanitize 된** 값이라 그대로 주입 가능.\n */\nexport function resolvePostBodySource(\n bodyHtml: string | null | undefined,\n): PostBodySource {\n if (typeof bodyHtml !== \"string\") return { kind: \"json\" };\n const trimmed = bodyHtml.trim();\n if (trimmed.length === 0) return { kind: \"json\" };\n\n const sanitized = sanitizeHtml(trimmed);\n if (!hasVisibleContent(sanitized)) return { kind: \"json\" };\n\n return { kind: \"html\", html: sanitized };\n}\n","import type { ReactElement } from \"react\";\nimport {\n breadcrumbSchema,\n jsonLdJson,\n type CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport {\n resolvePostCollection,\n type RouteCollection,\n} from \"@roottale/cms-core\";\n\nimport { defaultCategoryHref } from \"../blog-filter.js\";\nimport { trimSlash } from \"../routes-shared.js\";\nimport type { BlogPostBreadcrumbOptions } from \"./blog-post-types.js\";\n\ntype RenderBlogPostBreadcrumbInput = {\n readonly post: CmsPostContent;\n readonly collections: readonly RouteCollection[] | undefined;\n readonly postHref: (post: CmsPostContent) => string;\n readonly options: BlogPostBreadcrumbOptions | undefined;\n};\n\nfunction absoluteUrl(siteUrl: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return `${trimSlash(siteUrl)}${normalizedPath}`;\n}\n\nexport function renderBlogPostBreadcrumb(\n input: RenderBlogPostBreadcrumbInput,\n): ReactElement | null {\n const { post, collections, postHref, options } = input;\n if (!options) return null;\n\n const {\n siteUrl,\n homeLabel = \"홈\",\n homeHref = \"/\",\n listLabel = \"블로그\",\n showCategory = true,\n categoryHref = defaultCategoryHref,\n } = options;\n const listHref =\n options.listHref ??\n (collections\n ? resolvePostCollection(post, collections)?.basePath || \"/blog\"\n : \"/blog\");\n const categoryTerm = showCategory\n ? post.terms.find((term) => term.taxonomy === \"category\")\n : undefined;\n const crumbs: { label: string; href: string }[] = [\n { label: homeLabel, href: homeHref },\n { label: listLabel, href: listHref },\n ];\n if (categoryTerm) {\n crumbs.push({\n label: categoryTerm.name,\n href: categoryHref(categoryTerm.slug),\n });\n }\n crumbs.push({ label: post.title, href: postHref(post) });\n\n const jsonLd = siteUrl\n ? breadcrumbSchema(\n crumbs.map((crumb) => ({\n name: crumb.label,\n url: absoluteUrl(siteUrl, crumb.href),\n })),\n )\n : null;\n\n return (\n <>\n <nav aria-label=\"브레드크럼\" className=\"rt-cms-breadcrumb\">\n <ol>\n {crumbs.map((crumb, index) =>\n index === crumbs.length - 1 ? (\n <li key={index} aria-current=\"page\">\n {crumb.label}\n </li>\n ) : (\n <li key={index}>\n <a href={crumb.href}>{crumb.label}</a>\n </li>\n ),\n )}\n </ol>\n </nav>\n {jsonLd ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLdJson(jsonLd) }}\n />\n ) : null}\n </>\n );\n}\n","// 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석 색인.\n//\n// 키 → 현재 공개 경로. 발행 글의 `path`(플랫폼이 저장한 정규 경로)와 `previousSlugs`\n// 로 만든다. FRONT 는 이 색인을 RenderTiptap / RootTaleBlogPost 의 `internalLinks`\n// 로 넘기면 되고, RootTaleBlogPost 는 본문에 토큰이 있을 때만 스스로 만든다.\n\nimport { internalContentPathIndex } from \"@roottale/cms-core\";\nimport { fetchPosts } from \"@roottale/cms-client/server\";\n\nexport interface FetchInternalContentPathIndexOptions {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly locale?: string;\n /** ISR revalidate 초. */\n readonly revalidate?: number;\n /** 안전장치 — 이 이상 페이지는 읽지 않는다(100건/페이지). 기본 20 = 2,000건. */\n readonly maxPages?: number;\n}\n\n/**\n * 발행 글 전부의 경로를 모아 키 색인을 만든다. 한 페이지라도 실패하면 그때까지\n * 모은 것으로 답한다(fail-soft — 링크가 몇 개 문구로 남는 편이 500 보다 낫다).\n */\nexport async function fetchInternalContentPathIndex(\n options: FetchInternalContentPathIndexOptions,\n): Promise<Map<string, string>> {\n const entries: { path?: string | null; previousSlugs?: string[] }[] = [];\n const maxPages = options.maxPages ?? 20;\n let cursor: string | undefined;\n for (let page = 0; page < maxPages; page += 1) {\n try {\n const result = await fetchPosts({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n type: \"post\",\n limit: 100,\n cursor,\n ...(options.locale ? { locale: options.locale } : {}),\n ...(options.revalidate !== undefined\n ? { revalidate: options.revalidate }\n : {}),\n });\n for (const post of result.items) {\n entries.push({ path: post.path, previousSlugs: post.previousSlugs });\n }\n if (!result.hasMore || !result.nextCursor) break;\n cursor = result.nextCursor;\n } catch {\n break;\n }\n }\n return internalContentPathIndex(entries);\n}\n\n/** 본문(JSON 또는 HTML)에 토큰이 있는지 — 색인을 만들 필요가 있는지 미리 본다. */\nexport function bodyHasInternalContentLinkTokens(\n doc: Record<string, unknown> | null | undefined,\n html?: string | null,\n): boolean {\n if (html && html.includes(\"[[internal:\")) return true;\n if (!doc) return false;\n try {\n return JSON.stringify(doc).includes(\"[[internal:\");\n } catch {\n return false;\n }\n}\n","import type { ReactElement } from \"react\";\nimport {\n jsonLdJson,\n type CmsPostContent,\n type RootTaleBlogSettings,\n} from \"@roottale/cms-client/server\";\nimport { extractFaqEntries, faqPageJsonLd } from \"@roottale/cms-core\";\n\n/** 공통 블록으로 옮기기 전 사이트의 CTA를 안전한 링크일 때만 렌더한다. */\nexport function renderPostCta(\n cta: RootTaleBlogSettings[\"postCta\"],\n): ReactElement | null {\n if (!cta) return null;\n\n const href = cta.buttonHref.trim();\n const isSafeHref =\n href.startsWith(\"/\") ||\n href.startsWith(\"#\") ||\n /^(https?:|mailto:|tel:)/i.test(href);\n if (!isSafeHref) return null;\n\n const isExternal = /^https?:/i.test(href);\n return (\n <aside data-roottale-cms=\"post-cta\" className=\"rt-cms-post-cta\">\n {cta.title ? <p className=\"rt-cms-post-cta__title\">{cta.title}</p> : null}\n {cta.description ? (\n <p className=\"rt-cms-post-cta__desc\">{cta.description}</p>\n ) : null}\n <a\n className=\"rt-cms-post-cta__btn\"\n href={href}\n target={isExternal ? \"_blank\" : undefined}\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n {cta.buttonLabel}\n </a>\n </aside>\n );\n}\n\n/** 관련 글 카드 1장에 필요한 것 — 편집자 선택(`relatedPosts`)과 자동 추천이 같은 모양으로 들어온다. */\nexport type RelatedPostCard = {\n readonly id: string;\n readonly title: string;\n readonly href: string;\n readonly featuredImageUrl: string | null;\n};\n\nexport function relatedCardsFromPosts(\n posts: readonly CmsPostContent[],\n href: (post: CmsPostContent) => string,\n): RelatedPostCard[] {\n return posts.map((post) => ({\n id: post.id,\n title: post.title,\n href: href(post),\n featuredImageUrl: post.featuredImageUrl,\n }));\n}\n\nexport function renderRelatedPosts(\n posts: readonly RelatedPostCard[],\n title: string,\n): ReactElement | null {\n if (posts.length === 0) return null;\n\n return (\n <nav data-roottale-cms=\"related\" className=\"rt-cms-related\" aria-label={title}>\n <h2 className=\"rt-cms-related-title\">{title}</h2>\n <ul className=\"rt-cms-related-list\">\n {posts.map((post) => (\n <li key={post.id} className=\"rt-cms-related-item\">\n <a className=\"rt-cms-related-link\" href={post.href}>\n {post.featuredImageUrl ? (\n <img\n className=\"rt-cms-related-thumb\"\n src={post.featuredImageUrl}\n alt=\"\"\n loading=\"lazy\"\n />\n ) : null}\n <span className=\"rt-cms-related-post-title\">{post.title}</span>\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\nfunction renderFaqJsonLd(doc: unknown): ReactElement | null {\n const jsonLd = faqPageJsonLd(extractFaqEntries(doc));\n if (!jsonLd) return null;\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLdJson(jsonLd) }}\n />\n );\n}\n\nexport function RootTaleFaqJsonLd({\n doc,\n}: {\n readonly doc: unknown;\n}): ReactElement | null {\n return renderFaqJsonLd(doc);\n}\n\nexport { renderFaqJsonLd };\n","import type { ReactElement } from \"react\";\nimport { t } from \"@roottale/cms-i18n\";\n\nexport interface RootTalePreviewNoticeProps {\n /** `ko`(기본) · `en`. */\n locale?: string;\n /** 기본 안내 문구를 바꾸고 싶을 때. */\n message?: string;\n /** 토큰 만료 시각(ISO) — 주면 \"HH:MM까지\" 를 덧붙인다. */\n expiresAt?: string;\n}\n\nfunction formatExpiry(locale: string | undefined, iso: string): string | null {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return null;\n const time = new Intl.DateTimeFormat(locale === \"en\" ? \"en-US\" : \"ko-KR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n }).format(date);\n return locale === \"en\" ? `Valid until ${time}.` : `${time}까지 볼 수 있어요.`;\n}\n\n/**\n * 미리보기 화면 상단 안내 띠. 본문 템플릿은 발행 화면과 완전히 같게 두고, \"이건\n * 미리보기다\" 라는 신호만 이 띠 하나로 준다. 검색엔진 색인 차단은 metadata\n * (`buildPreviewMetadata`) 몫이며, 이 컴포넌트는 사람용 안내다.\n */\nexport function RootTalePreviewNotice({\n locale,\n message,\n expiresAt,\n}: RootTalePreviewNoticeProps): ReactElement {\n const expiry = expiresAt ? formatExpiry(locale, expiresAt) : null;\n return (\n <div data-roottale-cms=\"preview-notice\" className=\"rt-cms-preview-notice\" role=\"status\">\n <span>{message ?? t(locale, \"previewNotice\")}</span>\n {expiry ? <span className=\"rt-cms-preview-notice__expiry\">{expiry}</span> : null}\n </div>\n );\n}\n","import type { ReactElement } from \"react\";\nimport { fetchPost } from \"@roottale/cms-client/server\";\nimport { t } from \"@roottale/cms-i18n\";\n\nimport { resolvePostBodySource } from \"./post-body-source.js\";\nimport { RenderTiptap } from \"./render-tiptap.js\";\nimport {\n resolveThemeStyleFailSoft,\n type RootTaleThemeInput,\n} from \"./theme.js\";\n\nexport type RootTalePageProps = {\n apiKey: string;\n slugOrId: string;\n baseUrl?: string;\n notFoundElement?: ReactElement;\n showTitle?: boolean;\n theme?: RootTaleThemeInput;\n locale?: string;\n /** 페이지 JSON을 렌더하기 직전 콘텐츠 참조를 실제 섹션 props로 바꾸는 서버 함수. */\n prepareBody?: (\n document: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n};\n\nexport async function RootTalePage(\n props: RootTalePageProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n baseUrl,\n notFoundElement,\n showTitle = true,\n theme: themeProp,\n locale,\n prepareBody,\n } = props;\n const [post, themeStyle] = await Promise.all([\n fetchPost({ apiKey, slugOrId, baseUrl, ...(locale ? { locale } : {}) }),\n resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),\n ]);\n\n if (!post) {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"page-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{t(locale, \"pageNotFound\")}</p>\n </div>\n )\n );\n }\n\n const pageBodySource = resolvePostBodySource(post.bodyHtml);\n const bodyJson =\n pageBodySource.kind === \"json\" && prepareBody\n ? await prepareBody(post.bodyJson)\n : post.bodyJson;\n\n return (\n <article\n data-roottale-cms=\"page\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n {showTitle ? (\n <header className=\"rt-cms-post-header\">\n <h1 className=\"rt-cms-title\">{post.title}</h1>\n </header>\n ) : null}\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n {/* 글과 동일 — HTML 정본이 있으면 sanitize 후 우선, 없으면 Block JSON 폴백. */}\n {pageBodySource.kind === \"html\" ? (\n <div dangerouslySetInnerHTML={{ __html: pageBodySource.html }} />\n ) : (\n <RenderTiptap\n doc={bodyJson}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n />\n )}\n </div>\n </article>\n );\n}\n","import { fetchExposure } from \"@roottale/cms-client/server\";\n\nimport { RootTaleExposure } from \"../exposure-client.js\";\n\nexport interface RootTaleExposureSlotProps {\n readonly apiKey: string;\n readonly baseUrl?: string;\n readonly siteId?: string;\n readonly slotKey: string;\n readonly path: string;\n readonly allowedVariants: readonly string[];\n readonly revalidate?: number;\n}\n\n/** FRONT가 코드로 만든 슬롯 안에서만 활성 배너·팝업을 렌더한다. */\nexport async function RootTaleExposureSlot(props: RootTaleExposureSlotProps) {\n const campaign = await fetchExposure({\n apiKey: props.apiKey,\n baseUrl: props.baseUrl,\n siteId: props.siteId,\n slotKey: props.slotKey,\n path: props.path,\n revalidate: props.revalidate,\n }).catch(() => null);\n if (!campaign || !props.allowedVariants.includes(campaign.content.variant)) return null;\n return <RootTaleExposure campaign={campaign} allowedVariants={props.allowedVariants} />;\n}\n"],"mappings":"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","names":["t","jsx","jsxs","themeToCssVars","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","themeToCssVars","jsx","jsxs","jsx","jsxs","POST_FOOTER_PATTERN_SLOT","fetchPosts","resolvePostDisplay","Fragment","jsx","Fragment","jsx","jsx","jsxs","value","Fragment","Fragment","jsx","jsx","jsx","jsxs","internalContentLinkResolverFrom","renderInternalContentLinksInHtml","sanitizeHtml","Fragment","jsx","jsxs","renderInternalContentLinksInHtml","internalContentLinkResolverFrom","resolvePostCollection","Fragment","jsx","jsxs","resolvePostCollection","fetchPosts","jsonLdJson","jsx","jsxs","isSafeHref","jsx","jsxs","fetchPosts","resolvePostDisplay","POST_FOOTER_PATTERN_SLOT","jsx","jsxs","fetchPost","jsx","jsxs","fetchPost","jsx"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roottale/cms-renderer-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"CHANGELOG.md"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@roottale/cms-client": "^0.
|
|
57
|
-
"@roottale/cms-core": "^0.
|
|
56
|
+
"@roottale/cms-client": "^0.63.0",
|
|
57
|
+
"@roottale/cms-core": "^0.63.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^19.0.0",
|