@roottale/cms-renderer-next 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,672 @@
1
+ // src/server.tsx
2
+ import {
3
+ fetchPost,
4
+ fetchPosts,
5
+ fetchTheme
6
+ } from "@roottale/cms-client/server";
7
+
8
+ // src/PostCard.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function RootTalePostCard(props) {
11
+ const { post, href = defaultHref } = props;
12
+ 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: [
13
+ /* @__PURE__ */ jsx("p", { className: "rt-cms-meta", children: formatPublishedDate(post.publishedAt) }),
14
+ /* @__PURE__ */ jsx("h2", { className: "rt-cms-title", children: post.title }),
15
+ post.excerpt ? /* @__PURE__ */ jsx("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
16
+ ] }) });
17
+ }
18
+ function defaultHref(post) {
19
+ return `/blog/${post.slug}`;
20
+ }
21
+ function formatPublishedDate(iso) {
22
+ const d = new Date(iso);
23
+ if (Number.isNaN(d.getTime())) return "";
24
+ return new Intl.DateTimeFormat("ko-KR", {
25
+ year: "numeric",
26
+ month: "2-digit",
27
+ day: "2-digit"
28
+ }).format(d);
29
+ }
30
+
31
+ // src/TableOfContents.tsx
32
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
33
+ function RootTaleTableOfContents(props) {
34
+ const { entries, title = "\uBAA9\uCC28" } = props;
35
+ if (entries.length === 0) return null;
36
+ return /* @__PURE__ */ jsxs2("nav", { "data-roottale-cms": "toc", className: "rt-cms-toc", "aria-label": title, children: [
37
+ title ? /* @__PURE__ */ jsx2("p", { className: "rt-cms-toc-title", children: title }) : null,
38
+ /* @__PURE__ */ jsx2("ol", { className: "rt-cms-toc-list", children: entries.map((entry) => /* @__PURE__ */ jsx2(
39
+ "li",
40
+ {
41
+ className: `rt-cms-toc-item rt-cms-toc-level-${entry.level}`,
42
+ children: /* @__PURE__ */ jsx2("a", { href: `#${entry.id}`, children: entry.text })
43
+ },
44
+ entry.id
45
+ )) })
46
+ ] });
47
+ }
48
+
49
+ // src/toc.ts
50
+ function headingToId(text) {
51
+ return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
52
+ }
53
+ function textOf(node) {
54
+ if (typeof node.text === "string") return node.text;
55
+ if (!Array.isArray(node.content)) return "";
56
+ return node.content.map(textOf).join("");
57
+ }
58
+ function extractToc(doc) {
59
+ const root = doc;
60
+ const content = Array.isArray(root.content) ? root.content : [];
61
+ const entries = [];
62
+ const idCounts = /* @__PURE__ */ new Map();
63
+ for (const node of content) {
64
+ if (node?.type !== "heading") continue;
65
+ const rawLevel = Number(node.attrs?.level ?? 2);
66
+ if (rawLevel !== 2 && rawLevel !== 3) continue;
67
+ const text = textOf(node).trim();
68
+ if (!text) continue;
69
+ const base = headingToId(text) || "heading";
70
+ const count = idCounts.get(base) ?? 0;
71
+ const id = count === 0 ? base : `${base}-${count}`;
72
+ idCounts.set(base, count + 1);
73
+ entries.push({ level: rawLevel, text, id });
74
+ }
75
+ return entries;
76
+ }
77
+ function attachHeadingIds(doc, entries) {
78
+ const root = doc;
79
+ const content = Array.isArray(root.content) ? root.content : [];
80
+ let cursor = 0;
81
+ const nextContent = content.map((node) => {
82
+ if (node?.type !== "heading") return node;
83
+ const rawLevel = Number(node.attrs?.level ?? 2);
84
+ if (rawLevel !== 2 && rawLevel !== 3) return node;
85
+ if (!textOf(node).trim()) return node;
86
+ const entry = entries[cursor++];
87
+ if (!entry) return node;
88
+ return {
89
+ ...node,
90
+ attrs: { ...node.attrs ?? {}, id: entry.id }
91
+ };
92
+ });
93
+ return { ...root, content: nextContent };
94
+ }
95
+
96
+ // src/FloatingCta.tsx
97
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
98
+ function RootTaleFloatingCta(props) {
99
+ const { buttons, position = "bottom-right" } = props;
100
+ if (!buttons || buttons.length === 0) return null;
101
+ return /* @__PURE__ */ jsx3(
102
+ "div",
103
+ {
104
+ "data-roottale-cms": "floating-cta",
105
+ className: `rt-cms-floating-cta rt-cms-floating-cta--${position}`,
106
+ children: buttons.map((btn, idx) => {
107
+ const isExternal = btn.type === "kakao" || btn.type === "custom";
108
+ return /* @__PURE__ */ jsxs3(
109
+ "a",
110
+ {
111
+ href: btn.href,
112
+ className: `rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`,
113
+ target: isExternal ? "_blank" : void 0,
114
+ rel: isExternal ? "noopener noreferrer" : void 0,
115
+ children: [
116
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "rt-cms-floating-cta__icon", children: btn.icon ?? defaultIcon(btn.type) }),
117
+ /* @__PURE__ */ jsx3("span", { className: "rt-cms-floating-cta__label", children: btn.label })
118
+ ]
119
+ },
120
+ `${btn.type}-${idx}`
121
+ );
122
+ })
123
+ }
124
+ );
125
+ }
126
+ function defaultIcon(type) {
127
+ switch (type) {
128
+ case "phone":
129
+ return "\u260E";
130
+ case "contact":
131
+ return "\u2709";
132
+ case "kakao":
133
+ return "\u{1F4AC}";
134
+ case "custom":
135
+ return "\u2192";
136
+ }
137
+ }
138
+
139
+ // src/LeadForm.tsx
140
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
141
+ var VERTICAL_LABEL = {
142
+ consulting: "\uCEE8\uC124\uD305",
143
+ medical: "\uC758\uB8CC",
144
+ tax: "\uC138\uBB34",
145
+ legal: "\uBC95\uB960"
146
+ };
147
+ var VERTICAL_OPTIONS = [
148
+ { value: "consulting", label: "\uCEE8\uC124\uD305 / \uC77C\uBC18" },
149
+ { value: "medical", label: "\uC758\uB8CC (\uBCD1\uC758\uC6D0\xB7\uCE58\uACFC\xB7\uD55C\uC758\uC6D0)" },
150
+ { value: "tax", label: "\uC138\uBB34 (\uC138\uBB34\uC0AC\xB7\uD68C\uACC4\uC0AC)" },
151
+ { value: "legal", label: "\uBC95\uB960 (\uBC95\uBB34\uBC95\uC778\xB7\uBCC0\uD638\uC0AC)" }
152
+ ];
153
+ function RootTaleLeadForm(props) {
154
+ const {
155
+ action,
156
+ vertical,
157
+ redirectUrl,
158
+ heading,
159
+ description,
160
+ submitLabel = "\uC9C4\uB2E8 \uC2E0\uCCAD",
161
+ className
162
+ } = props;
163
+ const formClass = ["rt-cms-lead-form", className].filter(Boolean).join(" ");
164
+ return /* @__PURE__ */ jsxs4(
165
+ "form",
166
+ {
167
+ method: "post",
168
+ action,
169
+ "data-roottale-cms": "lead-form",
170
+ className: formClass,
171
+ children: [
172
+ heading ? /* @__PURE__ */ jsx4("h2", { className: "rt-cms-lead-heading", children: heading }) : null,
173
+ description ? /* @__PURE__ */ jsx4("p", { className: "rt-cms-lead-description", children: description }) : null,
174
+ redirectUrl ? /* @__PURE__ */ jsx4("input", { type: "hidden", name: "_redirect_url", value: redirectUrl }) : null,
175
+ vertical ? /* @__PURE__ */ jsxs4(Fragment, { children: [
176
+ /* @__PURE__ */ jsx4("input", { type: "hidden", name: "vertical", value: vertical }),
177
+ /* @__PURE__ */ jsxs4("p", { className: "rt-cms-lead-vertical-label", children: [
178
+ "\uBD84\uC57C: ",
179
+ /* @__PURE__ */ jsx4("strong", { children: VERTICAL_LABEL[vertical] })
180
+ ] })
181
+ ] }) : /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
182
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
183
+ "\uBD84\uC57C ",
184
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
185
+ ] }),
186
+ /* @__PURE__ */ jsxs4(
187
+ "select",
188
+ {
189
+ name: "vertical",
190
+ required: true,
191
+ className: "rt-cms-field-input rt-cms-field-select",
192
+ defaultValue: "",
193
+ children: [
194
+ /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: "\uC120\uD0DD\uD558\uC138\uC694" }),
195
+ VERTICAL_OPTIONS.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.value, children: opt.label }, opt.value))
196
+ ]
197
+ }
198
+ )
199
+ ] }),
200
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
201
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
202
+ "\uC774\uB984 ",
203
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
204
+ ] }),
205
+ /* @__PURE__ */ jsx4(
206
+ "input",
207
+ {
208
+ type: "text",
209
+ name: "contact_name",
210
+ required: true,
211
+ autoComplete: "name",
212
+ className: "rt-cms-field-input"
213
+ }
214
+ )
215
+ ] }),
216
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
217
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
218
+ "\uC0AC\uC5C5\uCCB4\uBA85 ",
219
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
220
+ ] }),
221
+ /* @__PURE__ */ jsx4(
222
+ "input",
223
+ {
224
+ type: "text",
225
+ name: "business_name",
226
+ required: true,
227
+ autoComplete: "organization",
228
+ className: "rt-cms-field-input"
229
+ }
230
+ )
231
+ ] }),
232
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
233
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
234
+ "\uC774\uBA54\uC77C ",
235
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
236
+ ] }),
237
+ /* @__PURE__ */ jsx4(
238
+ "input",
239
+ {
240
+ type: "email",
241
+ name: "email",
242
+ required: true,
243
+ autoComplete: "email",
244
+ className: "rt-cms-field-input"
245
+ }
246
+ )
247
+ ] }),
248
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
249
+ /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
250
+ "\uC804\uD654\uBC88\uD638 ",
251
+ /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
252
+ ] }),
253
+ /* @__PURE__ */ jsx4(
254
+ "input",
255
+ {
256
+ type: "tel",
257
+ name: "phone",
258
+ required: true,
259
+ autoComplete: "tel",
260
+ placeholder: "010-0000-0000",
261
+ className: "rt-cms-field-input"
262
+ }
263
+ )
264
+ ] }),
265
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
266
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-label", children: "\uD604\uC7AC \uC0AC\uC774\uD2B8 URL (\uC120\uD0DD)" }),
267
+ /* @__PURE__ */ jsx4(
268
+ "input",
269
+ {
270
+ type: "url",
271
+ name: "current_site_url",
272
+ placeholder: "https://",
273
+ className: "rt-cms-field-input"
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-hint", children: "\uC5C6\uC73C\uBA74 \uBE44\uC6CC\uB450\uC138\uC694." })
277
+ ] }),
278
+ /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
279
+ /* @__PURE__ */ jsx4("input", { type: "checkbox", name: "privacy_consent", required: true }),
280
+ /* @__PURE__ */ jsxs4("span", { children: [
281
+ /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218)" }),
282
+ " \uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9\uC5D0 \uB3D9\uC758\uD569\uB2C8\uB2E4. (\uC218\uC9D1 \uD56D\uBAA9: \uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7\uC774\uBA54\uC77C\xB7\uC0AC\uC5C5\uCCB4\uBA85. \uBCF4\uAD00 \uAE30\uAC04: \uBB38\uC758 \uC885\uB8CC \uD6C4 3\uB144)"
283
+ ] })
284
+ ] }),
285
+ vertical === "medical" ? /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
286
+ /* @__PURE__ */ jsx4(
287
+ "input",
288
+ {
289
+ type: "checkbox",
290
+ name: "overseas_transfer_consent",
291
+ required: true
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsxs4("span", { children: [
295
+ /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218, \uC758\uB8CC)" }),
296
+ " \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)"
297
+ ] })
298
+ ] }) : null,
299
+ /* @__PURE__ */ jsx4("div", { className: "rt-cms-lead-actions", children: /* @__PURE__ */ jsx4("button", { type: "submit", className: "rt-cms-lead-submit", children: submitLabel }) })
300
+ ]
301
+ }
302
+ );
303
+ }
304
+
305
+ // src/block-to-react.tsx
306
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
307
+ function isString(value) {
308
+ return typeof value === "string";
309
+ }
310
+ function htmlContent(html) {
311
+ return { __html: html };
312
+ }
313
+ function renderInner(blocks) {
314
+ return blocks.map((b, i) => renderBlock(b, i));
315
+ }
316
+ function renderBlock(block, key = 0) {
317
+ const dataBlockId = block._id;
318
+ switch (block.name) {
319
+ case "core/paragraph": {
320
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
321
+ return /* @__PURE__ */ jsx5(
322
+ "p",
323
+ {
324
+ "data-block-id": dataBlockId,
325
+ dangerouslySetInnerHTML: htmlContent(text)
326
+ },
327
+ key
328
+ );
329
+ }
330
+ case "core/heading": {
331
+ const levelRaw = block.attributes.level;
332
+ const level = typeof levelRaw === "number" && levelRaw >= 1 && levelRaw <= 6 ? levelRaw : 2;
333
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
334
+ const props = {
335
+ "data-block-id": dataBlockId,
336
+ dangerouslySetInnerHTML: htmlContent(text)
337
+ };
338
+ if (level === 1) return /* @__PURE__ */ jsx5("h1", { ...props }, key);
339
+ if (level === 2) return /* @__PURE__ */ jsx5("h2", { ...props }, key);
340
+ if (level === 3) return /* @__PURE__ */ jsx5("h3", { ...props }, key);
341
+ if (level === 4) return /* @__PURE__ */ jsx5("h4", { ...props }, key);
342
+ if (level === 5) return /* @__PURE__ */ jsx5("h5", { ...props }, key);
343
+ return /* @__PURE__ */ jsx5("h6", { ...props }, key);
344
+ }
345
+ case "core/image": {
346
+ const src = isString(block.attributes.url) ? block.attributes.url : "";
347
+ const alt = isString(block.attributes.alt) ? block.attributes.alt : "";
348
+ const caption = isString(block.attributes.caption) ? block.attributes.caption : null;
349
+ return /* @__PURE__ */ jsxs5("figure", { "data-block-id": dataBlockId, children: [
350
+ /* @__PURE__ */ jsx5("img", { src, alt, loading: "lazy" }),
351
+ caption && /* @__PURE__ */ jsx5("figcaption", { children: caption })
352
+ ] }, key);
353
+ }
354
+ case "core/list": {
355
+ const ordered = block.attributes.ordered === true;
356
+ const items = (block.innerBlocks ?? []).map(
357
+ (item, i) => item.rawHtml ? /* @__PURE__ */ jsx5(
358
+ "li",
359
+ {
360
+ dangerouslySetInnerHTML: htmlContent(item.rawHtml)
361
+ },
362
+ i
363
+ ) : /* @__PURE__ */ jsx5("li", { children: renderInner(item.innerBlocks ?? []) }, i)
364
+ );
365
+ return ordered ? /* @__PURE__ */ jsx5("ol", { "data-block-id": dataBlockId, children: items }, key) : /* @__PURE__ */ jsx5("ul", { "data-block-id": dataBlockId, children: items }, key);
366
+ }
367
+ case "core/quote": {
368
+ const inner = renderInner(block.innerBlocks ?? []);
369
+ const citation = isString(block.attributes.citation) ? block.attributes.citation : null;
370
+ return /* @__PURE__ */ jsxs5("blockquote", { "data-block-id": dataBlockId, children: [
371
+ inner,
372
+ citation && /* @__PURE__ */ jsx5("cite", { children: citation })
373
+ ] }, key);
374
+ }
375
+ case "core/code": {
376
+ const code = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
377
+ const lang = isString(block.attributes.language) ? block.attributes.language : void 0;
378
+ return /* @__PURE__ */ jsx5(
379
+ "pre",
380
+ {
381
+ "data-block-id": dataBlockId,
382
+ "data-language": lang,
383
+ children: /* @__PURE__ */ jsx5("code", { children: code })
384
+ },
385
+ key
386
+ );
387
+ }
388
+ case "core/columns": {
389
+ return /* @__PURE__ */ jsx5(
390
+ "div",
391
+ {
392
+ className: "rt-columns",
393
+ "data-block-id": dataBlockId,
394
+ children: renderInner(block.innerBlocks ?? [])
395
+ },
396
+ key
397
+ );
398
+ }
399
+ case "core/separator":
400
+ return /* @__PURE__ */ jsx5("hr", { "data-block-id": dataBlockId }, key);
401
+ case "core/spacer": {
402
+ const heightRaw = block.attributes.height;
403
+ const height = isString(heightRaw) ? heightRaw : "32px";
404
+ return /* @__PURE__ */ jsx5(
405
+ "div",
406
+ {
407
+ className: "rt-spacer",
408
+ "data-block-id": dataBlockId,
409
+ style: { height }
410
+ },
411
+ key
412
+ );
413
+ }
414
+ case "core/group": {
415
+ return /* @__PURE__ */ jsx5(
416
+ "div",
417
+ {
418
+ className: "rt-group",
419
+ "data-block-id": dataBlockId,
420
+ children: renderInner(block.innerBlocks ?? [])
421
+ },
422
+ key
423
+ );
424
+ }
425
+ default: {
426
+ const rawHtml = block.rawHtml;
427
+ return /* @__PURE__ */ jsx5(
428
+ "div",
429
+ {
430
+ className: "rt-unknown-block",
431
+ "data-block-id": dataBlockId,
432
+ "data-block-name": block.name,
433
+ "data-rawhtml-suppressed": rawHtml ? "true" : void 0,
434
+ children: renderInner(block.innerBlocks ?? [])
435
+ },
436
+ key
437
+ );
438
+ }
439
+ }
440
+ }
441
+ function renderBlocks(blocks) {
442
+ return blocks.map((b, i) => renderBlock(b, i));
443
+ }
444
+
445
+ // src/server.tsx
446
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
447
+ function RootTaleThemeProvider(props) {
448
+ const style = themeToCssVars(props.theme ?? null);
449
+ if (!style) return /* @__PURE__ */ jsx6(Fragment2, { children: props.children });
450
+ return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "theme-root", style, children: props.children });
451
+ }
452
+ async function RootTaleBlogList(props) {
453
+ const {
454
+ apiKey,
455
+ baseUrl,
456
+ limit,
457
+ type = "post",
458
+ postHref = defaultPostHref,
459
+ emptyMessage = "\uC544\uC9C1 \uBC1C\uD589\uB41C \uAE00\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
460
+ theme: themeProp
461
+ } = props;
462
+ const [page, themeStyle] = await Promise.all([
463
+ fetchPosts({ apiKey, baseUrl, limit, type }),
464
+ resolveThemeStyle({ themeProp, apiKey, baseUrl })
465
+ ]);
466
+ if (page.items.length === 0) {
467
+ return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx6("p", { className: "rt-cms-empty", children: emptyMessage }) });
468
+ }
469
+ return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx6("ul", { className: "rt-cms-list", children: page.items.map((post) => /* @__PURE__ */ jsx6("li", { className: "rt-cms-list-item", children: /* @__PURE__ */ jsx6(RootTalePostCard, { post, href: postHref }) }, post.id)) }) });
470
+ }
471
+ async function RootTaleBlogPost(props) {
472
+ const {
473
+ apiKey,
474
+ slugOrId,
475
+ baseUrl,
476
+ notFoundElement,
477
+ showTableOfContents = false,
478
+ tableOfContentsTitle,
479
+ theme: themeProp
480
+ } = props;
481
+ const [post, themeStyle] = await Promise.all([
482
+ fetchPost({ apiKey, slugOrId, baseUrl }),
483
+ resolveThemeStyle({ themeProp, apiKey, baseUrl })
484
+ ]);
485
+ if (!post) {
486
+ return notFoundElement ?? /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "post-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx6("p", { className: "rt-cms-empty", children: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." }) });
487
+ }
488
+ const toc = showTableOfContents ? extractToc(post.bodyJson) : [];
489
+ const renderDoc = toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;
490
+ return /* @__PURE__ */ jsxs6(
491
+ "article",
492
+ {
493
+ "data-roottale-cms": "post",
494
+ className: "rt-cms-article",
495
+ style: themeStyle ?? void 0,
496
+ children: [
497
+ /* @__PURE__ */ jsxs6("header", { children: [
498
+ /* @__PURE__ */ jsx6("p", { className: "rt-cms-meta", children: formatPublishedDate2(post.publishedAt) }),
499
+ /* @__PURE__ */ jsx6("h1", { className: "rt-cms-title", children: post.title }),
500
+ post.excerpt ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
501
+ ] }),
502
+ toc.length > 0 ? /* @__PURE__ */ jsx6(RootTaleTableOfContents, { entries: toc, title: tableOfContentsTitle }) : null,
503
+ /* @__PURE__ */ jsx6(RenderTiptap, { doc: renderDoc })
504
+ ]
505
+ }
506
+ );
507
+ }
508
+ async function resolveThemeStyle(input) {
509
+ const { themeProp, apiKey, baseUrl } = input;
510
+ if (themeProp === null) return null;
511
+ if (themeProp !== void 0) return themeToCssVars(themeProp);
512
+ try {
513
+ const fetched = await fetchTheme({ apiKey, baseUrl });
514
+ return themeToCssVars(fetched);
515
+ } catch {
516
+ return null;
517
+ }
518
+ }
519
+ function themeToCssVars(theme) {
520
+ if (!theme) return null;
521
+ const vars = {};
522
+ const c = theme.colors ?? {};
523
+ if (c.primary) vars["--rt-color-primary"] = c.primary;
524
+ if (c.primaryForeground) vars["--rt-color-primary-foreground"] = c.primaryForeground;
525
+ if (c.foreground) vars["--rt-color-foreground"] = c.foreground;
526
+ if (c.background) vars["--rt-color-background"] = c.background;
527
+ if (c.muted) vars["--rt-color-muted"] = c.muted;
528
+ if (c.mutedForeground) vars["--rt-color-muted-foreground"] = c.mutedForeground;
529
+ if (c.border) vars["--rt-color-border"] = c.border;
530
+ const f = theme.fonts ?? {};
531
+ if (f.body) vars["--rt-font-body"] = f.body;
532
+ if (f.display) vars["--rt-font-display"] = f.display;
533
+ const r = theme.radius ?? {};
534
+ if (r.md) vars["--rt-radius-md"] = r.md;
535
+ if (Object.keys(vars).length === 0) return null;
536
+ return vars;
537
+ }
538
+ function defaultPostHref(post) {
539
+ return `/blog/${post.slug}`;
540
+ }
541
+ function formatPublishedDate2(iso) {
542
+ const d = new Date(iso);
543
+ if (Number.isNaN(d.getTime())) return "";
544
+ return new Intl.DateTimeFormat("ko-KR", {
545
+ year: "numeric",
546
+ month: "2-digit",
547
+ day: "2-digit"
548
+ }).format(d);
549
+ }
550
+ function RenderTiptap({ doc }) {
551
+ const rawContent = doc.content;
552
+ const content = Array.isArray(rawContent) ? rawContent : [];
553
+ return /* @__PURE__ */ jsx6(Fragment2, { children: content.map((node, i) => renderNode(node, i)) });
554
+ }
555
+ function isSafeHref(value) {
556
+ if (typeof value !== "string") return false;
557
+ const v = value.trim();
558
+ if (v.length === 0) return false;
559
+ if (v.startsWith("/") || v.startsWith("#") || v.startsWith("?")) return true;
560
+ return /^(https?:|mailto:|tel:)/i.test(v);
561
+ }
562
+ function alignStyle(node) {
563
+ const align = node.attrs?.textAlign;
564
+ if (typeof align !== "string" || align === "" || align === "left") return void 0;
565
+ if (align === "center" || align === "right" || align === "justify") {
566
+ return { textAlign: align };
567
+ }
568
+ return void 0;
569
+ }
570
+ function renderNode(node, key) {
571
+ if (!node || typeof node !== "object") return null;
572
+ const children = Array.isArray(node.content) ? node.content.map((child, i) => renderNode(child, i)) : void 0;
573
+ switch (node.type) {
574
+ case "paragraph":
575
+ return /* @__PURE__ */ jsx6("p", { style: alignStyle(node), children }, key);
576
+ case "heading": {
577
+ const level = Math.min(Math.max(Number(node.attrs?.level ?? 2), 1), 3);
578
+ const idAttr = node.attrs?.id;
579
+ const id = typeof idAttr === "string" && idAttr.length > 0 ? idAttr : void 0;
580
+ const style = alignStyle(node);
581
+ if (level === 1) return /* @__PURE__ */ jsx6("h1", { id, style, children }, key);
582
+ if (level === 2) return /* @__PURE__ */ jsx6("h2", { id, style, children }, key);
583
+ return /* @__PURE__ */ jsx6("h3", { id, style, children }, key);
584
+ }
585
+ case "bulletList":
586
+ return /* @__PURE__ */ jsx6("ul", { children }, key);
587
+ case "orderedList":
588
+ return /* @__PURE__ */ jsx6("ol", { children }, key);
589
+ case "listItem":
590
+ return /* @__PURE__ */ jsx6("li", { children }, key);
591
+ case "blockquote":
592
+ return /* @__PURE__ */ jsx6("blockquote", { children }, key);
593
+ case "codeBlock":
594
+ return /* @__PURE__ */ jsx6("pre", { children: /* @__PURE__ */ jsx6("code", { children }) }, key);
595
+ case "horizontalRule":
596
+ return /* @__PURE__ */ jsx6("hr", {}, key);
597
+ case "hardBreak":
598
+ return /* @__PURE__ */ jsx6("br", {}, key);
599
+ case "image": {
600
+ const src = node.attrs?.src;
601
+ if (typeof src !== "string" || src.length === 0) return null;
602
+ const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
603
+ const title = typeof node.attrs?.title === "string" ? node.attrs.title : void 0;
604
+ return /* @__PURE__ */ jsx6("img", { src, alt, title, loading: "lazy" }, key);
605
+ }
606
+ case "columns":
607
+ return /* @__PURE__ */ jsx6("div", { className: "rt-columns", children }, key);
608
+ case "column":
609
+ return /* @__PURE__ */ jsx6("div", { className: "rt-column", children }, key);
610
+ case "text":
611
+ return renderText(node, key);
612
+ default:
613
+ return children ? /* @__PURE__ */ jsx6("span", { children }, key) : null;
614
+ }
615
+ }
616
+ function renderText(node, key) {
617
+ const text = node.text ?? "";
618
+ const marks = node.marks ?? [];
619
+ if (marks.length === 0) return text;
620
+ let element = text;
621
+ for (const mark of marks) {
622
+ element = applyMark(mark, element);
623
+ }
624
+ return /* @__PURE__ */ jsx6("span", { children: element }, key);
625
+ }
626
+ function applyMark(mark, child) {
627
+ switch (mark.type) {
628
+ case "bold":
629
+ return /* @__PURE__ */ jsx6("strong", { children: child });
630
+ case "italic":
631
+ return /* @__PURE__ */ jsx6("em", { children: child });
632
+ case "underline":
633
+ return /* @__PURE__ */ jsx6("u", { children: child });
634
+ case "strike":
635
+ return /* @__PURE__ */ jsx6("s", { children: child });
636
+ case "code":
637
+ return /* @__PURE__ */ jsx6("code", { children: child });
638
+ case "highlight": {
639
+ const color = mark.attrs?.color;
640
+ const style = typeof color === "string" && color.length > 0 ? { background: color } : void 0;
641
+ return /* @__PURE__ */ jsx6("mark", { style, children: child });
642
+ }
643
+ case "textStyle": {
644
+ const color = mark.attrs?.color;
645
+ const style = typeof color === "string" && color.length > 0 ? { color } : void 0;
646
+ if (!style) return /* @__PURE__ */ jsx6(Fragment2, { children: child });
647
+ return /* @__PURE__ */ jsx6("span", { style, children: child });
648
+ }
649
+ case "link": {
650
+ const raw = mark.attrs?.href;
651
+ const href = isSafeHref(raw) ? raw : "#";
652
+ return /* @__PURE__ */ jsx6("a", { href, rel: "noopener noreferrer", target: "_blank", children: child });
653
+ }
654
+ default:
655
+ return /* @__PURE__ */ jsx6("span", { children: child });
656
+ }
657
+ }
658
+ export {
659
+ RootTaleBlogList,
660
+ RootTaleBlogPost,
661
+ RootTaleFloatingCta,
662
+ RootTaleLeadForm,
663
+ RootTalePostCard,
664
+ RootTaleTableOfContents,
665
+ RootTaleThemeProvider,
666
+ attachHeadingIds,
667
+ extractToc,
668
+ headingToId,
669
+ renderBlock,
670
+ renderBlocks
671
+ };
672
+ //# sourceMappingURL=server.js.map