@roottale/cms-renderer-next 0.51.1 → 0.53.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +54 -4
  3. package/dist/{chunk-U2IM6OCQ.js → chunk-HM5VP7SR.js} +1 -1
  4. package/dist/{chunk-U2IM6OCQ.js.map → chunk-HM5VP7SR.js.map} +1 -1
  5. package/dist/chunk-KSPPYX22.js +14 -0
  6. package/dist/chunk-KSPPYX22.js.map +1 -0
  7. package/dist/cms-public.css +46 -0
  8. package/dist/routes.d.ts +25 -1
  9. package/dist/routes.js +72 -14
  10. package/dist/routes.js.map +1 -1
  11. package/dist/section-runtime.d.ts +46 -0
  12. package/dist/section-runtime.js +9 -0
  13. package/dist/server.d.ts +5 -3
  14. package/dist/server.js +1679 -34
  15. package/dist/server.js.map +1 -1
  16. package/package.json +7 -23
  17. package/dist/before-after-client.d.ts +0 -15
  18. package/dist/before-after-client.js +0 -8
  19. package/dist/chunk-27RT6B3O.js +0 -93
  20. package/dist/chunk-27RT6B3O.js.map +0 -1
  21. package/dist/chunk-2QU4JU46.js +0 -3927
  22. package/dist/chunk-2QU4JU46.js.map +0 -1
  23. package/dist/chunk-4RQ3SEDL.js +0 -106
  24. package/dist/chunk-4RQ3SEDL.js.map +0 -1
  25. package/dist/chunk-6GQCFFHU.js +0 -540
  26. package/dist/chunk-6GQCFFHU.js.map +0 -1
  27. package/dist/chunk-H46JUIHZ.js +0 -79
  28. package/dist/chunk-H46JUIHZ.js.map +0 -1
  29. package/dist/chunk-K62RR4UQ.js +0 -53
  30. package/dist/chunk-K62RR4UQ.js.map +0 -1
  31. package/dist/chunk-LEHHSI6G.js +0 -64
  32. package/dist/chunk-LEHHSI6G.js.map +0 -1
  33. package/dist/chunk-QYNEOFL7.js +0 -418
  34. package/dist/chunk-QYNEOFL7.js.map +0 -1
  35. package/dist/chunk-U5AKACPN.js +0 -113
  36. package/dist/chunk-U5AKACPN.js.map +0 -1
  37. package/dist/chunk-UM37DOIK.js +0 -961
  38. package/dist/chunk-UM37DOIK.js.map +0 -1
  39. package/dist/chunk-YLSQAT6B.js +0 -59
  40. package/dist/chunk-YLSQAT6B.js.map +0 -1
  41. package/dist/chunk-ZL2NA3QT.js +0 -313
  42. package/dist/chunk-ZL2NA3QT.js.map +0 -1
  43. package/dist/concerns-client.d.ts +0 -12
  44. package/dist/concerns-client.js +0 -8
  45. package/dist/concerns-client.js.map +0 -1
  46. package/dist/contact-map-client.d.ts +0 -27
  47. package/dist/contact-map-client.js +0 -8
  48. package/dist/contact-map-client.js.map +0 -1
  49. package/dist/expertise-client.d.ts +0 -16
  50. package/dist/expertise-client.js +0 -9
  51. package/dist/expertise-client.js.map +0 -1
  52. package/dist/hero-carousel-client.d.ts +0 -52
  53. package/dist/hero-carousel-client.js +0 -9
  54. package/dist/hero-carousel-client.js.map +0 -1
  55. package/dist/seo-checks.d.ts +0 -87
  56. package/dist/seo-checks.js +0 -27
  57. package/dist/seo-checks.js.map +0 -1
  58. package/dist/site-config-BvUyDx_f.d.ts +0 -3961
  59. package/dist/spaces-client.d.ts +0 -10
  60. package/dist/spaces-client.js +0 -8
  61. package/dist/spaces-client.js.map +0 -1
  62. package/dist/team-expanding-stage-client.d.ts +0 -35
  63. package/dist/team-expanding-stage-client.js +0 -8
  64. package/dist/team-expanding-stage-client.js.map +0 -1
  65. package/dist/templates.css +0 -4145
  66. package/dist/templates.d.ts +0 -171
  67. package/dist/templates.js +0 -291
  68. package/dist/templates.js.map +0 -1
  69. package/dist/theme-css-vars-CD3jcEkp.d.ts +0 -7
  70. package/dist/ui-react-motion-client.d.ts +0 -76
  71. package/dist/ui-react-motion-client.js +0 -16
  72. package/dist/ui-react-motion-client.js.map +0 -1
  73. package/dist/validate.d.ts +0 -104
  74. package/dist/validate.js +0 -1204
  75. package/dist/validate.js.map +0 -1
  76. /package/dist/{before-after-client.js.map → section-runtime.js.map} +0 -0
