@roottale/cms-renderer-next 0.20.0 → 0.22.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 CHANGED
@@ -1,918 +1,27 @@
1
+ import {
2
+ RenderTiptap,
3
+ RootTaleBlogCategories,
4
+ RootTaleBlogList,
5
+ RootTaleBlogPost,
6
+ RootTaleFloatingCta,
7
+ RootTaleLeadForm,
8
+ RootTalePage,
9
+ RootTalePostCard,
10
+ RootTaleTableOfContents,
11
+ RootTaleThemeProvider,
12
+ attachHeadingIds,
13
+ extractToc,
14
+ headingToId,
15
+ renderBlock,
16
+ renderBlocks,
17
+ renderBoundBlocks
18
+ } from "./chunk-DCTKVXWE.js";
1
19
  import {
2
20
  collectCategories,
3
21
  collectCategoriesWithCounts,
4
22
  defaultCategoryHref,
5
23
  filterPostsByCategory
6
24
  } from "./chunk-QWBFHYB6.js";
7
-
8
- // src/server.tsx
9
- import {
10
- DEFAULT_BLOG_SETTINGS,
11
- fetchBlogSettings,
12
- fetchPost,
13
- fetchPosts,
14
- fetchTheme,
15
- resolvePostDisplay
16
- } from "@roottale/cms-client/server";
17
- import {
18
- bindingSrcValue,
19
- bindingTextValue,
20
- boundFieldValue,
21
- isAllowedEmbedSrc
22
- } from "@roottale/cms-core";
23
-
24
- // src/PostCard.tsx
25
- import { jsx, jsxs } from "react/jsx-runtime";
26
- function firstCategoryName(terms) {
27
- if (!terms) return null;
28
- const cat = terms.find((t) => t.taxonomy === "category");
29
- return cat ? cat.name : null;
30
- }
31
- function RootTalePostCard(props) {
32
- const { post, href = defaultHref, display } = props;
33
- const showDate = display ? display.showDate : true;
34
- const showAuthor = display ? display.showAuthor : false;
35
- const showImage = display?.showImage !== false;
36
- const showCategory = display?.showCategory !== false;
37
- const authorName = post.authorName;
38
- const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : "";
39
- const imageUrl = showImage ? post.featuredImageUrl : null;
40
- const category = showCategory ? firstCategoryName(post.terms) : null;
41
- 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: [
42
- imageUrl ? (
43
- // eslint-disable-next-line @next/next/no-img-element -- SSR string URL, framework-agnostic
44
- /* @__PURE__ */ jsx(
45
- "img",
46
- {
47
- className: "rt-cms-card-image",
48
- src: imageUrl,
49
- alt: "",
50
- loading: "lazy",
51
- decoding: "async"
52
- }
53
- )
54
- ) : null,
55
- category ? /* @__PURE__ */ jsx("span", { className: "rt-cms-card-category", children: category }) : null,
56
- publishedDate || showAuthor && authorName ? /* @__PURE__ */ jsxs("p", { className: "rt-cms-meta", children: [
57
- publishedDate ? /* @__PURE__ */ jsx("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
58
- showAuthor && authorName ? /* @__PURE__ */ jsxs("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
59
- "\uC791\uC131\uC790 ",
60
- authorName
61
- ] }) : null
62
- ] }) : null,
63
- /* @__PURE__ */ jsx("h2", { className: "rt-cms-title", children: post.title }),
64
- post.excerpt ? /* @__PURE__ */ jsx("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null,
65
- /* @__PURE__ */ jsx("span", { className: "rt-cms-card-action", "aria-hidden": "true", children: "\uC77D\uAE30" })
66
- ] }) });
67
- }
68
- function defaultHref(post) {
69
- return `/blog/${post.slug}`;
70
- }
71
- function formatPublishedDate(iso) {
72
- const d = new Date(iso);
73
- if (Number.isNaN(d.getTime())) return "";
74
- return new Intl.DateTimeFormat("ko-KR", {
75
- year: "numeric",
76
- month: "2-digit",
77
- day: "2-digit"
78
- }).format(d);
79
- }
80
-
81
- // src/TableOfContents.tsx
82
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
83
- function RootTaleTableOfContents(props) {
84
- const { entries, title = "\uBAA9\uCC28" } = props;
85
- if (entries.length === 0) return null;
86
- 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: [
87
- /* @__PURE__ */ jsxs2("summary", { className: "rt-cms-toc-summary", children: [
88
- title ? /* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-title", children: title }) : null,
89
- /* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-toggle", "aria-hidden": "true" })
90
- ] }),
91
- /* @__PURE__ */ jsx2("ol", { className: "rt-cms-toc-list", children: entries.map((entry) => /* @__PURE__ */ jsx2(
92
- "li",
93
- {
94
- className: `rt-cms-toc-item rt-cms-toc-level-${entry.level}`,
95
- children: /* @__PURE__ */ jsx2("a", { href: `#${entry.id}`, children: entry.text })
96
- },
97
- entry.id
98
- )) })
99
- ] }) });
100
- }
101
-
102
- // src/toc.ts
103
- function tocLevel(rawLevel) {
104
- if (rawLevel < 1 || rawLevel > 4) return null;
105
- if (rawLevel < 3) return 2;
106
- return rawLevel === 3 ? 3 : 4;
107
- }
108
- function headingToId(text) {
109
- return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
110
- }
111
- function textOf(node) {
112
- if (typeof node.text === "string") return node.text;
113
- if (!Array.isArray(node.content)) return "";
114
- return node.content.map(textOf).join("");
115
- }
116
- function extractToc(doc) {
117
- const root = doc;
118
- const content = Array.isArray(root.content) ? root.content : [];
119
- const entries = [];
120
- const idCounts = /* @__PURE__ */ new Map();
121
- for (const node of content) {
122
- if (node?.type !== "heading") continue;
123
- const rawLevel = Number(node.attrs?.level ?? 2);
124
- const level = tocLevel(rawLevel);
125
- if (level === null) continue;
126
- const text = textOf(node).trim();
127
- if (!text) continue;
128
- const base = headingToId(text) || "heading";
129
- const count = idCounts.get(base) ?? 0;
130
- const id = count === 0 ? base : `${base}-${count}`;
131
- idCounts.set(base, count + 1);
132
- entries.push({ level, text, id });
133
- }
134
- return entries;
135
- }
136
- function attachHeadingIds(doc, entries) {
137
- const root = doc;
138
- const content = Array.isArray(root.content) ? root.content : [];
139
- let cursor = 0;
140
- const nextContent = content.map((node) => {
141
- if (node?.type !== "heading") return node;
142
- const rawLevel = Number(node.attrs?.level ?? 2);
143
- if (rawLevel < 1 || rawLevel > 4) return node;
144
- if (!textOf(node).trim()) return node;
145
- const entry = entries[cursor++];
146
- if (!entry) return node;
147
- return {
148
- ...node,
149
- attrs: { ...node.attrs ?? {}, id: entry.id }
150
- };
151
- });
152
- return { ...root, content: nextContent };
153
- }
154
-
155
- // src/FloatingCta.tsx
156
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
157
- function RootTaleFloatingCta(props) {
158
- const { buttons, position = "bottom-right" } = props;
159
- if (!buttons || buttons.length === 0) return null;
160
- return /* @__PURE__ */ jsx3(
161
- "div",
162
- {
163
- "data-roottale-cms": "floating-cta",
164
- className: `rt-cms-floating-cta rt-cms-floating-cta--${position}`,
165
- children: buttons.map((btn, idx) => {
166
- const isExternal = btn.type === "kakao" || btn.type === "custom";
167
- return /* @__PURE__ */ jsxs3(
168
- "a",
169
- {
170
- href: btn.href,
171
- className: `rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`,
172
- target: isExternal ? "_blank" : void 0,
173
- rel: isExternal ? "noopener noreferrer" : void 0,
174
- children: [
175
- /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "rt-cms-floating-cta__icon", children: btn.icon ?? defaultIcon(btn.type) }),
176
- /* @__PURE__ */ jsx3("span", { className: "rt-cms-floating-cta__label", children: btn.label })
177
- ]
178
- },
179
- `${btn.type}-${idx}`
180
- );
181
- })
182
- }
183
- );
184
- }
185
- function defaultIcon(type) {
186
- switch (type) {
187
- case "phone":
188
- return "\u260E";
189
- case "contact":
190
- return "\u2709";
191
- case "kakao":
192
- return "\u{1F4AC}";
193
- case "custom":
194
- return "\u2192";
195
- }
196
- }
197
-
198
- // src/LeadForm.tsx
199
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
200
- var VERTICAL_LABEL = {
201
- consulting: "\uCEE8\uC124\uD305",
202
- medical: "\uC758\uB8CC",
203
- tax: "\uC138\uBB34",
204
- legal: "\uBC95\uB960"
205
- };
206
- var VERTICAL_OPTIONS = [
207
- { value: "consulting", label: "\uCEE8\uC124\uD305 / \uC77C\uBC18" },
208
- { value: "medical", label: "\uC758\uB8CC (\uBCD1\uC758\uC6D0\xB7\uCE58\uACFC\xB7\uD55C\uC758\uC6D0)" },
209
- { value: "tax", label: "\uC138\uBB34 (\uC138\uBB34\uC0AC\xB7\uD68C\uACC4\uC0AC)" },
210
- { value: "legal", label: "\uBC95\uB960 (\uBC95\uBB34\uBC95\uC778\xB7\uBCC0\uD638\uC0AC)" }
211
- ];
212
- function RootTaleLeadForm(props) {
213
- const {
214
- action,
215
- vertical,
216
- redirectUrl,
217
- heading,
218
- description,
219
- submitLabel = "\uC9C4\uB2E8 \uC2E0\uCCAD",
220
- className
221
- } = props;
222
- const formClass = ["rt-cms-lead-form", className].filter(Boolean).join(" ");
223
- return /* @__PURE__ */ jsxs4(
224
- "form",
225
- {
226
- method: "post",
227
- action,
228
- "data-roottale-cms": "lead-form",
229
- className: formClass,
230
- children: [
231
- heading ? /* @__PURE__ */ jsx4("h2", { className: "rt-cms-lead-heading", children: heading }) : null,
232
- description ? /* @__PURE__ */ jsx4("p", { className: "rt-cms-lead-description", children: description }) : null,
233
- redirectUrl ? /* @__PURE__ */ jsx4("input", { type: "hidden", name: "_redirect_url", value: redirectUrl }) : null,
234
- vertical ? /* @__PURE__ */ jsxs4(Fragment, { children: [
235
- /* @__PURE__ */ jsx4("input", { type: "hidden", name: "vertical", value: vertical }),
236
- /* @__PURE__ */ jsxs4("p", { className: "rt-cms-lead-vertical-label", children: [
237
- "\uBD84\uC57C: ",
238
- /* @__PURE__ */ jsx4("strong", { children: VERTICAL_LABEL[vertical] })
239
- ] })
240
- ] }) : /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
241
- /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
242
- "\uBD84\uC57C ",
243
- /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
244
- ] }),
245
- /* @__PURE__ */ jsxs4(
246
- "select",
247
- {
248
- name: "vertical",
249
- required: true,
250
- className: "rt-cms-field-input rt-cms-field-select",
251
- defaultValue: "",
252
- children: [
253
- /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: "\uC120\uD0DD\uD558\uC138\uC694" }),
254
- VERTICAL_OPTIONS.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.value, children: opt.label }, opt.value))
255
- ]
256
- }
257
- )
258
- ] }),
259
- /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
260
- /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
261
- "\uC774\uB984 ",
262
- /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
263
- ] }),
264
- /* @__PURE__ */ jsx4(
265
- "input",
266
- {
267
- type: "text",
268
- name: "contact_name",
269
- required: true,
270
- autoComplete: "name",
271
- className: "rt-cms-field-input"
272
- }
273
- )
274
- ] }),
275
- /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
276
- /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
277
- "\uC0AC\uC5C5\uCCB4\uBA85 ",
278
- /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
279
- ] }),
280
- /* @__PURE__ */ jsx4(
281
- "input",
282
- {
283
- type: "text",
284
- name: "business_name",
285
- required: true,
286
- autoComplete: "organization",
287
- className: "rt-cms-field-input"
288
- }
289
- )
290
- ] }),
291
- /* @__PURE__ */ jsxs4("label", { className: "rt-cms-field", children: [
292
- /* @__PURE__ */ jsxs4("span", { className: "rt-cms-field-label", children: [
293
- "\uC774\uBA54\uC77C ",
294
- /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "*" })
295
- ] }),
296
- /* @__PURE__ */ jsx4(
297
- "input",
298
- {
299
- type: "email",
300
- name: "email",
301
- required: true,
302
- autoComplete: "email",
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__ */ jsx4("span", { className: "rt-cms-field-label", children: "\uD604\uC7AC \uC0AC\uC774\uD2B8 URL (\uC120\uD0DD)" }),
328
- /* @__PURE__ */ jsx4(
329
- "input",
330
- {
331
- type: "url",
332
- name: "current_site_url",
333
- placeholder: "https://",
334
- className: "rt-cms-field-input"
335
- }
336
- ),
337
- /* @__PURE__ */ jsx4("span", { className: "rt-cms-field-hint", children: "\uC5C6\uC73C\uBA74 \uBE44\uC6CC\uB450\uC138\uC694." })
338
- ] }),
339
- /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
340
- /* @__PURE__ */ jsx4("input", { type: "checkbox", name: "privacy_consent", required: true }),
341
- /* @__PURE__ */ jsxs4("span", { children: [
342
- /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218)" }),
343
- " \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)"
344
- ] })
345
- ] }),
346
- vertical === "medical" ? /* @__PURE__ */ jsxs4("label", { className: "rt-cms-lead-consent", children: [
347
- /* @__PURE__ */ jsx4(
348
- "input",
349
- {
350
- type: "checkbox",
351
- name: "overseas_transfer_consent",
352
- required: true
353
- }
354
- ),
355
- /* @__PURE__ */ jsxs4("span", { children: [
356
- /* @__PURE__ */ jsx4("strong", { children: "(\uD544\uC218, \uC758\uB8CC)" }),
357
- " \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)"
358
- ] })
359
- ] }) : null,
360
- /* @__PURE__ */ jsx4("div", { className: "rt-cms-lead-actions", children: /* @__PURE__ */ jsx4("button", { type: "submit", className: "rt-cms-lead-submit", children: submitLabel }) })
361
- ]
362
- }
363
- );
364
- }
365
-
366
- // src/block-to-react.tsx
367
- import {
368
- applyBlockBindings,
369
- createFieldBindingResolver,
370
- sanitizeHtml
371
- } from "@roottale/cms-core";
372
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
373
- function isString(value) {
374
- return typeof value === "string";
375
- }
376
- function htmlContent(html) {
377
- return { __html: sanitizeHtml(html) };
378
- }
379
- function renderInner(blocks) {
380
- return blocks.map((b, i) => renderBlock(b, i));
381
- }
382
- function renderBlock(block, key = 0) {
383
- const dataBlockId = block._id;
384
- switch (block.name) {
385
- case "core/paragraph": {
386
- const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
387
- return /* @__PURE__ */ jsx5(
388
- "p",
389
- {
390
- "data-block-id": dataBlockId,
391
- dangerouslySetInnerHTML: htmlContent(text)
392
- },
393
- key
394
- );
395
- }
396
- case "core/heading": {
397
- const levelRaw = block.attributes.level;
398
- const level = typeof levelRaw === "number" && levelRaw >= 2 && levelRaw <= 4 ? levelRaw : 2;
399
- const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
400
- const props = {
401
- "data-block-id": dataBlockId,
402
- dangerouslySetInnerHTML: htmlContent(text)
403
- };
404
- if (level === 2) return /* @__PURE__ */ jsx5("h2", { ...props }, key);
405
- if (level === 3) return /* @__PURE__ */ jsx5("h3", { ...props }, key);
406
- return /* @__PURE__ */ jsx5("h4", { ...props }, key);
407
- }
408
- case "core/image": {
409
- const src = isString(block.attributes.url) ? block.attributes.url : "";
410
- const alt = isString(block.attributes.alt) ? block.attributes.alt : "";
411
- const caption = isString(block.attributes.caption) ? block.attributes.caption : null;
412
- return /* @__PURE__ */ jsxs5("figure", { "data-block-id": dataBlockId, children: [
413
- /* @__PURE__ */ jsx5("img", { src, alt, loading: "lazy" }),
414
- caption && /* @__PURE__ */ jsx5("figcaption", { children: caption })
415
- ] }, key);
416
- }
417
- case "core/list": {
418
- const ordered = block.attributes.ordered === true;
419
- const items = (block.innerBlocks ?? []).map(
420
- (item, i) => item.rawHtml ? /* @__PURE__ */ jsx5(
421
- "li",
422
- {
423
- dangerouslySetInnerHTML: htmlContent(item.rawHtml)
424
- },
425
- i
426
- ) : /* @__PURE__ */ jsx5("li", { children: renderInner(item.innerBlocks ?? []) }, i)
427
- );
428
- return ordered ? /* @__PURE__ */ jsx5("ol", { "data-block-id": dataBlockId, children: items }, key) : /* @__PURE__ */ jsx5("ul", { "data-block-id": dataBlockId, children: items }, key);
429
- }
430
- case "core/quote": {
431
- const inner = renderInner(block.innerBlocks ?? []);
432
- const citation = isString(block.attributes.citation) ? block.attributes.citation : null;
433
- return /* @__PURE__ */ jsxs5("blockquote", { "data-block-id": dataBlockId, children: [
434
- inner,
435
- citation && /* @__PURE__ */ jsx5("cite", { children: citation })
436
- ] }, key);
437
- }
438
- case "core/code": {
439
- const code = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
440
- const lang = isString(block.attributes.language) ? block.attributes.language : void 0;
441
- return /* @__PURE__ */ jsx5(
442
- "pre",
443
- {
444
- "data-block-id": dataBlockId,
445
- "data-language": lang,
446
- children: /* @__PURE__ */ jsx5("code", { children: code })
447
- },
448
- key
449
- );
450
- }
451
- case "core/columns": {
452
- return /* @__PURE__ */ jsx5(
453
- "div",
454
- {
455
- className: "rt-columns",
456
- "data-block-id": dataBlockId,
457
- children: renderInner(block.innerBlocks ?? [])
458
- },
459
- key
460
- );
461
- }
462
- case "core/separator":
463
- return /* @__PURE__ */ jsx5("hr", { "data-block-id": dataBlockId }, key);
464
- case "core/spacer": {
465
- const heightRaw = block.attributes.height;
466
- const height = isString(heightRaw) ? heightRaw : "32px";
467
- return /* @__PURE__ */ jsx5(
468
- "div",
469
- {
470
- className: "rt-spacer",
471
- "data-block-id": dataBlockId,
472
- style: { height }
473
- },
474
- key
475
- );
476
- }
477
- case "core/group": {
478
- return /* @__PURE__ */ jsx5(
479
- "div",
480
- {
481
- className: "rt-group",
482
- "data-block-id": dataBlockId,
483
- children: renderInner(block.innerBlocks ?? [])
484
- },
485
- key
486
- );
487
- }
488
- default: {
489
- const rawHtml = block.rawHtml;
490
- return /* @__PURE__ */ jsx5(
491
- "div",
492
- {
493
- className: "rt-unknown-block",
494
- "data-block-id": dataBlockId,
495
- "data-block-name": block.name,
496
- "data-rawhtml-suppressed": rawHtml ? "true" : void 0,
497
- children: renderInner(block.innerBlocks ?? [])
498
- },
499
- key
500
- );
501
- }
502
- }
503
- }
504
- function renderBlocks(blocks) {
505
- return blocks.map((b, i) => renderBlock(b, i));
506
- }
507
- function renderBoundBlocks(blocks, opts = {}) {
508
- const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);
509
- return renderBlocks(applyBlockBindings(blocks, resolver));
510
- }
511
-
512
- // src/server.tsx
513
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
514
- function authorImageRadiusCss(radius) {
515
- if (radius === "circle") return "999px";
516
- if (radius === "rounded") return "0.875rem";
517
- return "0";
518
- }
519
- function RootTaleThemeProvider(props) {
520
- const style = themeToCssVars(props.theme ?? null);
521
- if (!style) return /* @__PURE__ */ jsx6(Fragment2, { children: props.children });
522
- return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "theme-root", style, children: props.children });
523
- }
524
- async function RootTaleBlogList(props) {
525
- const {
526
- apiKey,
527
- baseUrl,
528
- limit,
529
- type = "post",
530
- postHref = defaultPostHref,
531
- emptyMessage = "\uC544\uC9C1 \uBC1C\uD589\uB41C \uAE00\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
532
- showCategoryFilter = false,
533
- activeCategory,
534
- categoryHref = defaultCategoryHref,
535
- allCategoriesLabel = "\uC804\uCCB4",
536
- theme: themeProp
537
- } = props;
538
- const [page, themeStyle, blogSettings] = await Promise.all([
539
- fetchPosts({ apiKey, baseUrl, limit, type }),
540
- resolveThemeStyle({ themeProp, apiKey, baseUrl }),
541
- fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS)
542
- ]);
543
- if (page.items.length === 0) {
544
- return /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx6("p", { className: "rt-cms-empty", children: emptyMessage }) });
545
- }
546
- const categories = showCategoryFilter ? collectCategories(page.items) : [];
547
- const visiblePosts = showCategoryFilter ? filterPostsByCategory(page.items, activeCategory) : page.items;
548
- return /* @__PURE__ */ jsxs6("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: [
549
- showCategoryFilter && categories.length > 0 ? /* @__PURE__ */ jsxs6("nav", { className: "rt-cms-filter", "aria-label": "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130", children: [
550
- /* @__PURE__ */ jsx6(
551
- "a",
552
- {
553
- className: "rt-cms-filter-chip",
554
- href: categoryHref(null),
555
- "data-active": !activeCategory ? "" : void 0,
556
- "aria-current": !activeCategory ? "true" : void 0,
557
- children: allCategoriesLabel
558
- }
559
- ),
560
- categories.map((c) => /* @__PURE__ */ jsx6(
561
- "a",
562
- {
563
- className: "rt-cms-filter-chip",
564
- href: categoryHref(c.slug),
565
- "data-active": activeCategory === c.slug ? "" : void 0,
566
- "aria-current": activeCategory === c.slug ? "true" : void 0,
567
- children: c.name
568
- },
569
- c.slug
570
- ))
571
- ] }) : null,
572
- visiblePosts.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx6("ul", { className: "rt-cms-list", children: visiblePosts.map((post) => /* @__PURE__ */ jsx6("li", { className: "rt-cms-list-item", children: /* @__PURE__ */ jsx6(
573
- RootTalePostCard,
574
- {
575
- post,
576
- href: postHref,
577
- display: resolvePostDisplay(post, blogSettings)
578
- }
579
- ) }, post.id)) })
580
- ] });
581
- }
582
- async function RootTaleBlogCategories(props) {
583
- const {
584
- apiKey,
585
- baseUrl,
586
- limit,
587
- categoryHref = (slug) => defaultCategoryHref(slug),
588
- emptyMessage = "\uC544\uC9C1 \uCE74\uD14C\uACE0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
589
- theme: themeProp
590
- } = props;
591
- const [page, themeStyle] = await Promise.all([
592
- fetchPosts({ apiKey, baseUrl, limit, type: "post" }),
593
- resolveThemeStyle({ themeProp, apiKey, baseUrl })
594
- ]);
595
- const categories = collectCategoriesWithCounts(page.items);
596
- return /* @__PURE__ */ jsx6(
597
- "nav",
598
- {
599
- "data-roottale-cms": "categories",
600
- className: "rt-cms-categories",
601
- "aria-label": "\uBE14\uB85C\uADF8 \uCE74\uD14C\uACE0\uB9AC",
602
- style: themeStyle ?? void 0,
603
- children: categories.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx6("ul", { className: "rt-cms-category-list", children: categories.map((category) => /* @__PURE__ */ jsx6("li", { className: "rt-cms-category-list-item", children: /* @__PURE__ */ jsxs6(
604
- "a",
605
- {
606
- className: "rt-cms-category-link",
607
- href: categoryHref(category.slug),
608
- children: [
609
- /* @__PURE__ */ jsx6("span", { className: "rt-cms-category-name", children: category.name }),
610
- /* @__PURE__ */ jsx6("span", { className: "rt-cms-category-count", children: category.count })
611
- ]
612
- }
613
- ) }, category.slug)) })
614
- }
615
- );
616
- }
617
- async function RootTaleBlogPost(props) {
618
- const {
619
- apiKey,
620
- slugOrId,
621
- baseUrl,
622
- notFoundElement,
623
- showTableOfContents,
624
- tableOfContentsTitle,
625
- theme: themeProp
626
- } = props;
627
- const [post, themeStyle, blogSettings] = await Promise.all([
628
- fetchPost({ apiKey, slugOrId, baseUrl }),
629
- resolveThemeStyle({ themeProp, apiKey, baseUrl }),
630
- // 사이트 기본 표시 설정 — 실패해도 렌더는 진행 (안전 기본값).
631
- fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS)
632
- ]);
633
- if (!post) {
634
- 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." }) });
635
- }
636
- const display = resolvePostDisplay(post, blogSettings, {
637
- showTableOfContents,
638
- tableOfContentsTitle
639
- });
640
- const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];
641
- const renderDoc = toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;
642
- const authorName = post.authorName;
643
- const authorImageUrl = post.authorImageUrl;
644
- const postAuthorBio = post.authorBio;
645
- const publishedDate = display.showDate ? formatPublishedDate2(post.publishedAt) : "";
646
- const showTopAuthor = display.showAuthor && Boolean(authorName);
647
- const displayAuthorName = authorName ?? display.authorProfileName;
648
- const authorBio = postAuthorBio ?? display.authorProfileBio ?? display.authorCardDescription;
649
- const authorImageUrlForCard = authorImageUrl ?? display.authorProfileImageUrl;
650
- const authorImageAlt = `${displayAuthorName ?? ""} \uD504\uB85C\uD544 \uC0AC\uC9C4`.trim();
651
- const authorImageStyle = {
652
- borderRadius: authorImageRadiusCss(display.authorProfileImageRadius),
653
- objectPosition: `${display.authorProfileImagePositionX}% ${display.authorProfileImagePositionY}%`
654
- };
655
- const showAuthorCard = display.showAuthorCard && Boolean(displayAuthorName);
656
- return /* @__PURE__ */ jsxs6(
657
- "article",
658
- {
659
- "data-roottale-cms": "post",
660
- className: "rt-cms-article",
661
- style: themeStyle ?? void 0,
662
- children: [
663
- /* @__PURE__ */ jsxs6("header", { className: "rt-cms-post-header", children: [
664
- publishedDate || showTopAuthor ? /* @__PURE__ */ jsxs6("p", { className: "rt-cms-meta", children: [
665
- publishedDate ? /* @__PURE__ */ jsx6("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
666
- showTopAuthor ? /* @__PURE__ */ jsxs6("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
667
- "\uC791\uC131\uC790 ",
668
- authorName
669
- ] }) : null
670
- ] }) : null,
671
- /* @__PURE__ */ jsx6("h1", { className: "rt-cms-title", children: post.title }),
672
- post.excerpt ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
673
- ] }),
674
- toc.length > 0 ? /* @__PURE__ */ jsx6(RootTaleTableOfContents, { entries: toc, title: display.tocTitle }) : null,
675
- /* @__PURE__ */ jsx6("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx6(RenderTiptap, { doc: renderDoc, fields: post.fields }) }),
676
- showAuthorCard && displayAuthorName ? /* @__PURE__ */ jsxs6("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
677
- authorImageUrlForCard ? /* @__PURE__ */ jsx6(
678
- "img",
679
- {
680
- className: "rt-cms-author-card__image",
681
- src: authorImageUrlForCard,
682
- alt: authorImageAlt,
683
- loading: "lazy",
684
- style: authorImageStyle
685
- }
686
- ) : /* @__PURE__ */ jsx6("div", { className: "rt-cms-author-card__avatar", "aria-hidden": "true", children: authorInitials(displayAuthorName) }),
687
- /* @__PURE__ */ jsxs6("div", { className: "rt-cms-author-card__body", children: [
688
- /* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__eyebrow", children: display.authorCardEyebrow }),
689
- /* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__name", children: displayAuthorName }),
690
- authorBio ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__description", children: authorBio }) : null
691
- ] })
692
- ] }) : null
693
- ]
694
- }
695
- );
696
- }
697
- function authorInitials(name) {
698
- const trimmed = name.trim();
699
- if (!trimmed) return "";
700
- const parts = trimmed.split(/\s+/).filter(Boolean);
701
- if (parts.length >= 2) {
702
- return `${parts[0]?.[0] ?? ""}${parts[1]?.[0] ?? ""}`.toUpperCase();
703
- }
704
- return [...trimmed].slice(0, 2).join("").toUpperCase();
705
- }
706
- async function resolveThemeStyle(input) {
707
- const { themeProp, apiKey, baseUrl } = input;
708
- if (themeProp === null) return null;
709
- if (themeProp !== void 0) return themeToCssVars(themeProp);
710
- try {
711
- const fetched = await fetchTheme({ apiKey, baseUrl });
712
- return themeToCssVars(fetched);
713
- } catch {
714
- return null;
715
- }
716
- }
717
- function themeToCssVars(theme) {
718
- if (!theme) return null;
719
- const vars = {};
720
- const c = theme.colors ?? {};
721
- if (c.primary) vars["--rt-color-primary"] = c.primary;
722
- if (c.primaryForeground) vars["--rt-color-primary-foreground"] = c.primaryForeground;
723
- if (c.foreground) vars["--rt-color-foreground"] = c.foreground;
724
- if (c.background) vars["--rt-color-background"] = c.background;
725
- if (c.secondary) vars["--rt-color-secondary"] = c.secondary;
726
- if (c.secondaryForeground) vars["--rt-color-secondary-foreground"] = c.secondaryForeground;
727
- if (c.accent) vars["--rt-color-accent"] = c.accent;
728
- if (c.accentForeground) vars["--rt-color-accent-foreground"] = c.accentForeground;
729
- if (c.muted) vars["--rt-color-muted"] = c.muted;
730
- if (c.mutedForeground) vars["--rt-color-muted-foreground"] = c.mutedForeground;
731
- if (c.destructive) vars["--rt-color-destructive"] = c.destructive;
732
- if (c.destructiveForeground) vars["--rt-color-destructive-foreground"] = c.destructiveForeground;
733
- if (c.border) vars["--rt-color-border"] = c.border;
734
- if (c.input) vars["--rt-color-input"] = c.input;
735
- if (c.ring) vars["--rt-color-ring"] = c.ring;
736
- const f = theme.fonts ?? {};
737
- if (f.body) vars["--rt-font-body"] = f.body;
738
- if (f.display) vars["--rt-font-display"] = f.display;
739
- const r = theme.radius ?? {};
740
- if (r.sm) vars["--rt-radius-sm"] = r.sm;
741
- if (r.md) vars["--rt-radius-md"] = r.md;
742
- if (r.lg) vars["--rt-radius-lg"] = r.lg;
743
- if (r.xl) vars["--rt-radius-xl"] = r.xl;
744
- if (Object.keys(vars).length === 0) return null;
745
- return vars;
746
- }
747
- function defaultPostHref(post) {
748
- return `/blog/${post.slug}`;
749
- }
750
- function formatPublishedDate2(iso) {
751
- const d = new Date(iso);
752
- if (Number.isNaN(d.getTime())) return "";
753
- return new Intl.DateTimeFormat("ko-KR", {
754
- year: "numeric",
755
- month: "2-digit",
756
- day: "2-digit"
757
- }).format(d);
758
- }
759
- function RenderTiptap({
760
- doc,
761
- fields
762
- }) {
763
- const rawContent = doc.content;
764
- const content = Array.isArray(rawContent) ? rawContent : [];
765
- return /* @__PURE__ */ jsx6(Fragment2, { children: content.map((node, i) => renderNode(node, i, fields ?? null)) });
766
- }
767
- function boundContentText(node, fields) {
768
- return bindingTextValue(
769
- boundFieldValue(node.attrs?.bindings, "content", fields)
770
- );
771
- }
772
- function isSafeHref(value) {
773
- if (typeof value !== "string") return false;
774
- const v = value.trim();
775
- if (v.length === 0) return false;
776
- if (v.startsWith("/") || v.startsWith("#") || v.startsWith("?")) return true;
777
- return /^(https?:|mailto:|tel:)/i.test(v);
778
- }
779
- function alignStyle(node) {
780
- const align = node.attrs?.textAlign;
781
- if (typeof align !== "string" || align === "" || align === "left") return void 0;
782
- if (align === "center" || align === "right" || align === "justify") {
783
- return { textAlign: align };
784
- }
785
- return void 0;
786
- }
787
- function renderNode(node, key, fields = null) {
788
- if (!node || typeof node !== "object") return null;
789
- const children = Array.isArray(node.content) ? node.content.map((child, i) => renderNode(child, i, fields)) : void 0;
790
- switch (node.type) {
791
- case "paragraph": {
792
- const bound = boundContentText(node, fields);
793
- return /* @__PURE__ */ jsx6("p", { style: alignStyle(node), children: bound ?? children }, key);
794
- }
795
- case "heading": {
796
- const rawLevel = Number(node.attrs?.level ?? 2);
797
- const level = Math.min(Math.max(Number.isFinite(rawLevel) ? rawLevel : 2, 2), 4);
798
- const idAttr = node.attrs?.id;
799
- const id = typeof idAttr === "string" && idAttr.length > 0 ? idAttr : void 0;
800
- const style = alignStyle(node);
801
- const inner = boundContentText(node, fields) ?? children;
802
- if (level === 2) return /* @__PURE__ */ jsx6("h2", { id, style, children: inner }, key);
803
- if (level === 3) return /* @__PURE__ */ jsx6("h3", { id, style, children: inner }, key);
804
- return /* @__PURE__ */ jsx6("h4", { id, style, children: inner }, key);
805
- }
806
- case "bulletList":
807
- return /* @__PURE__ */ jsx6("ul", { children }, key);
808
- case "orderedList":
809
- return /* @__PURE__ */ jsx6("ol", { children }, key);
810
- case "listItem":
811
- return /* @__PURE__ */ jsx6("li", { children }, key);
812
- case "blockquote":
813
- return /* @__PURE__ */ jsx6("blockquote", { children }, key);
814
- case "codeBlock":
815
- return /* @__PURE__ */ jsx6("pre", { children: /* @__PURE__ */ jsx6("code", { children }) }, key);
816
- case "horizontalRule":
817
- return /* @__PURE__ */ jsx6("hr", {}, key);
818
- case "hardBreak":
819
- return /* @__PURE__ */ jsx6("br", {}, key);
820
- case "image": {
821
- const boundSrc = bindingSrcValue(
822
- boundFieldValue(node.attrs?.bindings, "src", fields)
823
- );
824
- const src = boundSrc ?? node.attrs?.src;
825
- if (typeof src !== "string" || src.length === 0) return null;
826
- const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
827
- const title = typeof node.attrs?.title === "string" ? node.attrs.title : void 0;
828
- return /* @__PURE__ */ jsx6("img", { src, alt, title, loading: "lazy" }, key);
829
- }
830
- case "columns":
831
- return /* @__PURE__ */ jsx6("div", { className: "rt-columns", children }, key);
832
- case "column":
833
- return /* @__PURE__ */ jsx6("div", { className: "rt-column", children }, key);
834
- case "embed":
835
- return renderEmbed(node, key);
836
- case "text":
837
- return renderText(node, key);
838
- default:
839
- return children ? /* @__PURE__ */ jsx6("span", { children }, key) : null;
840
- }
841
- }
842
- function renderEmbed(node, key) {
843
- const src = node.attrs?.src;
844
- if (isAllowedEmbedSrc(src)) {
845
- const provider = typeof node.attrs?.provider === "string" ? node.attrs.provider : void 0;
846
- return /* @__PURE__ */ jsx6(
847
- "div",
848
- {
849
- className: "rt-embed",
850
- "data-provider": provider,
851
- style: { aspectRatio: "16 / 9" },
852
- children: /* @__PURE__ */ jsx6(
853
- "iframe",
854
- {
855
- src,
856
- title: "\uC784\uBCA0\uB4DC\uB41C \uC601\uC0C1",
857
- loading: "lazy",
858
- allowFullScreen: true,
859
- referrerPolicy: "strict-origin-when-cross-origin",
860
- allow: "accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share",
861
- style: { width: "100%", height: "100%", border: 0 }
862
- }
863
- )
864
- },
865
- key
866
- );
867
- }
868
- const orig = node.attrs?.originalUrl;
869
- if (isSafeHref(orig)) {
870
- return /* @__PURE__ */ jsx6("p", { className: "rt-embed-fallback", children: /* @__PURE__ */ jsx6("a", { href: orig, rel: "noopener noreferrer", target: "_blank", children: orig }) }, key);
871
- }
872
- return null;
873
- }
874
- function renderText(node, key) {
875
- const text = node.text ?? "";
876
- const marks = node.marks ?? [];
877
- if (marks.length === 0) return text;
878
- let element = text;
879
- for (const mark of marks) {
880
- element = applyMark(mark, element);
881
- }
882
- return /* @__PURE__ */ jsx6("span", { children: element }, key);
883
- }
884
- function applyMark(mark, child) {
885
- switch (mark.type) {
886
- case "bold":
887
- return /* @__PURE__ */ jsx6("strong", { children: child });
888
- case "italic":
889
- return /* @__PURE__ */ jsx6("em", { children: child });
890
- case "underline":
891
- return /* @__PURE__ */ jsx6("u", { children: child });
892
- case "strike":
893
- return /* @__PURE__ */ jsx6("s", { children: child });
894
- case "code":
895
- return /* @__PURE__ */ jsx6("code", { children: child });
896
- case "highlight": {
897
- const color = mark.attrs?.color;
898
- const style = typeof color === "string" && color.length > 0 ? { background: color } : void 0;
899
- return /* @__PURE__ */ jsx6("mark", { style, children: child });
900
- }
901
- case "textStyle": {
902
- const color = mark.attrs?.color;
903
- const style = typeof color === "string" && color.length > 0 ? { color } : void 0;
904
- if (!style) return /* @__PURE__ */ jsx6(Fragment2, { children: child });
905
- return /* @__PURE__ */ jsx6("span", { style, children: child });
906
- }
907
- case "link": {
908
- const raw = mark.attrs?.href;
909
- const href = isSafeHref(raw) ? raw : "#";
910
- return /* @__PURE__ */ jsx6("a", { href, rel: "noopener noreferrer", target: "_blank", children: child });
911
- }
912
- default:
913
- return /* @__PURE__ */ jsx6("span", { children: child });
914
- }
915
- }
916
25
  export {
917
26
  RenderTiptap,
918
27
  RootTaleBlogCategories,
@@ -920,6 +29,7 @@ export {
920
29
  RootTaleBlogPost,
921
30
  RootTaleFloatingCta,
922
31
  RootTaleLeadForm,
32
+ RootTalePage,
923
33
  RootTalePostCard,
924
34
  RootTaleTableOfContents,
925
35
  RootTaleThemeProvider,