@roottale/cms-renderer-next 0.52.0 → 0.53.1

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