package/dist/server.js CHANGED
@@ -1,40 +1,1685 @@
1
- import {
2
- RenderTiptap,
3
- RootTaleBlogCategories,
4
- RootTaleBlogList,
5
- RootTaleBlogPost,
6
- RootTaleFaqJsonLd,
7
- RootTaleFloatingCta,
8
- RootTaleLeadForm,
9
- RootTalePage,
10
- RootTalePostCard,
11
- RootTaleTableOfContents,
12
- RootTaleThemeProvider,
13
- attachHeadingIds,
14
- extractToc,
15
- headingToId,
16
- renderBlock,
17
- renderBlocks,
18
- renderBoundBlocks,
19
- themeToCssVars
20
- } from "./chunk-2QU4JU46.js";
21
- import "./chunk-VG6UUF4D.js";
22
- import "./chunk-QYNEOFL7.js";
23
- import "./chunk-U5AKACPN.js";
24
- import "./chunk-LEHHSI6G.js";
25
- import "./chunk-27RT6B3O.js";
26
- import "./chunk-YLSQAT6B.js";
27
- import "./chunk-4RQ3SEDL.js";
28
- import "./chunk-K62RR4UQ.js";
29
- import "./chunk-H46JUIHZ.js";
30
- import "./chunk-ZL2NA3QT.js";
31
1
  import {
32
2
  collectCategories,
33
3
  collectCategoriesWithCounts,
4
+ collectCollectionCategories,
34
5
  defaultCategoryHref,
35
- filterPostsByCategory
36
- } from "./chunk-U2IM6OCQ.js";
37
- import "./chunk-UM37DOIK.js";
6
+ filterPostsByCategory,
7
+ trimSlash
8
+ } from "./chunk-HM5VP7SR.js";
9
+ import {
10
+ getSectionNodeRenderer
11
+ } from "./chunk-KSPPYX22.js";
12
+ import {
13
+ LeadFormMeta
14
+ } from "./chunk-VG6UUF4D.js";
15
+
16
+ // src/PostCard.tsx
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ function firstCategoryName(terms) {
19
+ if (!terms) return null;
20
+ const cat = terms.find((t2) => t2.taxonomy === "category");
21
+ return cat ? cat.name : null;
22
+ }
23
+ function RootTalePostCard(props) {
24
+ const { post, href = defaultHref, display } = props;
25
+ const showDate = display ? display.showDate : true;
26
+ const showAuthor = display ? display.showAuthor : false;
27
+ const showImage = display?.showImage !== false;
28
+ const showCategory = display?.showCategory !== false;
29
+ const authorName = post.authorName;
30
+ const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : "";
31
+ const views = typeof post.viewCount === "number" ? post.viewCount : null;
32
+ const imageUrl = showImage ? post.featuredImageUrl : null;
33
+ const category = showCategory ? firstCategoryName(post.terms) : null;
34
+ return /* @__PURE__ */ jsx("article", { "data-roottale-cms": "card", className: "rt-cms-card", children: /* @__PURE__ */ jsxs("a", { className: "rt-cms-card-link", href: href(post), children: [
35
+ imageUrl ? /* @__PURE__ */ jsx(
36
+ "img",
37
+ {
38
+ className: "rt-cms-card-image",
39
+ src: imageUrl,
40
+ alt: "",
41
+ loading: "lazy",
42
+ decoding: "async"
43
+ }
44
+ ) : null,
45
+ category ? /* @__PURE__ */ jsx("span", { className: "rt-cms-card-category", children: category }) : null,
46
+ publishedDate || showAuthor && authorName || views !== null ? /* @__PURE__ */ jsxs("p", { className: "rt-cms-meta", children: [
47
+ publishedDate ? /* @__PURE__ */ jsx("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
48
+ showAuthor && authorName ? /* @__PURE__ */ jsxs("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
49
+ "\uC791\uC131\uC790 ",
50
+ authorName
51
+ ] }) : null,
52
+ views !== null ? /* @__PURE__ */ jsxs("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "views", children: [
53
+ "\uC870\uD68C ",
54
+ views.toLocaleString("ko-KR")
55
+ ] }) : null
56
+ ] }) : null,
57
+ /* @__PURE__ */ jsx("h2", { className: "rt-cms-title", children: post.title }),
58
+ post.excerpt ? /* @__PURE__ */ jsx("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null,
59
+ /* @__PURE__ */ jsx("span", { className: "rt-cms-card-action", "aria-hidden": "true", children: "\uC77D\uAE30" })
60
+ ] }) });
61
+ }
62
+ function defaultHref(post) {
63
+ return `/blog/${post.slug}`;
64
+ }
65
+ function formatPublishedDate(iso) {
66
+ const d = new Date(iso);
67
+ if (Number.isNaN(d.getTime())) return "";
68
+ return new Intl.DateTimeFormat("ko-KR", {
69
+ year: "numeric",
70
+ month: "2-digit",
71
+ day: "2-digit"
72
+ }).format(d);
73
+ }
74
+
75
+ // ../cms-i18n/src/index.ts
76
+ var DICTS = {
77
+ ko: {
78
+ blogListEmpty: "\uC544\uC9C1 \uBC1C\uD589\uB41C \uAE00\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
79
+ blogCategoriesEmpty: "\uC544\uC9C1 \uCE74\uD14C\uACE0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
80
+ allCategoriesLabel: "\uC804\uCCB4",
81
+ relatedPostsTitle: "\uAD00\uB828 \uAE00",
82
+ postNotFound: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
83
+ pageNotFound: "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
84
+ tableOfContentsTitle: "\uBAA9\uCC28"
85
+ },
86
+ en: {
87
+ blogListEmpty: "No posts published yet.",
88
+ blogCategoriesEmpty: "No categories yet.",
89
+ allCategoriesLabel: "All",
90
+ relatedPostsTitle: "Related posts",
91
+ postNotFound: "Post not found.",
92
+ pageNotFound: "Page not found.",
93
+ tableOfContentsTitle: "Table of contents"
94
+ }
95
+ };
96
+ function resolveLocale(locale) {
97
+ return locale === "en" ? "en" : "ko";
98
+ }
99
+ function t(locale, key) {
100
+ return DICTS[resolveLocale(locale)][key];
101
+ }
102
+
103
+ // src/TableOfContents.tsx
104
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
105
+ function RootTaleTableOfContents(props) {
106
+ const { entries, locale, title = t(locale, "tableOfContentsTitle") } = props;
107
+ if (entries.length === 0) return null;
108
+ return /* @__PURE__ */ jsx2("nav", { "data-roottale-cms": "toc", className: "rt-cms-toc", "aria-label": title, children: /* @__PURE__ */ jsxs2("details", { className: "rt-cms-toc-details", open: true, children: [
109
+ /* @__PURE__ */ jsxs2("summary", { className: "rt-cms-toc-summary", children: [
110
+ title ? /* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-title", children: title }) : null,
111
+ /* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-toggle", "aria-hidden": "true" })
112
+ ] }),
113
+ /* @__PURE__ */ jsx2("ol", { className: "rt-cms-toc-list", children: entries.map((entry) => /* @__PURE__ */ jsx2(
114
+ "li",
115
+ {
116
+ className: `rt-cms-toc-item rt-cms-toc-level-${entry.level}`,
117
+ children: /* @__PURE__ */ jsx2("a", { href: `#${entry.id}`, children: entry.text })
118
+ },
119
+ entry.id
120
+ )) })
121
+ ] }) });
122
+ }
123
+
124
+ // ../ui-react/dist/theme/index.js
125
+ import { jsx as _jsx } from "react/jsx-runtime";
126
+ function themeToCssVars(theme) {
127
+ return {
128
+ ...theme.colors?.primary ? { "--rt-color-primary": theme.colors.primary } : {},
129
+ ...theme.colors?.primaryForeground ? { "--rt-color-primary-foreground": theme.colors.primaryForeground } : {},
130
+ ...theme.colors?.secondary ? { "--rt-color-secondary": theme.colors.secondary } : {},
131
+ ...theme.colors?.secondaryForeground ? { "--rt-color-secondary-foreground": theme.colors.secondaryForeground } : {},
132
+ ...theme.colors?.accent ? { "--rt-color-accent": theme.colors.accent } : {},
133
+ ...theme.colors?.accentForeground ? { "--rt-color-accent-foreground": theme.colors.accentForeground } : {},
134
+ ...theme.colors?.background ? { "--rt-color-background": theme.colors.background } : {},
135
+ ...theme.colors?.foreground ? { "--rt-color-foreground": theme.colors.foreground } : {},
136
+ ...theme.colors?.muted ? { "--rt-color-muted": theme.colors.muted } : {},
137
+ ...theme.colors?.mutedForeground ? { "--rt-color-muted-foreground": theme.colors.mutedForeground } : {},
138
+ ...theme.colors?.destructive ? { "--rt-color-destructive": theme.colors.destructive } : {},
139
+ ...theme.colors?.destructiveForeground ? { "--rt-color-destructive-foreground": theme.colors.destructiveForeground } : {},
140
+ ...theme.colors?.border ? { "--rt-color-border": theme.colors.border } : {},
141
+ ...theme.colors?.input ? { "--rt-color-input": theme.colors.input } : {},
142
+ ...theme.colors?.ring ? { "--rt-color-ring": theme.colors.ring } : {},
143
+ ...theme.fonts?.body ? { "--rt-font-body": theme.fonts.body } : {},
144
+ ...theme.fonts?.display ? { "--rt-font-display": theme.fonts.display } : {},
145
+ ...theme.radius?.sm ? { "--rt-radius-sm": theme.radius.sm } : {},
146
+ ...theme.radius?.md ? { "--rt-radius-md": theme.radius.md } : {},
147
+ ...theme.radius?.lg ? { "--rt-radius-lg": theme.radius.lg } : {},
148
+ ...theme.radius?.xl ? { "--rt-radius-xl": theme.radius.xl } : {}
149
+ };
150
+ }
151
+
152
+ // src/theme-css-vars.ts
153
+ function themeToCssVars2(theme) {
154
+ if (!theme) return null;
155
+ const vars = themeToCssVars(theme);
156
+ if (Object.keys(vars).length === 0) return null;
157
+ return vars;
158
+ }
159
+
160
+ // src/FloatingCta.tsx
161
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
162
+ function RootTaleFloatingCta(props) {
163
+ const { buttons, position = "bottom-right" } = props;
164
+ if (!buttons || buttons.length === 0) return null;
165
+ return /* @__PURE__ */ jsx3(
166
+ "div",
167
+ {
168
+ "data-roottale-cms": "floating-cta",
169
+ className: `rt-cms-floating-cta rt-cms-floating-cta--${position}`,
170
+ children: buttons.map((btn, idx) => {
171
+ const isExternal = btn.type === "kakao" || btn.type === "custom";
172
+ return /* @__PURE__ */ jsxs3(
173
+ "a",
174
+ {
175
+ href: btn.href,
176
+ className: `rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`,
177
+ target: isExternal ? "_blank" : void 0,
178
+ rel: isExternal ? "noopener noreferrer" : void 0,
179
+ children: [
180
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "rt-cms-floating-cta__icon", children: btn.icon ?? defaultIcon(btn.type) }),
181
+ /* @__PURE__ */ jsx3("span", { className: "rt-cms-floating-cta__label", children: btn.label })
182
+ ]
183
+ },
184
+ `${btn.type}-${idx}`
185
+ );
186
+ })
187
+ }
188
+ );
189
+ }
190
+ function defaultIcon(type) {
191
+ switch (type) {
192
+ case "phone":
193
+ return "\u260E";
194
+ case "contact":
195
+ return "\u2709";
196
+ case "kakao":
197
+ return "\u{1F4AC}";
198
+ case "custom":
199
+ return "\u2192";
200
+ }
201
+ }
202
+
203
+ // src/LeadForm.tsx
204
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
205
+ var VERTICAL_LABEL = {
206
+ consulting: "\uCEE8\uC124\uD305",
207
+ medical: "\uC758\uB8CC",
208
+ tax: "\uC138\uBB34",
209
+ legal: "\uBC95\uB960"
210
+ };
211
+ var VERTICAL_OPTIONS = [
212
+ { value: "consulting", label: "\uCEE8\uC124\uD305 / \uC77C\uBC18" },
213
+ { value: "medical", label: "\uC758\uB8CC (\uBCD1\uC758\uC6D0\xB7\uCE58\uACFC\xB7\uD55C\uC758\uC6D0)" },
214
+ { value: "tax", label: "\uC138\uBB34 (\uC138\uBB34\uC0AC\xB7\uD68C\uACC4\uC0AC)" },
215
+ { value: "legal", label: "\uBC95\uB960 (\uBC95\uBB34\uBC95\uC778\xB7\uBCC0\uD638\uC0AC)" }
216
+ ];
217
+ function RootTaleLeadForm(props) {
218
+ const {
219
+ action,
220
+ vertical,
221
+ redirectUrl,
222
+ heading,
223
+ description,
224
+ submitLabel = "\uC9C4\uB2E8 \uC2E0\uCCAD",
225
+ turnstileSiteKey,
226
+ className
227
+ } = props;
228
+ const formClass = ["rt-cms-lead-form", className].filter(Boolean).join(" ");
229
+ const isMedical = vertical === "medical";
230
+ return /* @__PURE__ */ jsxs4(
231
+ "form",
232
+ {
233
+ method: "post",
234
+ action,
235
+ "data-roottale-cms": "lead-form",
236
+ className: formClass,
237
+ children: [
238
+ /* @__PURE__ */ jsx4(LeadFormMeta, { formId: "contact" }),
239
+ heading ? /* @__PURE__ */ jsx4("h2", { className: "rt-cms-lead-heading", children: heading }) : null,
240
+ description ? /* @__PURE__ */ jsx4("p", { className: "rt-cms-lead-description", children: description }) : null,
241
+ redirectUrl ? /* @__PURE__ */ jsx4("input", { type: "hidden", name: "_redirect_url", value: redirectUrl }) : null,
242
+ vertical ? /* @__PURE__ */ jsxs4(Fragment, { children: [
243
+ /* @__PURE__ */ jsx4("input", { type: "hidden", name: "vertical", value: vertical }),
244
+ /* @__PURE__ */ jsxs4("p", { className: "rt-cms-lead-vertical-label", children: [
245
+ "\uBD84\uC57C: ",
246
+ /* @__PURE__ */ jsx4("strong", { children: VERTICAL_LABEL[vertical] })
247
+ ] })
248
+ ] }) : /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
249
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
250
+ "\uBD84\uC57C ",
251
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
252
+ ] }),
253
+ /* @__PURE__ */ jsxs4(
254
+ "select",
255
+ {
256
+ name: "vertical",
257
+ required: true,
258
+ className: "rt-cms-field-input rt-cms-field-select",
259
+ defaultValue: "",
260
+ children: [
261
+ /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: "\uC120\uD0DD\uD558\uC138\uC694" }),
262
+ VERTICAL_OPTIONS.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.value, children: opt.label }, opt.value))
263
+ ]
264
+ }
265
+ )
266
+ ] }),
267
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
268
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
269
+ "\uC774\uB984 ",
270
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
271
+ ] }),
272
+ /* @__PURE__ */ jsx4(
273
+ "input",
274
+ {
275
+ type: "text",
276
+ name: "contact_name",
277
+ required: true,
278
+ autoComplete: "name",
279
+ className: "rt-cms-field-input"
280
+ }
281
+ )
282
+ ] }),
283
+ isMedical ? (
284
+ // 환자 문의에는 "사업체명" 개념이 없다 — 화면에는 노출하지 않되,
285
+ // intake 가 vertical 불문 hard-required 로 검증하는 계약(위 파일
286
+ // 헤더 코멘트 참조)을 만족시키기 위해 자리표시자만 hidden 전송.
287
+ /* @__PURE__ */ jsx4("input", { type: "hidden", name: "business_name", value: "-" })
288
+ ) : /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
289
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
290
+ "\uC0AC\uC5C5\uCCB4\uBA85 ",
291
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
292
+ ] }),
293
+ /* @__PURE__ */ jsx4(
294
+ "input",
295
+ {
296
+ type: "text",
297
+ name: "business_name",
298
+ required: true,
299
+ autoComplete: "organization",
300
+ className: "rt-cms-field-input"
301
+ }
302
+ )
303
+ ] }),
304
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
305
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
306
+ "\uC804\uD654\uBC88\uD638 ",
307
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
308
+ ] }),
309
+ /* @__PURE__ */ jsx4(
310
+ "input",
311
+ {
312
+ type: "tel",
313
+ name: "phone",
314
+ required: true,
315
+ autoComplete: "tel",
316
+ inputMode: "tel",
317
+ pattern: "0\\d{1,2}-?\\d{3,4}-?\\d{4}",
318
+ placeholder: "010-1234-5678",
319
+ className: "rt-cms-field-input"
320
+ }
321
+ )
322
+ ] }),
323
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
324
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
325
+ "\uC774\uBA54\uC77C ",
326
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
327
+ ] }),
328
+ /* @__PURE__ */ jsx4(
329
+ "input",
330
+ {
331
+ type: "email",
332
+ name: "email",
333
+ required: true,
334
+ autoComplete: "email",
335
+ className: "rt-cms-field-input"
336
+ }
337
+ )
338
+ ] }),
339
+ isMedical ? /* @__PURE__ */ jsxs4(Fragment, { children: [
340
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
341
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-label", children: "\uC0C1\uB2F4 \uD76C\uB9DD \uBD84\uC57C\xB7\uC99D\uC0C1 (\uC120\uD0DD)" }),
342
+ /* @__PURE__ */ jsx4(
343
+ "input",
344
+ {
345
+ type: "text",
346
+ name: "consultation_field",
347
+ placeholder: "\uC608: \uBB34\uB98E \uD1B5\uC99D, \uC815\uAE30\uAC80\uC9C4",
348
+ className: "rt-cms-field-input"
349
+ }
350
+ )
351
+ ] }),
352
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
353
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-label", children: "\uBB38\uC758 \uB0B4\uC6A9 (\uC120\uD0DD)" }),
354
+ /* @__PURE__ */ jsx4(
355
+ "textarea",
356
+ {
357
+ name: "message",
358
+ rows: 4,
359
+ placeholder: "\uC99D\uC0C1\uC774 \uC5B8\uC81C\uBD80\uD130 \uC788\uC5C8\uB294\uC9C0, \uAD81\uAE08\uD558\uC2E0 \uC810\uC744 \uC790\uC720\uB86D\uAC8C \uC801\uC5B4\uC8FC\uC138\uC694.",
360
+ className: "rt-cms-field-input rt-cms-field-textarea"
361
+ }
362
+ )
363
+ ] })
364
+ ] }) : /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
365
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-label", children: "\uD604\uC7AC \uC0AC\uC774\uD2B8 URL (\uC120\uD0DD)" }),
366
+ /* @__PURE__ */ jsx4(
367
+ "input",
368
+ {
369
+ type: "url",
370
+ name: "current_site_url",
371
+ placeholder: "https://",
372
+ className: "rt-cms-field-input"
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-hint", children: "\uC5C6\uC73C\uBA74 \uBE44\uC6CC\uB450\uC138\uC694." })
376
+ ] }),
377
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
378
+ /* @__PURE__ */ jsx4("input", { type: "checkbox", name: "privacy_consent", required: true }),
379
+ /* @__PURE__ */ jsxs4("span", { children: [
380
+ /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218)" }),
381
+ " \uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9\uC5D0 \uB3D9\uC758\uD569\uB2C8\uB2E4. (\uC218\uC9D1 \uD56D\uBAA9:",
382
+ " ",
383
+ isMedical ? "\uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7\uC774\uBA54\uC77C\xB7\uC0C1\uB2F4 \uB0B4\uC6A9(\uC791\uC131 \uC2DC)\xB7\uC0AC\uC774\uD2B8 \uC774\uC6A9 \uAE30\uB85D(\uBC29\uBB38 \uD398\uC774\uC9C0\xB7\uC0C1\uD638\uC791\uC6A9)" : "\uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7\uC774\uBA54\uC77C\xB7\uC0AC\uC5C5\uCCB4\uBA85\xB7\uC0AC\uC774\uD2B8 \uC774\uC6A9 \uAE30\uB85D(\uBC29\uBB38 \uD398\uC774\uC9C0\xB7\uC0C1\uD638\uC791\uC6A9)",
384
+ ". \uBCF4\uAD00 \uAE30\uAC04: \uBB38\uC758 \uC885\uB8CC \uD6C4 3\uB144)"
385
+ ] })
386
+ ] }),
387
+ vertical === "medical" ? /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
388
+ /* @__PURE__ */ jsx4(
389
+ "input",
390
+ {
391
+ type: "checkbox",
392
+ name: "overseas_transfer_consent",
393
+ required: true
394
+ }
395
+ ),
396
+ /* @__PURE__ */ jsxs4("span", { children: [
397
+ /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218, \uC758\uB8CC)" }),
398
+ " \uC758\uB8CC PII \uC758 \uAD6D\uC678\uC774\uC804(\uBCF4\uAD00\xB7\uCC98\uB9AC)\uC5D0 \uB3D9\uC758\uD569\uB2C8\uB2E4. (\uC758\uB8CC\uBC95 \xA721\xB7\uAC1C\uC778\uC815\uBCF4\uBCF4\uD638\uBC95 \xA728 \u2014 ADR-0018)"
399
+ ] })
400
+ ] }) : null,
401
+ turnstileSiteKey ? /* @__PURE__ */ jsxs4(Fragment, { children: [
402
+ /* @__PURE__ */ jsx4(
403
+ "script",
404
+ {
405
+ src: "https://challenges.cloudflare.com/turnstile/v0/api.js",
406
+ async: true,
407
+ defer: true
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx4(
411
+ "div",
412
+ {
413
+ className: "cf-turnstile",
414
+ "data-sitekey": turnstileSiteKey,
415
+ "data-action": "turnstile-spin-v1"
416
+ }
417
+ )
418
+ ] }) : null,
419
+ /* @__PURE__ */ jsx4("div", { className: "rt-cms-lead-actions", children: /* @__PURE__ */ jsx4("button", { type: "submit", className: "rt-cms-lead-submit", children: submitLabel }) })
420
+ ]
421
+ }
422
+ );
423
+ }
424
+
425
+ // src/block-to-react.tsx
426
+ import {
427
+ applyBlockBindings,
428
+ createFieldBindingResolver,
429
+ sanitizeHtml
430
+ } from "@roottale/cms-core";
431
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
432
+ function isString(value) {
433
+ return typeof value === "string";
434
+ }
435
+ function htmlContent(html) {
436
+ return { __html: sanitizeHtml(html) };
437
+ }
438
+ function renderInner(blocks) {
439
+ return blocks.map((b, i) => renderBlock(b, i));
440
+ }
441
+ function renderBlock(block, key = 0) {
442
+ const dataBlockId = block._id;
443
+ switch (block.name) {
444
+ case "core/paragraph": {
445
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
446
+ return /* @__PURE__ */ jsx5(
447
+ "p",
448
+ {
449
+ "data-block-id": dataBlockId,
450
+ dangerouslySetInnerHTML: htmlContent(text)
451
+ },
452
+ key
453
+ );
454
+ }
455
+ case "core/heading": {
456
+ const levelRaw = block.attributes.level;
457
+ const level = typeof levelRaw === "number" && levelRaw >= 2 && levelRaw <= 4 ? levelRaw : 2;
458
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
459
+ const props = {
460
+ "data-block-id": dataBlockId,
461
+ dangerouslySetInnerHTML: htmlContent(text)
462
+ };
463
+ if (level === 2) return /* @__PURE__ */ jsx5("h2", { ...props }, key);
464
+ if (level === 3) return /* @__PURE__ */ jsx5("h3", { ...props }, key);
465
+ return /* @__PURE__ */ jsx5("h4", { ...props }, key);
466
+ }
467
+ case "core/image": {
468
+ const src = isString(block.attributes.url) ? block.attributes.url : "";
469
+ const alt = isString(block.attributes.alt) ? block.attributes.alt : "";
470
+ const caption = isString(block.attributes.caption) ? block.attributes.caption : null;
471
+ return /* @__PURE__ */ jsxs5("figure", { "data-block-id": dataBlockId, children: [
472
+ /* @__PURE__ */ jsx5("img", { src, alt, loading: "lazy" }),
473
+ caption && /* @__PURE__ */ jsx5("figcaption", { children: caption })
474
+ ] }, key);
475
+ }
476
+ case "core/list": {
477
+ const ordered = block.attributes.ordered === true;
478
+ const items = (block.innerBlocks ?? []).map(
479
+ (item, i) => item.rawHtml ? /* @__PURE__ */ jsx5(
480
+ "li",
481
+ {
482
+ dangerouslySetInnerHTML: htmlContent(item.rawHtml)
483
+ },
484
+ i
485
+ ) : /* @__PURE__ */ jsx5("li", { children: renderInner(item.innerBlocks ?? []) }, i)
486
+ );
487
+ return ordered ? /* @__PURE__ */ jsx5("ol", { "data-block-id": dataBlockId, children: items }, key) : /* @__PURE__ */ jsx5("ul", { "data-block-id": dataBlockId, children: items }, key);
488
+ }
489
+ case "core/quote": {
490
+ const inner = renderInner(block.innerBlocks ?? []);
491
+ const citation = isString(block.attributes.citation) ? block.attributes.citation : null;
492
+ return /* @__PURE__ */ jsxs5("blockquote", { "data-block-id": dataBlockId, children: [
493
+ inner,
494
+ citation && /* @__PURE__ */ jsx5("cite", { children: citation })
495
+ ] }, key);
496
+ }
497
+ case "core/code": {
498
+ const code = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
499
+ const lang = isString(block.attributes.language) ? block.attributes.language : void 0;
500
+ return /* @__PURE__ */ jsx5(
501
+ "pre",
502
+ {
503
+ "data-block-id": dataBlockId,
504
+ "data-language": lang,
505
+ children: /* @__PURE__ */ jsx5("code", { children: code })
506
+ },
507
+ key
508
+ );
509
+ }
510
+ case "core/columns": {
511
+ return /* @__PURE__ */ jsx5(
512
+ "div",
513
+ {
514
+ className: "rt-columns",
515
+ "data-block-id": dataBlockId,
516
+ children: renderInner(block.innerBlocks ?? [])
517
+ },
518
+ key
519
+ );
520
+ }
521
+ case "core/separator":
522
+ return /* @__PURE__ */ jsx5("hr", { "data-block-id": dataBlockId }, key);
523
+ case "core/spacer": {
524
+ const heightRaw = block.attributes.height;
525
+ const height = isString(heightRaw) ? heightRaw : "32px";
526
+ return /* @__PURE__ */ jsx5(
527
+ "div",
528
+ {
529
+ className: "rt-spacer",
530
+ "data-block-id": dataBlockId,
531
+ style: { height }
532
+ },
533
+ key
534
+ );
535
+ }
536
+ case "core/group": {
537
+ return /* @__PURE__ */ jsx5(
538
+ "div",
539
+ {
540
+ className: "rt-group",
541
+ "data-block-id": dataBlockId,
542
+ children: renderInner(block.innerBlocks ?? [])
543
+ },
544
+ key
545
+ );
546
+ }
547
+ default: {
548
+ const rawHtml = block.rawHtml;
549
+ return /* @__PURE__ */ jsx5(
550
+ "div",
551
+ {
552
+ className: "rt-unknown-block",
553
+ "data-block-id": dataBlockId,
554
+ "data-block-name": block.name,
555
+ "data-rawhtml-suppressed": rawHtml ? "true" : void 0,
556
+ children: renderInner(block.innerBlocks ?? [])
557
+ },
558
+ key
559
+ );
560
+ }
561
+ }
562
+ }
563
+ function renderBlocks(blocks) {
564
+ return blocks.map((b, i) => renderBlock(b, i));
565
+ }
566
+ function renderBoundBlocks(blocks, opts = {}) {
567
+ const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);
568
+ return renderBlocks(applyBlockBindings(blocks, resolver));
569
+ }
570
+
571
+ // src/toc.ts
572
+ function tocLevel(rawLevel) {
573
+ if (rawLevel < 1 || rawLevel > 4) return null;
574
+ if (rawLevel < 3) return 2;
575
+ return rawLevel === 3 ? 3 : 4;
576
+ }
577
+ function headingToId(text) {
578
+ return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
579
+ }
580
+ function textOf(node) {
581
+ if (typeof node.text === "string") return node.text;
582
+ if (!Array.isArray(node.content)) return "";
583
+ return node.content.map(textOf).join("");
584
+ }
585
+ function extractToc(doc) {
586
+ const root = doc;
587
+ const content = Array.isArray(root.content) ? root.content : [];
588
+ const entries = [];
589
+ const idCounts = /* @__PURE__ */ new Map();
590
+ for (const node of content) {
591
+ if (node?.type !== "heading") continue;
592
+ const rawLevel = Number(node.attrs?.level ?? 2);
593
+ const level = tocLevel(rawLevel);
594
+ if (level === null) continue;
595
+ const text = textOf(node).trim();
596
+ if (!text) continue;
597
+ const base = headingToId(text) || "heading";
598
+ const count = idCounts.get(base) ?? 0;
599
+ const id = count === 0 ? base : `${base}-${count}`;
600
+ idCounts.set(base, count + 1);
601
+ entries.push({ level, text, id });
602
+ }
603
+ return entries;
604
+ }
605
+ function attachHeadingIds(doc, entries) {
606
+ const root = doc;
607
+ const content = Array.isArray(root.content) ? root.content : [];
608
+ let cursor = 0;
609
+ const nextContent = content.map((node) => {
610
+ if (node?.type !== "heading") return node;
611
+ const rawLevel = Number(node.attrs?.level ?? 2);
612
+ if (rawLevel < 1 || rawLevel > 4) return node;
613
+ if (!textOf(node).trim()) return node;
614
+ const entry = entries[cursor++];
615
+ if (!entry) return node;
616
+ return {
617
+ ...node,
618
+ attrs: { ...node.attrs ?? {}, id: entry.id }
619
+ };
620
+ });
621
+ return { ...root, content: nextContent };
622
+ }
623
+
624
+ // src/server/blog-list.tsx
625
+ import {
626
+ fetchPosts,
627
+ resolvePostDisplay
628
+ } from "@roottale/cms-client/server";
629
+ import {
630
+ postsForCollection,
631
+ postsNotInAnyCollection
632
+ } from "@roottale/cms-core";
633
+
634
+ // src/server/blog-settings.ts
635
+ import {
636
+ DEFAULT_BLOG_SETTINGS,
637
+ fetchBlogSettings
638
+ } from "@roottale/cms-client/server";
639
+ async function fetchBlogSettingsFailSoft(input) {
640
+ return fetchBlogSettings(input).catch(() => DEFAULT_BLOG_SETTINGS);
641
+ }
642
+
643
+ // src/server/post-links.ts
644
+ import {
645
+ resolvePostCollection,
646
+ resolvePostPath
647
+ } from "@roottale/cms-core";
648
+ function defaultPostHref(post) {
649
+ return `/blog/${post.slug}`;
650
+ }
651
+ function defaultAuthorHref(slug) {
652
+ return `/blog/author/${slug}`;
653
+ }
654
+ function makeCollectionPostHref(collections) {
655
+ return (post) => {
656
+ const path = resolvePostPath(post, collections);
657
+ if (path) return path;
658
+ const collection = resolvePostCollection(post, collections);
659
+ if (collection?.detail === false && collection.basePath) {
660
+ return collection.basePath;
661
+ }
662
+ return defaultPostHref(post);
663
+ };
664
+ }
665
+
666
+ // src/server/theme.tsx
667
+ import {
668
+ fetchTheme
669
+ } from "@roottale/cms-client/server";
670
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
671
+ async function resolveThemeStyleFailSoft(input) {
672
+ const { themeProp, apiKey, baseUrl } = input;
673
+ if (themeProp === null) return null;
674
+ if (themeProp !== void 0) return themeToCssVars2(themeProp);
675
+ const fetched = await fetchTheme({ apiKey, baseUrl }).catch(() => null);
676
+ return themeToCssVars2(fetched);
677
+ }
678
+ function RootTaleThemeProvider(props) {
679
+ const style = themeToCssVars2(props.theme ?? null);
680
+ if (!style) return /* @__PURE__ */ jsx6(Fragment2, { children: props.children });
681
+ return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "theme-root", "data-rt-theme": "", style, children: props.children });
682
+ }
683
+
684
+ // src/server/blog-list.tsx
685
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
686
+ function validateCollectionScope(props) {
687
+ if (props.collection && props.excludeCollections) {
688
+ throw new Error(
689
+ "RootTaleBlogList: `collection` \uACFC `excludeCollections` \uB97C \uB3D9\uC2DC\uC5D0 \uC904 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uD3EC\uC9C0\uD2F0\uBE0C/\uB124\uAC70\uD2F0\uBE0C \uC2A4\uCF54\uD504\uB294 \uBC30\uD0C0\uC801\uC785\uB2C8\uB2E4."
690
+ );
691
+ }
692
+ if (props.excludeCollections && !props.collections) {
693
+ throw new Error(
694
+ "RootTaleBlogList: `excludeCollections` \uB294 `collections` \uC640 \uD568\uAED8 \uC918\uC57C \uD569\uB2C8\uB2E4."
695
+ );
696
+ }
697
+ }
698
+ function scopePosts(input) {
699
+ const { posts, collection, collections, excludeCollections } = input;
700
+ if (collection && collections) {
701
+ return postsForCollection(posts, collections, collection);
702
+ }
703
+ if (excludeCollections && collections) {
704
+ return postsNotInAnyCollection(posts, collections);
705
+ }
706
+ return posts;
707
+ }
708
+ async function RootTaleBlogList(props) {
709
+ validateCollectionScope(props);
710
+ const {
711
+ apiKey,
712
+ baseUrl,
713
+ limit,
714
+ type = "post",
715
+ collection,
716
+ collections,
717
+ excludeCollections = false,
718
+ locale,
719
+ emptyMessage = t(locale, "blogListEmpty"),
720
+ showCategoryFilter = false,
721
+ activeCategory,
722
+ categoryHref = defaultCategoryHref,
723
+ allCategoriesLabel = t(locale, "allCategoriesLabel"),
724
+ author,
725
+ theme: themeProp
726
+ } = props;
727
+ const postHref = props.postHref ?? (collections ? makeCollectionPostHref(collections) : defaultPostHref);
728
+ const [page, themeStyle, blogSettings] = await Promise.all([
729
+ fetchPosts({
730
+ apiKey,
731
+ baseUrl,
732
+ limit,
733
+ type,
734
+ ...author ? { author } : {},
735
+ ...collection && collections ? { collectionKey: collection } : {},
736
+ ...excludeCollections && collections ? { excludeCollections: true } : {},
737
+ ...locale ? { locale } : {}
738
+ }),
739
+ resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),
740
+ fetchBlogSettingsFailSoft({ apiKey, baseUrl })
741
+ ]);
742
+ const items = scopePosts({
743
+ posts: page.items,
744
+ collection,
745
+ collections,
746
+ excludeCollections
747
+ });
748
+ if (items.length === 0) {
749
+ return /* @__PURE__ */ jsx7("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx7("p", { className: "rt-cms-empty", children: emptyMessage }) });
750
+ }
751
+ const categories = showCategoryFilter ? collectCategories(items) : [];
752
+ const visiblePosts = showCategoryFilter ? filterPostsByCategory(items, activeCategory) : items;
753
+ const isAllCategoriesActive = !activeCategory;
754
+ return /* @__PURE__ */ jsxs6("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: [
755
+ showCategoryFilter && categories.length > 0 ? /* @__PURE__ */ jsxs6("nav", { className: "rt-cms-filter", "aria-label": "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130", children: [
756
+ /* @__PURE__ */ jsx7(
757
+ "a",
758
+ {
759
+ className: "rt-cms-filter-chip",
760
+ href: categoryHref(null),
761
+ "data-active": isAllCategoriesActive ? "" : void 0,
762
+ "aria-current": isAllCategoriesActive ? "true" : void 0,
763
+ children: allCategoriesLabel
764
+ }
765
+ ),
766
+ categories.map((category) => /* @__PURE__ */ jsx7(
767
+ "a",
768
+ {
769
+ className: "rt-cms-filter-chip",
770
+ href: categoryHref(category.slug),
771
+ "data-active": activeCategory === category.slug ? "" : void 0,
772
+ "aria-current": activeCategory === category.slug ? "true" : void 0,
773
+ children: category.name
774
+ },
775
+ category.slug
776
+ ))
777
+ ] }) : null,
778
+ visiblePosts.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx7("ul", { className: "rt-cms-list", children: visiblePosts.map((post) => /* @__PURE__ */ jsx7("li", { className: "rt-cms-list-item", children: /* @__PURE__ */ jsx7(
779
+ RootTalePostCard,
780
+ {
781
+ post,
782
+ href: postHref,
783
+ display: resolvePostDisplay(post, blogSettings)
784
+ }
785
+ ) }, post.id)) })
786
+ ] });
787
+ }
788
+
789
+ // src/server/blog-categories.tsx
790
+ import {
791
+ fetchPosts as fetchPosts2
792
+ } from "@roottale/cms-client/server";
793
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
794
+ async function RootTaleBlogCategories(props) {
795
+ const {
796
+ apiKey,
797
+ baseUrl,
798
+ limit,
799
+ collection,
800
+ collections,
801
+ categoryHref = defaultCategoryHref,
802
+ locale,
803
+ emptyMessage = t(locale, "blogCategoriesEmpty"),
804
+ theme: themeProp
805
+ } = props;
806
+ const [page, themeStyle] = await Promise.all([
807
+ fetchPosts2({
808
+ apiKey,
809
+ baseUrl,
810
+ limit,
811
+ type: "post",
812
+ ...locale ? { locale } : {}
813
+ }),
814
+ resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl })
815
+ ]);
816
+ const categories = collection && collections ? collectCollectionCategories(page.items, collections, collection) : collectCategoriesWithCounts(page.items);
817
+ return /* @__PURE__ */ jsx8(
818
+ "nav",
819
+ {
820
+ "data-roottale-cms": "categories",
821
+ className: "rt-cms-categories",
822
+ "aria-label": "\uBE14\uB85C\uADF8 \uCE74\uD14C\uACE0\uB9AC",
823
+ style: themeStyle ?? void 0,
824
+ children: categories.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx8("ul", { className: "rt-cms-category-list", children: categories.map((category) => /* @__PURE__ */ jsx8("li", { className: "rt-cms-category-list-item", children: /* @__PURE__ */ jsxs7(
825
+ "a",
826
+ {
827
+ className: "rt-cms-category-link",
828
+ href: categoryHref(category.slug),
829
+ children: [
830
+ /* @__PURE__ */ jsx8("span", { className: "rt-cms-category-name", children: category.name }),
831
+ /* @__PURE__ */ jsx8("span", { className: "rt-cms-category-count", children: category.count })
832
+ ]
833
+ }
834
+ ) }, category.slug)) })
835
+ }
836
+ );
837
+ }
838
+
839
+ // src/server/blog-post.tsx
840
+ import {
841
+ fetchPost,
842
+ fetchPosts as fetchPosts3,
843
+ resolvePostDisplay as resolvePostDisplay2,
844
+ selectRelatedPosts
845
+ } from "@roottale/cms-client/server";
846
+
847
+ // src/server/blog-post-archive.tsx
848
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
849
+ var FIELD_FACT_TYPES = /* @__PURE__ */ new Set([
850
+ "text",
851
+ "textarea",
852
+ "email",
853
+ "url",
854
+ "select",
855
+ "radio",
856
+ "button_group",
857
+ "number",
858
+ "range"
859
+ ]);
860
+ function archiveGroupTitle(group) {
861
+ if (group.collectionKey === "conditions") {
862
+ return `\uC774 \uC99D\uC0C1\uACFC \uAD00\uB828\uB41C \uC9C8\uD658 ${group.items.length}`;
863
+ }
864
+ if (group.collectionKey === "treatments") {
865
+ return `\uAD00\uB828 \uCE58\uB8CC\uBC95 ${group.items.length}`;
866
+ }
867
+ if (group.collectionKey === "symptoms") {
868
+ return `\uAD00\uB828 \uC99D\uC0C1 ${group.items.length}`;
869
+ }
870
+ return `${group.label} ${group.items.length}`;
871
+ }
872
+ function defaultArchiveBasePath(collectionKey) {
873
+ if (collectionKey === "symptoms") return "/symptoms";
874
+ if (collectionKey === "conditions") return "/conditions";
875
+ if (collectionKey === "treatments") return "/treatments";
876
+ return "/blog";
877
+ }
878
+ function archivePostHref(collectionKey, slug, collections) {
879
+ const collection = collections?.find((item) => item.key === collectionKey);
880
+ const basePath = collection?.basePath || defaultArchiveBasePath(collectionKey);
881
+ const trimmedBase = basePath.replace(/\/+$/, "");
882
+ if (collection?.detail === false) return trimmedBase;
883
+ return `${trimmedBase}/${slug}`;
884
+ }
885
+ function renderArchiveRelated(groups, collections) {
886
+ const visibleGroups = groups.filter((group) => group.items.length > 0);
887
+ if (visibleGroups.length === 0) return null;
888
+ return /* @__PURE__ */ jsx9("aside", { className: "rt-archive-related", "aria-label": "\uC5F0\uAD00 \uC815\uBCF4", children: visibleGroups.map((group) => /* @__PURE__ */ jsxs8("section", { className: "rt-archive-related__group", children: [
889
+ /* @__PURE__ */ jsx9("h2", { className: "rt-archive-related__title", children: archiveGroupTitle(group) }),
890
+ /* @__PURE__ */ jsx9("ul", { className: "rt-archive-related__list", children: group.items.map((item) => /* @__PURE__ */ jsx9("li", { className: "rt-archive-related__item", children: /* @__PURE__ */ jsxs8(
891
+ "a",
892
+ {
893
+ className: "rt-archive-related__link",
894
+ href: archivePostHref(
895
+ item.collectionKey,
896
+ item.slug,
897
+ collections
898
+ ),
899
+ children: [
900
+ /* @__PURE__ */ jsx9("span", { className: "rt-archive-related__item-title", children: item.title }),
901
+ item.excerpt ? /* @__PURE__ */ jsx9("span", { className: "rt-archive-related__item-excerpt", children: item.excerpt }) : null
902
+ ]
903
+ }
904
+ ) }, item.id)) })
905
+ ] }, group.collectionKey)) });
906
+ }
907
+ function fieldFactValue(raw, meta) {
908
+ if (raw === null || raw === void 0) return null;
909
+ if (typeof raw === "number") return raw.toLocaleString("ko-KR");
910
+ if (typeof raw !== "string" || raw.trim() === "") return null;
911
+ if (!meta.choices) return raw;
912
+ const choice = meta.choices.find((item) => item.value === raw);
913
+ return choice ? choice.label : raw;
914
+ }
915
+ function renderFieldFacts(post) {
916
+ if (!post.fields) return null;
917
+ const rows = post.fieldsMeta.filter((meta) => FIELD_FACT_TYPES.has(meta.type)).flatMap((meta) => {
918
+ const value = fieldFactValue(post.fields?.[meta.name], meta);
919
+ return value === null ? [] : [{ meta, value }];
920
+ });
921
+ if (rows.length === 0) return null;
922
+ return /* @__PURE__ */ jsx9("aside", { className: "rt-archive-facts", "aria-label": "\uC0C1\uC138 \uC815\uBCF4", children: /* @__PURE__ */ jsx9("dl", { className: "rt-archive-facts__list", children: rows.map(({ meta, value }) => /* @__PURE__ */ jsxs8("div", { className: "rt-archive-facts__row", children: [
923
+ /* @__PURE__ */ jsx9("dt", { className: "rt-archive-facts__label", children: meta.label }),
924
+ /* @__PURE__ */ jsx9(
925
+ "dd",
926
+ {
927
+ className: meta.type === "textarea" ? "rt-archive-facts__value rt-archive-facts__value--multiline" : "rt-archive-facts__value",
928
+ children: value
929
+ }
930
+ )
931
+ ] }, meta.name)) }) });
932
+ }
933
+
934
+ // src/server/tiptap-node-renderer.tsx
935
+ import {
936
+ bindingSrcValue,
937
+ bindingTextValue,
938
+ boundFieldValue,
939
+ normalizeImageAlt
940
+ } from "@roottale/cms-core";
941
+
942
+ // src/server/tiptap-embed-renderer.tsx
943
+ import { isAllowedEmbedSrc } from "@roottale/cms-core";
944
+ import { jsx as jsx10 } from "react/jsx-runtime";
945
+ function isSafeFallbackHref(value) {
946
+ if (typeof value !== "string") return false;
947
+ const href = value.trim();
948
+ if (href.length === 0) return false;
949
+ if (href.startsWith("/") || href.startsWith("#") || href.startsWith("?")) {
950
+ return true;
951
+ }
952
+ return /^(https?:|mailto:|tel:)/i.test(href);
953
+ }
954
+ function renderEmbedNode(node, key) {
955
+ const src = node.attrs?.src;
956
+ if (isAllowedEmbedSrc(src)) {
957
+ const provider = typeof node.attrs?.provider === "string" ? node.attrs.provider : void 0;
958
+ return /* @__PURE__ */ jsx10(
959
+ "div",
960
+ {
961
+ className: "rt-embed",
962
+ "data-provider": provider,
963
+ style: { aspectRatio: "16 / 9" },
964
+ children: /* @__PURE__ */ jsx10(
965
+ "iframe",
966
+ {
967
+ src,
968
+ title: "\uC784\uBCA0\uB4DC\uB41C \uC601\uC0C1",
969
+ loading: "lazy",
970
+ allowFullScreen: true,
971
+ referrerPolicy: "strict-origin-when-cross-origin",
972
+ allow: "accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share",
973
+ style: { width: "100%", height: "100%", border: 0 }
974
+ }
975
+ )
976
+ },
977
+ key
978
+ );
979
+ }
980
+ const originalUrl = node.attrs?.originalUrl;
981
+ if (!isSafeFallbackHref(originalUrl)) return null;
982
+ return /* @__PURE__ */ jsx10("p", { className: "rt-embed-fallback", children: /* @__PURE__ */ jsx10("a", { href: originalUrl, rel: "noopener noreferrer", target: "_blank", children: originalUrl }) }, key);
983
+ }
984
+
985
+ // src/server/tiptap-types.ts
986
+ function isRecord(value) {
987
+ return typeof value === "object" && value !== null;
988
+ }
989
+ function parseTiptapNode(value) {
990
+ if (!isRecord(value)) return null;
991
+ return {
992
+ type: typeof value.type === "string" ? value.type : void 0,
993
+ text: typeof value.text === "string" ? value.text : void 0,
994
+ attrs: isRecord(value.attrs) ? value.attrs : void 0,
995
+ content: Array.isArray(value.content) ? value.content : void 0,
996
+ marks: Array.isArray(value.marks) ? value.marks : void 0
997
+ };
998
+ }
999
+ function parseTiptapMark(value) {
1000
+ if (!isRecord(value)) return null;
1001
+ return {
1002
+ type: typeof value.type === "string" ? value.type : void 0,
1003
+ attrs: isRecord(value.attrs) ? value.attrs : void 0
1004
+ };
1005
+ }
1006
+
1007
+ // src/server/tiptap-inline-renderer.tsx
1008
+ import { Fragment as Fragment3, jsx as jsx11 } from "react/jsx-runtime";
1009
+ function isSafeHref(value) {
1010
+ if (typeof value !== "string") return false;
1011
+ const href = value.trim();
1012
+ if (href.length === 0) return false;
1013
+ if (href.startsWith("/") || href.startsWith("#") || href.startsWith("?")) {
1014
+ return true;
1015
+ }
1016
+ return /^(https?:|mailto:|tel:)/i.test(href);
1017
+ }
1018
+ function applyMark(mark, child) {
1019
+ switch (mark.type) {
1020
+ case "bold":
1021
+ return /* @__PURE__ */ jsx11("strong", { children: child });
1022
+ case "italic":
1023
+ return /* @__PURE__ */ jsx11("em", { children: child });
1024
+ case "underline":
1025
+ return /* @__PURE__ */ jsx11("u", { children: child });
1026
+ case "strike":
1027
+ return /* @__PURE__ */ jsx11("s", { children: child });
1028
+ case "code":
1029
+ return /* @__PURE__ */ jsx11("code", { children: child });
1030
+ case "highlight": {
1031
+ const color = mark.attrs?.color;
1032
+ const style = typeof color === "string" && color.length > 0 ? { background: color } : void 0;
1033
+ return /* @__PURE__ */ jsx11("mark", { style, children: child });
1034
+ }
1035
+ case "textStyle": {
1036
+ const color = mark.attrs?.color;
1037
+ if (typeof color !== "string" || color.length === 0) {
1038
+ return /* @__PURE__ */ jsx11(Fragment3, { children: child });
1039
+ }
1040
+ return /* @__PURE__ */ jsx11("span", { style: { color }, children: child });
1041
+ }
1042
+ case "link": {
1043
+ const rawHref = mark.attrs?.href;
1044
+ const href = isSafeHref(rawHref) ? rawHref : "#";
1045
+ return /* @__PURE__ */ jsx11("a", { href, rel: "noopener noreferrer", target: "_blank", children: child });
1046
+ }
1047
+ default:
1048
+ return /* @__PURE__ */ jsx11("span", { children: child });
1049
+ }
1050
+ }
1051
+ function renderTextNode(node, key) {
1052
+ const text = node.text ?? "";
1053
+ const marks = (node.marks ?? []).flatMap((value) => {
1054
+ const mark = parseTiptapMark(value);
1055
+ return mark ? [mark] : [];
1056
+ });
1057
+ if (marks.length === 0) return text;
1058
+ let element = text;
1059
+ for (const mark of marks) {
1060
+ element = applyMark(mark, element);
1061
+ }
1062
+ return /* @__PURE__ */ jsx11("span", { children: element }, key);
1063
+ }
1064
+
1065
+ // src/server/tiptap-node-renderer.tsx
1066
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1067
+ function boundContentText(node, fields) {
1068
+ return bindingTextValue(
1069
+ boundFieldValue(node.attrs?.bindings, "content", fields)
1070
+ );
1071
+ }
1072
+ function alignStyle(node) {
1073
+ const align = node.attrs?.textAlign;
1074
+ if (typeof align !== "string" || align === "" || align === "left") {
1075
+ return void 0;
1076
+ }
1077
+ if (align === "center" || align === "right" || align === "justify") {
1078
+ return { textAlign: align };
1079
+ }
1080
+ return void 0;
1081
+ }
1082
+ function tableCellSpans(node) {
1083
+ const colspan = Number(node.attrs?.colspan);
1084
+ const rowspan = Number(node.attrs?.rowspan);
1085
+ return {
1086
+ colSpan: Number.isFinite(colspan) && colspan > 1 ? colspan : void 0,
1087
+ rowSpan: Number.isFinite(rowspan) && rowspan > 1 ? rowspan : void 0
1088
+ };
1089
+ }
1090
+ function renderTiptapNode(value, key, context) {
1091
+ const node = parseTiptapNode(value);
1092
+ if (!node) return null;
1093
+ const children = node.content?.map(
1094
+ (child, index) => renderTiptapNode(child, index, context)
1095
+ );
1096
+ switch (node.type) {
1097
+ case "paragraph": {
1098
+ const bound = boundContentText(node, context.fields);
1099
+ return /* @__PURE__ */ jsx12("p", { style: alignStyle(node), children: bound ?? children }, key);
1100
+ }
1101
+ case "heading": {
1102
+ const rawLevel = Number(node.attrs?.level ?? 2);
1103
+ const finiteLevel = Number.isFinite(rawLevel) ? rawLevel : 2;
1104
+ const level = Math.min(Math.max(finiteLevel, 2), 4);
1105
+ const rawId = node.attrs?.id;
1106
+ const id = typeof rawId === "string" && rawId.length > 0 ? rawId : void 0;
1107
+ const style = alignStyle(node);
1108
+ const inner = boundContentText(node, context.fields) ?? children;
1109
+ if (level === 2) {
1110
+ return /* @__PURE__ */ jsx12("h2", { id, style, children: inner }, key);
1111
+ }
1112
+ if (level === 3) {
1113
+ return /* @__PURE__ */ jsx12("h3", { id, style, children: inner }, key);
1114
+ }
1115
+ return /* @__PURE__ */ jsx12("h4", { id, style, children: inner }, key);
1116
+ }
1117
+ case "bulletList":
1118
+ return /* @__PURE__ */ jsx12("ul", { children }, key);
1119
+ case "orderedList":
1120
+ return /* @__PURE__ */ jsx12("ol", { children }, key);
1121
+ case "listItem":
1122
+ return /* @__PURE__ */ jsx12("li", { children }, key);
1123
+ case "blockquote":
1124
+ return /* @__PURE__ */ jsx12("blockquote", { children }, key);
1125
+ case "codeBlock":
1126
+ return /* @__PURE__ */ jsx12("pre", { children: /* @__PURE__ */ jsx12("code", { children }) }, key);
1127
+ case "horizontalRule":
1128
+ return /* @__PURE__ */ jsx12("hr", {}, key);
1129
+ case "hardBreak":
1130
+ return /* @__PURE__ */ jsx12("br", {}, key);
1131
+ case "image": {
1132
+ const boundSrc = bindingSrcValue(
1133
+ boundFieldValue(node.attrs?.bindings, "src", context.fields)
1134
+ );
1135
+ const src = boundSrc ?? node.attrs?.src;
1136
+ if (typeof src !== "string" || src.length === 0) return null;
1137
+ const alt = normalizeImageAlt(node.attrs?.alt);
1138
+ const title = typeof node.attrs?.title === "string" ? node.attrs.title : void 0;
1139
+ const width = typeof node.attrs?.width === "number" && node.attrs.width > 0 ? node.attrs.width : void 0;
1140
+ const height = typeof node.attrs?.height === "number" && node.attrs.height > 0 ? node.attrs.height : void 0;
1141
+ return /* @__PURE__ */ jsx12(
1142
+ "img",
1143
+ {
1144
+ src,
1145
+ alt,
1146
+ title,
1147
+ width,
1148
+ height,
1149
+ loading: "lazy"
1150
+ },
1151
+ key
1152
+ );
1153
+ }
1154
+ case "columns":
1155
+ return /* @__PURE__ */ jsx12("div", { className: "rt-columns", children }, key);
1156
+ case "column":
1157
+ return /* @__PURE__ */ jsx12("div", { className: "rt-column", children }, key);
1158
+ case "table":
1159
+ return /* @__PURE__ */ jsx12("div", { className: "rt-table-wrap", children: /* @__PURE__ */ jsx12("table", { className: "rt-table", children: /* @__PURE__ */ jsx12("tbody", { children }) }) }, key);
1160
+ case "tableRow":
1161
+ return /* @__PURE__ */ jsx12("tr", { children }, key);
1162
+ case "tableHeader": {
1163
+ const { colSpan, rowSpan } = tableCellSpans(node);
1164
+ return /* @__PURE__ */ jsx12("th", { colSpan, rowSpan, children }, key);
1165
+ }
1166
+ case "tableCell": {
1167
+ const { colSpan, rowSpan } = tableCellSpans(node);
1168
+ return /* @__PURE__ */ jsx12("td", { colSpan, rowSpan, children }, key);
1169
+ }
1170
+ case "embed":
1171
+ return renderEmbedNode(node, key);
1172
+ case "faq":
1173
+ return /* @__PURE__ */ jsx12("div", { "data-roottale-cms": "faq", className: "rt-cms-faq", children }, key);
1174
+ case "faqItem":
1175
+ return /* @__PURE__ */ jsx12("details", { className: "rt-cms-faq-item", children }, key);
1176
+ case "faqQuestion":
1177
+ return /* @__PURE__ */ jsx12("summary", { className: "rt-cms-faq-q", children }, key);
1178
+ case "faqAnswer":
1179
+ return /* @__PURE__ */ jsx12("div", { className: "rt-cms-faq-a", children }, key);
1180
+ case "references": {
1181
+ const rawTitle = node.attrs?.title;
1182
+ const title = typeof rawTitle === "string" && rawTitle.trim() ? rawTitle.trim() : "\uACF5\uC2DD \uCD9C\uCC98";
1183
+ const titleId = `rt-cms-references-${key}`;
1184
+ return /* @__PURE__ */ jsxs9(
1185
+ "section",
1186
+ {
1187
+ "data-roottale-cms": "references",
1188
+ className: "rt-cms-references",
1189
+ "aria-labelledby": titleId,
1190
+ children: [
1191
+ /* @__PURE__ */ jsx12("h2", { id: titleId, className: "rt-cms-references__title", children: title }),
1192
+ children
1193
+ ]
1194
+ },
1195
+ key
1196
+ );
1197
+ }
1198
+ case "tableOfContents":
1199
+ return null;
1200
+ case "section": {
1201
+ const renderSection = getSectionNodeRenderer();
1202
+ if (!renderSection) {
1203
+ if (context.section.isDev()) {
1204
+ console.warn(
1205
+ "[cms-renderer-next] section renderer\uAC00 \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC544 \uB178\uB4DC\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4. \uACF5\uAC1C CMS runtime\uC740 \uB9C8\uCF00\uD305 section \uAD6C\uD604\uC744 \uD3EC\uD568\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
1206
+ );
1207
+ }
1208
+ return null;
1209
+ }
1210
+ return renderSection(node, key, context.section);
1211
+ }
1212
+ case "text":
1213
+ return renderTextNode(node, key);
1214
+ default:
1215
+ return children ? /* @__PURE__ */ jsx12("span", { children }, key) : null;
1216
+ }
1217
+ }
1218
+
1219
+ // src/server/render-tiptap.tsx
1220
+ import { Fragment as Fragment4, jsx as jsx13 } from "react/jsx-runtime";
1221
+ function isDevelopmentRuntime() {
1222
+ const processValue = Reflect.get(globalThis, "process");
1223
+ if (!isRecord(processValue)) return true;
1224
+ const env = processValue.env;
1225
+ if (!isRecord(env)) return true;
1226
+ return env.NODE_ENV !== "production";
1227
+ }
1228
+ function createSectionContextWithBlogListNetworkFetch(apiKey, baseUrl) {
1229
+ const cms = apiKey ? { apiKey, baseUrl } : null;
1230
+ return {
1231
+ cms,
1232
+ isDev: isDevelopmentRuntime,
1233
+ renderBlogList: (props) => /* @__PURE__ */ jsx13(RootTaleBlogList, { ...props })
1234
+ };
1235
+ }
1236
+ function contentNodes(doc) {
1237
+ return Array.isArray(doc.content) ? doc.content : [];
1238
+ }
1239
+ function RenderTiptap({
1240
+ doc,
1241
+ fields,
1242
+ apiKey,
1243
+ baseUrl
1244
+ }) {
1245
+ const content = contentNodes(doc);
1246
+ const hasTableOfContents = content.some(
1247
+ (value) => parseTiptapNode(value)?.type === "tableOfContents"
1248
+ );
1249
+ const entries = hasTableOfContents ? extractToc(doc) : [];
1250
+ const renderDoc = hasTableOfContents && entries.length > 0 ? attachHeadingIds(doc, entries) : doc;
1251
+ const context = {
1252
+ fields: fields ?? null,
1253
+ section: createSectionContextWithBlogListNetworkFetch(apiKey, baseUrl)
1254
+ };
1255
+ return /* @__PURE__ */ jsx13(Fragment4, { children: contentNodes(renderDoc).map((value, index) => {
1256
+ const node = parseTiptapNode(value);
1257
+ if (node?.type !== "tableOfContents") {
1258
+ return renderTiptapNode(value, index, context);
1259
+ }
1260
+ const title = node.attrs?.title;
1261
+ return /* @__PURE__ */ jsx13(
1262
+ RootTaleTableOfContents,
1263
+ {
1264
+ entries,
1265
+ title: typeof title === "string" ? title : "\uBAA9\uCC28"
1266
+ },
1267
+ index
1268
+ );
1269
+ }) });
1270
+ }
1271
+
1272
+ // src/server/blog-post-body.tsx
1273
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1274
+ function BlogPostBody(props) {
1275
+ const {
1276
+ doc,
1277
+ fields,
1278
+ apiKey,
1279
+ baseUrl,
1280
+ toc,
1281
+ tocTitle,
1282
+ tocPosition
1283
+ } = props;
1284
+ const tableOfContents = toc.length > 0 ? /* @__PURE__ */ jsx14(RootTaleTableOfContents, { entries: toc, title: tocTitle }) : null;
1285
+ const body = /* @__PURE__ */ jsx14("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx14(
1286
+ RenderTiptap,
1287
+ {
1288
+ doc,
1289
+ fields,
1290
+ apiKey,
1291
+ baseUrl
1292
+ }
1293
+ ) });
1294
+ if (tocPosition === "sidebar" && tableOfContents) {
1295
+ return /* @__PURE__ */ jsxs10("div", { className: "rt-cms-layout rt-cms-layout--toc-side", children: [
1296
+ body,
1297
+ /* @__PURE__ */ jsx14("aside", { className: "rt-cms-toc-side", children: tableOfContents })
1298
+ ] });
1299
+ }
1300
+ return /* @__PURE__ */ jsxs10(Fragment5, { children: [
1301
+ tableOfContents,
1302
+ body
1303
+ ] });
1304
+ }
1305
+
1306
+ // src/server/blog-post-breadcrumb.tsx
1307
+ import {
1308
+ breadcrumbSchema,
1309
+ jsonLdJson
1310
+ } from "@roottale/cms-client/server";
1311
+ import {
1312
+ resolvePostCollection as resolvePostCollection2
1313
+ } from "@roottale/cms-core";
1314
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1315
+ function absoluteUrl(siteUrl, path) {
1316
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1317
+ return `${trimSlash(siteUrl)}${normalizedPath}`;
1318
+ }
1319
+ function renderBlogPostBreadcrumb(input) {
1320
+ const { post, collections, postHref, options } = input;
1321
+ if (!options) return null;
1322
+ const {
1323
+ siteUrl,
1324
+ homeLabel = "\uD648",
1325
+ homeHref = "/",
1326
+ listLabel = "\uBE14\uB85C\uADF8",
1327
+ showCategory = true,
1328
+ categoryHref = defaultCategoryHref
1329
+ } = options;
1330
+ const listHref = options.listHref ?? (collections ? resolvePostCollection2(post, collections)?.basePath || "/blog" : "/blog");
1331
+ const categoryTerm = showCategory ? post.terms.find((term) => term.taxonomy === "category") : void 0;
1332
+ const crumbs = [
1333
+ { label: homeLabel, href: homeHref },
1334
+ { label: listLabel, href: listHref }
1335
+ ];
1336
+ if (categoryTerm) {
1337
+ crumbs.push({
1338
+ label: categoryTerm.name,
1339
+ href: categoryHref(categoryTerm.slug)
1340
+ });
1341
+ }
1342
+ crumbs.push({ label: post.title, href: postHref(post) });
1343
+ const jsonLd = siteUrl ? breadcrumbSchema(
1344
+ crumbs.map((crumb) => ({
1345
+ name: crumb.label,
1346
+ url: absoluteUrl(siteUrl, crumb.href)
1347
+ }))
1348
+ ) : null;
1349
+ return /* @__PURE__ */ jsxs11(Fragment6, { children: [
1350
+ /* @__PURE__ */ jsx15("nav", { "aria-label": "\uBE0C\uB808\uB4DC\uD06C\uB7FC", className: "rt-cms-breadcrumb", children: /* @__PURE__ */ jsx15("ol", { children: crumbs.map(
1351
+ (crumb, index) => index === crumbs.length - 1 ? /* @__PURE__ */ jsx15("li", { "aria-current": "page", children: crumb.label }, index) : /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsx15("a", { href: crumb.href, children: crumb.label }) }, index)
1352
+ ) }) }),
1353
+ jsonLd ? /* @__PURE__ */ jsx15(
1354
+ "script",
1355
+ {
1356
+ type: "application/ld+json",
1357
+ dangerouslySetInnerHTML: { __html: jsonLdJson(jsonLd) }
1358
+ }
1359
+ ) : null
1360
+ ] });
1361
+ }
1362
+
1363
+ // src/server/blog-post-footer.tsx
1364
+ import {
1365
+ jsonLdJson as jsonLdJson2
1366
+ } from "@roottale/cms-client/server";
1367
+ import { extractFaqEntries, faqPageJsonLd } from "@roottale/cms-core";
1368
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1369
+ function renderPostCta(cta) {
1370
+ if (!cta) return null;
1371
+ const href = cta.buttonHref.trim();
1372
+ const isSafeHref2 = href.startsWith("/") || href.startsWith("#") || /^(https?:|mailto:|tel:)/i.test(href);
1373
+ if (!isSafeHref2) return null;
1374
+ const isExternal = /^https?:/i.test(href);
1375
+ return /* @__PURE__ */ jsxs12("aside", { "data-roottale-cms": "post-cta", className: "rt-cms-post-cta", children: [
1376
+ cta.title ? /* @__PURE__ */ jsx16("p", { className: "rt-cms-post-cta__title", children: cta.title }) : null,
1377
+ cta.description ? /* @__PURE__ */ jsx16("p", { className: "rt-cms-post-cta__desc", children: cta.description }) : null,
1378
+ /* @__PURE__ */ jsx16(
1379
+ "a",
1380
+ {
1381
+ className: "rt-cms-post-cta__btn",
1382
+ href,
1383
+ target: isExternal ? "_blank" : void 0,
1384
+ rel: isExternal ? "noopener noreferrer" : void 0,
1385
+ children: cta.buttonLabel
1386
+ }
1387
+ )
1388
+ ] });
1389
+ }
1390
+ function renderRelatedPosts(posts, href, title) {
1391
+ if (posts.length === 0) return null;
1392
+ return /* @__PURE__ */ jsxs12("nav", { "data-roottale-cms": "related", className: "rt-cms-related", "aria-label": title, children: [
1393
+ /* @__PURE__ */ jsx16("h2", { className: "rt-cms-related-title", children: title }),
1394
+ /* @__PURE__ */ jsx16("ul", { className: "rt-cms-related-list", children: posts.map((post) => /* @__PURE__ */ jsx16("li", { className: "rt-cms-related-item", children: /* @__PURE__ */ jsxs12("a", { className: "rt-cms-related-link", href: href(post), children: [
1395
+ post.featuredImageUrl ? /* @__PURE__ */ jsx16(
1396
+ "img",
1397
+ {
1398
+ className: "rt-cms-related-thumb",
1399
+ src: post.featuredImageUrl,
1400
+ alt: "",
1401
+ loading: "lazy"
1402
+ }
1403
+ ) : null,
1404
+ /* @__PURE__ */ jsx16("span", { className: "rt-cms-related-post-title", children: post.title })
1405
+ ] }) }, post.id)) })
1406
+ ] });
1407
+ }
1408
+ function renderFaqJsonLd(doc) {
1409
+ const jsonLd = faqPageJsonLd(extractFaqEntries(doc));
1410
+ if (!jsonLd) return null;
1411
+ return /* @__PURE__ */ jsx16(
1412
+ "script",
1413
+ {
1414
+ type: "application/ld+json",
1415
+ dangerouslySetInnerHTML: { __html: jsonLdJson2(jsonLd) }
1416
+ }
1417
+ );
1418
+ }
1419
+ function RootTaleFaqJsonLd({
1420
+ doc
1421
+ }) {
1422
+ return renderFaqJsonLd(doc);
1423
+ }
1424
+
1425
+ // src/server/blog-post.tsx
1426
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1427
+ async function fetchRelatedPostsFailSoft(input) {
1428
+ if (input.count <= 0) return [];
1429
+ const candidates = await fetchPosts3({
1430
+ apiKey: input.apiKey,
1431
+ baseUrl: input.baseUrl,
1432
+ type: "post",
1433
+ limit: 30,
1434
+ ...input.locale ? { locale: input.locale } : {}
1435
+ }).catch(() => null);
1436
+ if (!candidates) return [];
1437
+ return selectRelatedPosts(input.post, candidates.items, input.count);
1438
+ }
1439
+ function authorInitials(name) {
1440
+ const trimmed = name.trim();
1441
+ if (!trimmed) return "";
1442
+ const parts = trimmed.split(/\s+/).filter(Boolean);
1443
+ if (parts.length >= 2) {
1444
+ return `${parts[0]?.[0] ?? ""}${parts[1]?.[0] ?? ""}`.toUpperCase();
1445
+ }
1446
+ return [...trimmed].slice(0, 2).join("").toUpperCase();
1447
+ }
1448
+ function formatPostDate(iso) {
1449
+ const date = new Date(iso);
1450
+ if (Number.isNaN(date.getTime())) return "";
1451
+ return new Intl.DateTimeFormat("ko-KR", {
1452
+ year: "numeric",
1453
+ month: "2-digit",
1454
+ day: "2-digit"
1455
+ }).format(date);
1456
+ }
1457
+ function distinctModifiedDate(publishedAt, updatedAt) {
1458
+ const published = formatPostDate(publishedAt);
1459
+ const updated = formatPostDate(updatedAt);
1460
+ if (!updated || updated === published) return null;
1461
+ return updated;
1462
+ }
1463
+ function readEditorialReview(metaJson) {
1464
+ if (!metaJson || typeof metaJson !== "object") return null;
1465
+ const raw = metaJson.editorialReview;
1466
+ if (!raw || typeof raw !== "object") return null;
1467
+ const value = raw;
1468
+ if (typeof value.reviewerName !== "string" || typeof value.reviewedAt !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value.reviewedAt)) {
1469
+ return null;
1470
+ }
1471
+ return { reviewerName: value.reviewerName, reviewedAt: value.reviewedAt };
1472
+ }
1473
+ function authorImageRadiusCss(radius) {
1474
+ if (radius === "circle") return "999px";
1475
+ if (radius === "rounded") return "0.875rem";
1476
+ return "0";
1477
+ }
1478
+ async function RootTaleBlogPost(props) {
1479
+ const {
1480
+ apiKey,
1481
+ slugOrId,
1482
+ baseUrl,
1483
+ notFoundElement,
1484
+ showTitle = true,
1485
+ showTableOfContents,
1486
+ tableOfContentsTitle,
1487
+ relatedPostsCount = 0,
1488
+ locale,
1489
+ relatedPostsTitle = t(locale, "relatedPostsTitle"),
1490
+ collections,
1491
+ showFieldFacts = false,
1492
+ theme: themeProp,
1493
+ authorHref = defaultAuthorHref,
1494
+ breadcrumb
1495
+ } = props;
1496
+ const [post, themeStyle, blogSettings] = await Promise.all([
1497
+ fetchPost({ apiKey, slugOrId, baseUrl, ...locale ? { locale } : {} }),
1498
+ resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),
1499
+ fetchBlogSettingsFailSoft({ apiKey, baseUrl })
1500
+ ]);
1501
+ if (!post) {
1502
+ return notFoundElement ?? /* @__PURE__ */ jsx17("div", { "data-roottale-cms": "post-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx17("p", { className: "rt-cms-empty", children: t(locale, "postNotFound") }) });
1503
+ }
1504
+ const display = resolvePostDisplay2(post, blogSettings, {
1505
+ showTableOfContents,
1506
+ tableOfContentsTitle
1507
+ });
1508
+ const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];
1509
+ const renderDoc = toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;
1510
+ const related = await fetchRelatedPostsFailSoft({
1511
+ post,
1512
+ apiKey,
1513
+ baseUrl,
1514
+ count: relatedPostsCount,
1515
+ locale
1516
+ });
1517
+ const relatedHref = props.postHref ?? (collections ? makeCollectionPostHref(collections) : defaultPostHref);
1518
+ const authorName = post.authorName;
1519
+ const authorSlug = post.authorSlug;
1520
+ const views = typeof post.viewCount === "number" ? post.viewCount : null;
1521
+ const publishedDate = display.showDate ? formatPostDate(post.publishedAt) : "";
1522
+ const modifiedDate = display.showDate ? distinctModifiedDate(post.publishedAt, post.updatedAt) : null;
1523
+ const showTopAuthor = display.showAuthor && Boolean(authorName);
1524
+ const categoryHref = breadcrumb?.categoryHref ?? defaultCategoryHref;
1525
+ const categories = breadcrumb?.showCategory === false ? [] : post.terms.filter((term) => term.taxonomy === "category");
1526
+ const authorBio = post.authorBio;
1527
+ const authorImageUrl = post.authorImageUrl;
1528
+ const authorImageAlt = `${authorName ?? ""} \uD504\uB85C\uD544 \uC0AC\uC9C4`.trim();
1529
+ const authorImageStyle = {
1530
+ borderRadius: authorImageRadiusCss(display.authorProfileImageRadius),
1531
+ objectPosition: `${display.authorProfileImagePositionX}% ${display.authorProfileImagePositionY}%`
1532
+ };
1533
+ const showAuthorCard = display.showAuthorCard && Boolean(authorName);
1534
+ const editorialReview = readEditorialReview(post.metaJson);
1535
+ const reviewedDate = editorialReview ? formatPostDate(editorialReview.reviewedAt) : "";
1536
+ return /* @__PURE__ */ jsxs13(
1537
+ "article",
1538
+ {
1539
+ "data-roottale-cms": "post",
1540
+ className: "rt-cms-article",
1541
+ style: themeStyle ?? void 0,
1542
+ children: [
1543
+ renderBlogPostBreadcrumb({
1544
+ post,
1545
+ collections,
1546
+ postHref: relatedHref,
1547
+ options: breadcrumb
1548
+ }),
1549
+ /* @__PURE__ */ jsxs13("header", { className: "rt-cms-post-header", children: [
1550
+ categories.length > 0 ? /* @__PURE__ */ jsx17("nav", { className: "rt-cms-post-categories", "aria-label": "\uAE00 \uCE74\uD14C\uACE0\uB9AC", children: categories.map((category) => /* @__PURE__ */ jsx17(
1551
+ "a",
1552
+ {
1553
+ className: "rt-cms-post-category",
1554
+ href: categoryHref(category.slug),
1555
+ children: category.name
1556
+ },
1557
+ category.id
1558
+ )) }) : null,
1559
+ showTitle ? /* @__PURE__ */ jsx17("h1", { className: "rt-cms-title", children: post.title }) : null,
1560
+ post.excerpt ? /* @__PURE__ */ jsx17("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null,
1561
+ publishedDate || modifiedDate || showTopAuthor || views !== null || reviewedDate ? /* @__PURE__ */ jsxs13("p", { className: "rt-cms-meta", children: [
1562
+ publishedDate ? /* @__PURE__ */ jsxs13(
1563
+ "time",
1564
+ {
1565
+ dateTime: post.publishedAt ?? void 0,
1566
+ className: "rt-cms-meta-item",
1567
+ "data-rt-cms-meta": "date",
1568
+ children: [
1569
+ "\uBC1C\uD589 ",
1570
+ publishedDate
1571
+ ]
1572
+ }
1573
+ ) : null,
1574
+ modifiedDate ? /* @__PURE__ */ jsxs13(
1575
+ "time",
1576
+ {
1577
+ dateTime: post.updatedAt,
1578
+ className: "rt-cms-meta-item",
1579
+ "data-rt-cms-meta": "modified",
1580
+ children: [
1581
+ "\uC218\uC815 ",
1582
+ modifiedDate
1583
+ ]
1584
+ }
1585
+ ) : null,
1586
+ showTopAuthor ? /* @__PURE__ */ jsxs13("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
1587
+ "\uC791\uC131\uC790",
1588
+ " ",
1589
+ authorSlug ? /* @__PURE__ */ jsx17("a", { className: "rt-cms-author-link", href: authorHref(authorSlug), children: authorName }) : authorName
1590
+ ] }) : null,
1591
+ editorialReview && reviewedDate ? /* @__PURE__ */ jsxs13("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "reviewed", children: [
1592
+ "\uB0B4\uC6A9 \uAC80\uD1A0 ",
1593
+ reviewedDate,
1594
+ " \xB7 ",
1595
+ editorialReview.reviewerName
1596
+ ] }) : null,
1597
+ views !== null ? /* @__PURE__ */ jsxs13("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "views", children: [
1598
+ "\uC870\uD68C ",
1599
+ views.toLocaleString("ko-KR")
1600
+ ] }) : null
1601
+ ] }) : null
1602
+ ] }),
1603
+ renderArchiveRelated(post.archiveRelated, collections),
1604
+ showFieldFacts ? renderFieldFacts(post) : null,
1605
+ /* @__PURE__ */ jsx17(
1606
+ BlogPostBody,
1607
+ {
1608
+ doc: renderDoc,
1609
+ fields: post.fields,
1610
+ apiKey,
1611
+ baseUrl,
1612
+ toc,
1613
+ tocTitle: display.tocTitle,
1614
+ tocPosition: display.tocPosition
1615
+ }
1616
+ ),
1617
+ showAuthorCard && authorName ? /* @__PURE__ */ jsxs13("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
1618
+ authorImageUrl ? /* @__PURE__ */ jsx17(
1619
+ "img",
1620
+ {
1621
+ className: "rt-cms-author-card__image",
1622
+ src: authorImageUrl,
1623
+ alt: authorImageAlt,
1624
+ loading: "lazy",
1625
+ style: authorImageStyle
1626
+ }
1627
+ ) : /* @__PURE__ */ jsx17("div", { className: "rt-cms-author-card__avatar", "aria-hidden": "true", children: authorInitials(authorName) }),
1628
+ /* @__PURE__ */ jsxs13("div", { className: "rt-cms-author-card__body", children: [
1629
+ /* @__PURE__ */ jsx17("p", { className: "rt-cms-author-card__eyebrow", children: display.authorCardEyebrow }),
1630
+ /* @__PURE__ */ jsx17("p", { className: "rt-cms-author-card__name", children: authorSlug ? /* @__PURE__ */ jsx17("a", { className: "rt-cms-author-link", href: authorHref(authorSlug), children: authorName }) : authorName }),
1631
+ authorBio ? /* @__PURE__ */ jsx17("p", { className: "rt-cms-author-card__description", children: authorBio }) : null
1632
+ ] })
1633
+ ] }) : null,
1634
+ renderRelatedPosts(related, relatedHref, relatedPostsTitle),
1635
+ renderPostCta(blogSettings.postCta),
1636
+ renderFaqJsonLd(post.bodyJson)
1637
+ ]
1638
+ }
1639
+ );
1640
+ }
1641
+
1642
+ // src/server/page.tsx
1643
+ import { fetchPost as fetchPost2 } from "@roottale/cms-client/server";
1644
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1645
+ async function RootTalePage(props) {
1646
+ const {
1647
+ apiKey,
1648
+ slugOrId,
1649
+ baseUrl,
1650
+ notFoundElement,
1651
+ showTitle = true,
1652
+ theme: themeProp,
1653
+ locale
1654
+ } = props;
1655
+ const [post, themeStyle] = await Promise.all([
1656
+ fetchPost2({ apiKey, slugOrId, baseUrl, ...locale ? { locale } : {} }),
1657
+ resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl })
1658
+ ]);
1659
+ if (!post) {
1660
+ return notFoundElement ?? /* @__PURE__ */ jsx18("div", { "data-roottale-cms": "page-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx18("p", { className: "rt-cms-empty", children: t(locale, "pageNotFound") }) });
1661
+ }
1662
+ return /* @__PURE__ */ jsxs14(
1663
+ "article",
1664
+ {
1665
+ "data-roottale-cms": "page",
1666
+ className: "rt-cms-article",
1667
+ style: themeStyle ?? void 0,
1668
+ children: [
1669
+ showTitle ? /* @__PURE__ */ jsx18("header", { className: "rt-cms-post-header", children: /* @__PURE__ */ jsx18("h1", { className: "rt-cms-title", children: post.title }) }) : null,
1670
+ /* @__PURE__ */ jsx18("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx18(
1671
+ RenderTiptap,
1672
+ {
1673
+ doc: post.bodyJson,
1674
+ fields: post.fields,
1675
+ apiKey,
1676
+ baseUrl
1677
+ }
1678
+ ) })
1679
+ ]
1680
+ }
1681
+ );
1682
+ }
38
1683
  export {
39
1684
  RenderTiptap,
40
1685
  RootTaleBlogCategories,
@@ -57,6 +1702,6 @@ export {
57
1702
  renderBlock,
58
1703
  renderBlocks,
59
1704
  renderBoundBlocks,
60
- themeToCssVars
1705
+ themeToCssVars2 as themeToCssVars
61
1706
  };
62
1707
  //# sourceMappingURL=server.js.map