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