@roottale/cms-renderer-next 0.11.0 → 0.13.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/CHANGELOG.md +21 -0
- package/README.md +24 -0
- package/dist/cms-public.css +414 -44
- package/dist/mui.d.ts +93 -0
- package/dist/mui.js +62 -0
- package/dist/mui.js.map +1 -0
- package/dist/server.d.ts +6 -6
- package/dist/server.js +75 -28
- package/dist/server.js.map +1 -1
- package/package.json +7 -2
package/dist/mui.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/mui.ts
|
|
2
|
+
function parseRadiusPx(raw) {
|
|
3
|
+
const v = raw.trim();
|
|
4
|
+
const num = Number.parseFloat(v);
|
|
5
|
+
if (Number.isNaN(num)) return void 0;
|
|
6
|
+
if (v.endsWith("rem")) return num * 16;
|
|
7
|
+
if (v.endsWith("em")) return num * 16;
|
|
8
|
+
return num;
|
|
9
|
+
}
|
|
10
|
+
function isPlainObject(x) {
|
|
11
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
12
|
+
}
|
|
13
|
+
function deepMerge(a, b) {
|
|
14
|
+
const out = { ...a };
|
|
15
|
+
for (const [k, bv] of Object.entries(b)) {
|
|
16
|
+
const av = out[k];
|
|
17
|
+
out[k] = isPlainObject(av) && isPlainObject(bv) ? deepMerge(av, bv) : bv;
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function rootTaleThemeToMui(theme, overrides) {
|
|
22
|
+
const c = theme?.colors ?? {};
|
|
23
|
+
const f = theme?.fonts ?? {};
|
|
24
|
+
const r = theme?.radius ?? {};
|
|
25
|
+
const palette = {};
|
|
26
|
+
if (c.primary) {
|
|
27
|
+
palette.primary = {
|
|
28
|
+
main: c.primary,
|
|
29
|
+
...c.primaryForeground ? { contrastText: c.primaryForeground } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (c.background) palette.background = { default: c.background };
|
|
33
|
+
if (c.foreground || c.mutedForeground) {
|
|
34
|
+
palette.text = {
|
|
35
|
+
...c.foreground ? { primary: c.foreground } : {},
|
|
36
|
+
...c.mutedForeground ? { secondary: c.mutedForeground } : {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (c.border) palette.divider = c.border;
|
|
40
|
+
const typography = {};
|
|
41
|
+
if (f.body) typography.fontFamily = f.body;
|
|
42
|
+
if (f.display) {
|
|
43
|
+
typography.h1 = { fontFamily: f.display };
|
|
44
|
+
typography.h2 = { fontFamily: f.display };
|
|
45
|
+
typography.h3 = { fontFamily: f.display };
|
|
46
|
+
typography.h4 = { fontFamily: f.display };
|
|
47
|
+
typography.h5 = { fontFamily: f.display };
|
|
48
|
+
typography.h6 = { fontFamily: f.display };
|
|
49
|
+
}
|
|
50
|
+
const base = {};
|
|
51
|
+
if (Object.keys(palette).length > 0) base.palette = palette;
|
|
52
|
+
if (Object.keys(typography).length > 0) base.typography = typography;
|
|
53
|
+
if (r.md) {
|
|
54
|
+
const radiusPx = parseRadiusPx(r.md);
|
|
55
|
+
if (radiusPx !== void 0) base.shape = { borderRadius: radiusPx };
|
|
56
|
+
}
|
|
57
|
+
return overrides ? deepMerge(base, overrides) : base;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
rootTaleThemeToMui
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=mui.js.map
|
package/dist/mui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mui.ts"],"sourcesContent":["/**\n * `@roottale/cms-renderer-next/mui` — `RootTaleTheme`(admin /design 에서 fetch 하는\n * 런타임 테마 객체) → MUI `ThemeOptions` 호환 객체 브리지.\n *\n * 목적(ADR-0040 §Decisions, P0.5 RFC §③): MUI 기반 고객 사이트(예: kjmtax)가\n * 자체 하드코딩 palette 를 버리고 admin /design 토큰을 **단일 SSoT** 로 쓰도록.\n * 탈-MUI 가 아니라 *이중 토큰 제거 + 공존 충돌 최소화* 가 목적이다.\n *\n * 설계:\n * - **런타임 MUI 의존 없음**. 반환값은 MUI `ThemeOptions` 에 구조적으로 assignable 한\n * 평범한 객체 — `@mui/material` 의 `createTheme()` 에 그대로 넘긴다. 이 패키지는\n * MUI-free 를 유지(비-MUI 고객사 roottale-web 은 이 서브패스를 import 하지 않음).\n * - 의미가 명확한 매핑만 수행. 모호한 토큰(`muted` 배경 등)은 CSS 변수\n * (`<RootTaleThemeProvider>`) 소비로 남긴다 — MUI 에 억지 매핑하지 않는다.\n *\n * ```ts\n * import { createTheme } from \"@mui/material/styles\";\n * import { fetchTheme } from \"@roottale/cms-client/server\";\n * import { rootTaleThemeToMui } from \"@roottale/cms-renderer-next/mui\";\n *\n * const rt = await fetchTheme({ apiKey: process.env.ROOTTALE_API_KEY! });\n * const theme = createTheme(rootTaleThemeToMui(rt, {\n * // 브랜드 고유값은 overrides 로 (deep-merge, overrides 우선).\n * palette: { secondary: { main: \"#0B1F49\" } },\n * }));\n * ```\n */\nimport type { RootTaleTheme } from \"@roottale/cms-client/server\";\n\n/** MUI `ThemeOptions` 의 구조적 부분집합 — `createTheme()` 에 assignable. */\nexport interface RootTaleMuiThemeOptions {\n palette?: {\n primary?: { main: string; contrastText?: string };\n background?: { default?: string };\n text?: { primary?: string; secondary?: string };\n divider?: string;\n [k: string]: unknown;\n };\n typography?: {\n fontFamily?: string;\n h1?: { fontFamily?: string };\n h2?: { fontFamily?: string };\n h3?: { fontFamily?: string };\n h4?: { fontFamily?: string };\n h5?: { fontFamily?: string };\n h6?: { fontFamily?: string };\n [k: string]: unknown;\n };\n shape?: { borderRadius?: number };\n [k: string]: unknown;\n}\n\n/** CSS 길이(\"8px\" / \"0.5rem\" / \"6\")를 MUI shape.borderRadius(px number)로 파싱. */\nfunction parseRadiusPx(raw: string): number | undefined {\n const v = raw.trim();\n const num = Number.parseFloat(v);\n if (Number.isNaN(num)) return undefined;\n if (v.endsWith(\"rem\")) return num * 16;\n if (v.endsWith(\"em\")) return num * 16;\n // \"8px\" | \"8\" | \"0\" 모두 px 로 간주.\n return num;\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n return typeof x === \"object\" && x !== null && !Array.isArray(x);\n}\n\n/** plain object 깊은 병합. `b`(overrides) 우선. 배열/원시값은 b 가 덮어씀. */\nfunction deepMerge<T extends Record<string, unknown>>(a: T, b: Record<string, unknown>): T {\n const out: Record<string, unknown> = { ...a };\n for (const [k, bv] of Object.entries(b)) {\n const av = out[k];\n out[k] = isPlainObject(av) && isPlainObject(bv) ? deepMerge(av, bv) : bv;\n }\n return out as T;\n}\n\n/**\n * `RootTaleTheme` → MUI `ThemeOptions` 호환 객체.\n *\n * 매핑:\n * - `colors.primary` → `palette.primary.main`, `colors.primaryForeground` → `…contrastText`\n * - `colors.background` → `palette.background.default`\n * - `colors.foreground` → `palette.text.primary`, `colors.mutedForeground` → `…secondary`\n * - `colors.border` → `palette.divider`\n * - `fonts.body` → `typography.fontFamily`, `fonts.display` → `typography.h1..h6.fontFamily`\n * - `radius.md` → `shape.borderRadius`(px)\n *\n * `colors.muted` 는 명확한 MUI 대응이 없어 의도적으로 미매핑(CSS 변수로 소비).\n *\n * @param theme admin /design 테마(빈/부분 객체 허용)\n * @param overrides 브랜드 고유값(deep-merge, overrides 우선)\n */\nexport function rootTaleThemeToMui(\n theme: RootTaleTheme | null | undefined,\n overrides?: RootTaleMuiThemeOptions,\n): RootTaleMuiThemeOptions {\n const c = theme?.colors ?? {};\n const f = theme?.fonts ?? {};\n const r = theme?.radius ?? {};\n\n const palette: NonNullable<RootTaleMuiThemeOptions[\"palette\"]> = {};\n if (c.primary) {\n palette.primary = {\n main: c.primary,\n ...(c.primaryForeground ? { contrastText: c.primaryForeground } : {}),\n };\n }\n if (c.background) palette.background = { default: c.background };\n if (c.foreground || c.mutedForeground) {\n palette.text = {\n ...(c.foreground ? { primary: c.foreground } : {}),\n ...(c.mutedForeground ? { secondary: c.mutedForeground } : {}),\n };\n }\n if (c.border) palette.divider = c.border;\n\n const typography: NonNullable<RootTaleMuiThemeOptions[\"typography\"]> = {};\n if (f.body) typography.fontFamily = f.body;\n if (f.display) {\n typography.h1 = { fontFamily: f.display };\n typography.h2 = { fontFamily: f.display };\n typography.h3 = { fontFamily: f.display };\n typography.h4 = { fontFamily: f.display };\n typography.h5 = { fontFamily: f.display };\n typography.h6 = { fontFamily: f.display };\n }\n\n const base: RootTaleMuiThemeOptions = {};\n if (Object.keys(palette).length > 0) base.palette = palette;\n if (Object.keys(typography).length > 0) base.typography = typography;\n if (r.md) {\n const radiusPx = parseRadiusPx(r.md);\n if (radiusPx !== undefined) base.shape = { borderRadius: radiusPx };\n }\n\n return overrides ? deepMerge(base, overrides as Record<string, unknown>) : base;\n}\n"],"mappings":";AAqDA,SAAS,cAAc,KAAiC;AACtD,QAAM,IAAI,IAAI,KAAK;AACnB,QAAM,MAAM,OAAO,WAAW,CAAC;AAC/B,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAC9B,MAAI,EAAE,SAAS,KAAK,EAAG,QAAO,MAAM;AACpC,MAAI,EAAE,SAAS,IAAI,EAAG,QAAO,MAAM;AAEnC,SAAO;AACT;AAEA,SAAS,cAAc,GAA0C;AAC/D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAGA,SAAS,UAA6C,GAAM,GAA+B;AACzF,QAAM,MAA+B,EAAE,GAAG,EAAE;AAC5C,aAAW,CAAC,GAAG,EAAE,KAAK,OAAO,QAAQ,CAAC,GAAG;AACvC,UAAM,KAAK,IAAI,CAAC;AAChB,QAAI,CAAC,IAAI,cAAc,EAAE,KAAK,cAAc,EAAE,IAAI,UAAU,IAAI,EAAE,IAAI;AAAA,EACxE;AACA,SAAO;AACT;AAkBO,SAAS,mBACd,OACA,WACyB;AACzB,QAAM,IAAI,OAAO,UAAU,CAAC;AAC5B,QAAM,IAAI,OAAO,SAAS,CAAC;AAC3B,QAAM,IAAI,OAAO,UAAU,CAAC;AAE5B,QAAM,UAA2D,CAAC;AAClE,MAAI,EAAE,SAAS;AACb,YAAQ,UAAU;AAAA,MAChB,MAAM,EAAE;AAAA,MACR,GAAI,EAAE,oBAAoB,EAAE,cAAc,EAAE,kBAAkB,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AACA,MAAI,EAAE,WAAY,SAAQ,aAAa,EAAE,SAAS,EAAE,WAAW;AAC/D,MAAI,EAAE,cAAc,EAAE,iBAAiB;AACrC,YAAQ,OAAO;AAAA,MACb,GAAI,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,IAAI,CAAC;AAAA,MAChD,GAAI,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AACA,MAAI,EAAE,OAAQ,SAAQ,UAAU,EAAE;AAElC,QAAM,aAAiE,CAAC;AACxE,MAAI,EAAE,KAAM,YAAW,aAAa,EAAE;AACtC,MAAI,EAAE,SAAS;AACb,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AACxC,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AACxC,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AACxC,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AACxC,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AACxC,eAAW,KAAK,EAAE,YAAY,EAAE,QAAQ;AAAA,EAC1C;AAEA,QAAM,OAAgC,CAAC;AACvC,MAAI,OAAO,KAAK,OAAO,EAAE,SAAS,EAAG,MAAK,UAAU;AACpD,MAAI,OAAO,KAAK,UAAU,EAAE,SAAS,EAAG,MAAK,aAAa;AAC1D,MAAI,EAAE,IAAI;AACR,UAAM,WAAW,cAAc,EAAE,EAAE;AACnC,QAAI,aAAa,OAAW,MAAK,QAAQ,EAAE,cAAc,SAAS;AAAA,EACpE;AAEA,SAAO,YAAY,UAAU,MAAM,SAAoC,IAAI;AAC7E;","names":[]}
|
package/dist/server.d.ts
CHANGED
|
@@ -31,24 +31,24 @@ declare function RootTalePostCard(props: RootTalePostCardProps): ReactElement;
|
|
|
31
31
|
/**
|
|
32
32
|
* `@roottale/cms-renderer/toc` — Tiptap doc → TOC entries + heading id injection.
|
|
33
33
|
*
|
|
34
|
-
* Server-only utility. Walks a Tiptap JSON doc, extracts
|
|
34
|
+
* Server-only utility. Walks a Tiptap JSON doc, extracts H1-H4 headings, and
|
|
35
35
|
* derives stable kebab-case ids (Korean-safe). The companion `attachHeadingIds`
|
|
36
|
-
* mutates a shallow clone of the doc so renderers can emit
|
|
37
|
-
*
|
|
36
|
+
* mutates a shallow clone of the doc so renderers can emit heading anchors
|
|
37
|
+
* that match what `<RootTaleTableOfContents>` links to.
|
|
38
38
|
*
|
|
39
39
|
* Internal ancestor: `roottale-internal/packages/cms-renderer/src/toc.ts`
|
|
40
40
|
* (HTML-string based, vendored snapshot). Ported to Tiptap-JSON because the
|
|
41
41
|
* platform renderer no longer round-trips through HTML before render.
|
|
42
42
|
*/
|
|
43
43
|
interface TocEntry {
|
|
44
|
-
level: 2 | 3;
|
|
44
|
+
level: 2 | 3 | 4;
|
|
45
45
|
text: string;
|
|
46
46
|
id: string;
|
|
47
47
|
}
|
|
48
48
|
declare function headingToId(text: string): string;
|
|
49
49
|
declare function extractToc(doc: Record<string, unknown>): TocEntry[];
|
|
50
50
|
/**
|
|
51
|
-
* Returns a shallow-cloned Tiptap doc where each
|
|
51
|
+
* Returns a shallow-cloned Tiptap doc where each H1-H4 carries the matching
|
|
52
52
|
* `attrs.id` from `entries` (same order as `extractToc` returns). Existing
|
|
53
53
|
* `id` attrs are overwritten so renderer + TOC always agree on anchors.
|
|
54
54
|
*/
|
|
@@ -232,7 +232,7 @@ type RootTaleBlogPostProps = {
|
|
|
232
232
|
notFoundElement?: ReactElement;
|
|
233
233
|
/**
|
|
234
234
|
* When true, prepends an auto-derived `<RootTaleTableOfContents>` before
|
|
235
|
-
* the article body (H2
|
|
235
|
+
* the article body (H2-H4 headings only). Default: false — keep markup
|
|
236
236
|
* stable for customers who already control their own layout.
|
|
237
237
|
*/
|
|
238
238
|
showTableOfContents?: boolean;
|
package/dist/server.js
CHANGED
|
@@ -16,14 +16,18 @@ function RootTalePostCard(props) {
|
|
|
16
16
|
const showDate = display ? display.showDate : true;
|
|
17
17
|
const showAuthor = display ? display.showAuthor : false;
|
|
18
18
|
const authorName = post.authorName;
|
|
19
|
-
const
|
|
20
|
-
showDate ? formatPublishedDate(post.publishedAt) : null,
|
|
21
|
-
showAuthor && authorName ? `\uC791\uC131\uC790 ${authorName}` : null
|
|
22
|
-
].filter(Boolean).join(" \xB7 ");
|
|
19
|
+
const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : "";
|
|
23
20
|
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: [
|
|
24
|
-
|
|
21
|
+
publishedDate || showAuthor && authorName ? /* @__PURE__ */ jsxs("p", { className: "rt-cms-meta", children: [
|
|
22
|
+
publishedDate ? /* @__PURE__ */ jsx("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
|
|
23
|
+
showAuthor && authorName ? /* @__PURE__ */ jsxs("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
|
|
24
|
+
"\uC791\uC131\uC790 ",
|
|
25
|
+
authorName
|
|
26
|
+
] }) : null
|
|
27
|
+
] }) : null,
|
|
25
28
|
/* @__PURE__ */ jsx("h2", { className: "rt-cms-title", children: post.title }),
|
|
26
|
-
post.excerpt ? /* @__PURE__ */ jsx("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
|
|
29
|
+
post.excerpt ? /* @__PURE__ */ jsx("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null,
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "rt-cms-card-action", "aria-hidden": "true", children: "\uC77D\uAE30" })
|
|
27
31
|
] }) });
|
|
28
32
|
}
|
|
29
33
|
function defaultHref(post) {
|
|
@@ -44,8 +48,11 @@ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
|
44
48
|
function RootTaleTableOfContents(props) {
|
|
45
49
|
const { entries, title = "\uBAA9\uCC28" } = props;
|
|
46
50
|
if (entries.length === 0) return null;
|
|
47
|
-
return /* @__PURE__ */
|
|
48
|
-
|
|
51
|
+
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: [
|
|
52
|
+
/* @__PURE__ */ jsxs2("summary", { className: "rt-cms-toc-summary", children: [
|
|
53
|
+
title ? /* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-title", children: title }) : null,
|
|
54
|
+
/* @__PURE__ */ jsx2("span", { className: "rt-cms-toc-toggle", "aria-hidden": "true" })
|
|
55
|
+
] }),
|
|
49
56
|
/* @__PURE__ */ jsx2("ol", { className: "rt-cms-toc-list", children: entries.map((entry) => /* @__PURE__ */ jsx2(
|
|
50
57
|
"li",
|
|
51
58
|
{
|
|
@@ -54,10 +61,15 @@ function RootTaleTableOfContents(props) {
|
|
|
54
61
|
},
|
|
55
62
|
entry.id
|
|
56
63
|
)) })
|
|
57
|
-
] });
|
|
64
|
+
] }) });
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
// src/toc.ts
|
|
68
|
+
function tocLevel(rawLevel) {
|
|
69
|
+
if (rawLevel < 1 || rawLevel > 4) return null;
|
|
70
|
+
if (rawLevel < 3) return 2;
|
|
71
|
+
return rawLevel === 3 ? 3 : 4;
|
|
72
|
+
}
|
|
61
73
|
function headingToId(text) {
|
|
62
74
|
return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
63
75
|
}
|
|
@@ -74,14 +86,15 @@ function extractToc(doc) {
|
|
|
74
86
|
for (const node of content) {
|
|
75
87
|
if (node?.type !== "heading") continue;
|
|
76
88
|
const rawLevel = Number(node.attrs?.level ?? 2);
|
|
77
|
-
|
|
89
|
+
const level = tocLevel(rawLevel);
|
|
90
|
+
if (level === null) continue;
|
|
78
91
|
const text = textOf(node).trim();
|
|
79
92
|
if (!text) continue;
|
|
80
93
|
const base = headingToId(text) || "heading";
|
|
81
94
|
const count = idCounts.get(base) ?? 0;
|
|
82
95
|
const id = count === 0 ? base : `${base}-${count}`;
|
|
83
96
|
idCounts.set(base, count + 1);
|
|
84
|
-
entries.push({ level
|
|
97
|
+
entries.push({ level, text, id });
|
|
85
98
|
}
|
|
86
99
|
return entries;
|
|
87
100
|
}
|
|
@@ -92,7 +105,7 @@ function attachHeadingIds(doc, entries) {
|
|
|
92
105
|
const nextContent = content.map((node) => {
|
|
93
106
|
if (node?.type !== "heading") return node;
|
|
94
107
|
const rawLevel = Number(node.attrs?.level ?? 2);
|
|
95
|
-
if (rawLevel
|
|
108
|
+
if (rawLevel < 1 || rawLevel > 4) return node;
|
|
96
109
|
if (!textOf(node).trim()) return node;
|
|
97
110
|
const entry = entries[cursor++];
|
|
98
111
|
if (!entry) return node;
|
|
@@ -314,12 +327,13 @@ function RootTaleLeadForm(props) {
|
|
|
314
327
|
}
|
|
315
328
|
|
|
316
329
|
// src/block-to-react.tsx
|
|
330
|
+
import { sanitizeHtml } from "@roottale/cms-core";
|
|
317
331
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
318
332
|
function isString(value) {
|
|
319
333
|
return typeof value === "string";
|
|
320
334
|
}
|
|
321
335
|
function htmlContent(html) {
|
|
322
|
-
return { __html: html };
|
|
336
|
+
return { __html: sanitizeHtml(html) };
|
|
323
337
|
}
|
|
324
338
|
function renderInner(blocks) {
|
|
325
339
|
return blocks.map((b, i) => renderBlock(b, i));
|
|
@@ -340,18 +354,15 @@ function renderBlock(block, key = 0) {
|
|
|
340
354
|
}
|
|
341
355
|
case "core/heading": {
|
|
342
356
|
const levelRaw = block.attributes.level;
|
|
343
|
-
const level = typeof levelRaw === "number" && levelRaw >=
|
|
357
|
+
const level = typeof levelRaw === "number" && levelRaw >= 2 && levelRaw <= 4 ? levelRaw : 2;
|
|
344
358
|
const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
|
|
345
359
|
const props = {
|
|
346
360
|
"data-block-id": dataBlockId,
|
|
347
361
|
dangerouslySetInnerHTML: htmlContent(text)
|
|
348
362
|
};
|
|
349
|
-
if (level === 1) return /* @__PURE__ */ jsx5("h1", { ...props }, key);
|
|
350
363
|
if (level === 2) return /* @__PURE__ */ jsx5("h2", { ...props }, key);
|
|
351
364
|
if (level === 3) return /* @__PURE__ */ jsx5("h3", { ...props }, key);
|
|
352
|
-
|
|
353
|
-
if (level === 5) return /* @__PURE__ */ jsx5("h5", { ...props }, key);
|
|
354
|
-
return /* @__PURE__ */ jsx5("h6", { ...props }, key);
|
|
365
|
+
return /* @__PURE__ */ jsx5("h4", { ...props }, key);
|
|
355
366
|
}
|
|
356
367
|
case "core/image": {
|
|
357
368
|
const src = isString(block.attributes.url) ? block.attributes.url : "";
|
|
@@ -513,10 +524,14 @@ async function RootTaleBlogPost(props) {
|
|
|
513
524
|
const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];
|
|
514
525
|
const renderDoc = toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;
|
|
515
526
|
const authorName = post.authorName;
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
527
|
+
const authorImageUrl = post.authorImageUrl;
|
|
528
|
+
const postAuthorBio = post.authorBio;
|
|
529
|
+
const publishedDate = display.showDate ? formatPublishedDate2(post.publishedAt) : "";
|
|
530
|
+
const showTopAuthor = display.showAuthor && Boolean(authorName);
|
|
531
|
+
const displayAuthorName = display.authorProfileName ?? authorName;
|
|
532
|
+
const authorBio = display.authorProfileBio ?? postAuthorBio ?? display.authorCardDescription;
|
|
533
|
+
const authorImageUrlForCard = display.authorProfileImageUrl ?? authorImageUrl;
|
|
534
|
+
const showAuthorCard = display.showAuthorCard && Boolean(displayAuthorName);
|
|
520
535
|
return /* @__PURE__ */ jsxs6(
|
|
521
536
|
"article",
|
|
522
537
|
{
|
|
@@ -524,17 +539,48 @@ async function RootTaleBlogPost(props) {
|
|
|
524
539
|
className: "rt-cms-article",
|
|
525
540
|
style: themeStyle ?? void 0,
|
|
526
541
|
children: [
|
|
527
|
-
/* @__PURE__ */ jsxs6("header", { children: [
|
|
528
|
-
|
|
542
|
+
/* @__PURE__ */ jsxs6("header", { className: "rt-cms-post-header", children: [
|
|
543
|
+
publishedDate || showTopAuthor ? /* @__PURE__ */ jsxs6("p", { className: "rt-cms-meta", children: [
|
|
544
|
+
publishedDate ? /* @__PURE__ */ jsx6("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
|
|
545
|
+
showTopAuthor ? /* @__PURE__ */ jsxs6("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
|
|
546
|
+
"\uC791\uC131\uC790 ",
|
|
547
|
+
authorName
|
|
548
|
+
] }) : null
|
|
549
|
+
] }) : null,
|
|
529
550
|
/* @__PURE__ */ jsx6("h1", { className: "rt-cms-title", children: post.title }),
|
|
530
551
|
post.excerpt ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
|
|
531
552
|
] }),
|
|
532
553
|
toc.length > 0 ? /* @__PURE__ */ jsx6(RootTaleTableOfContents, { entries: toc, title: display.tocTitle }) : null,
|
|
533
|
-
/* @__PURE__ */ jsx6(RenderTiptap, { doc: renderDoc })
|
|
554
|
+
/* @__PURE__ */ jsx6("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx6(RenderTiptap, { doc: renderDoc }) }),
|
|
555
|
+
showAuthorCard && displayAuthorName ? /* @__PURE__ */ jsxs6("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
|
|
556
|
+
authorImageUrlForCard ? /* @__PURE__ */ jsx6(
|
|
557
|
+
"img",
|
|
558
|
+
{
|
|
559
|
+
className: "rt-cms-author-card__image",
|
|
560
|
+
src: authorImageUrlForCard,
|
|
561
|
+
alt: "",
|
|
562
|
+
loading: "lazy"
|
|
563
|
+
}
|
|
564
|
+
) : /* @__PURE__ */ jsx6("div", { className: "rt-cms-author-card__avatar", "aria-hidden": "true", children: authorInitials(displayAuthorName) }),
|
|
565
|
+
/* @__PURE__ */ jsxs6("div", { className: "rt-cms-author-card__body", children: [
|
|
566
|
+
/* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__eyebrow", children: display.authorCardEyebrow }),
|
|
567
|
+
/* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__name", children: displayAuthorName }),
|
|
568
|
+
authorBio ? /* @__PURE__ */ jsx6("p", { className: "rt-cms-author-card__description", children: authorBio }) : null
|
|
569
|
+
] })
|
|
570
|
+
] }) : null
|
|
534
571
|
]
|
|
535
572
|
}
|
|
536
573
|
);
|
|
537
574
|
}
|
|
575
|
+
function authorInitials(name) {
|
|
576
|
+
const trimmed = name.trim();
|
|
577
|
+
if (!trimmed) return "";
|
|
578
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
579
|
+
if (parts.length >= 2) {
|
|
580
|
+
return `${parts[0]?.[0] ?? ""}${parts[1]?.[0] ?? ""}`.toUpperCase();
|
|
581
|
+
}
|
|
582
|
+
return [...trimmed].slice(0, 2).join("").toUpperCase();
|
|
583
|
+
}
|
|
538
584
|
async function resolveThemeStyle(input) {
|
|
539
585
|
const { themeProp, apiKey, baseUrl } = input;
|
|
540
586
|
if (themeProp === null) return null;
|
|
@@ -604,13 +650,14 @@ function renderNode(node, key) {
|
|
|
604
650
|
case "paragraph":
|
|
605
651
|
return /* @__PURE__ */ jsx6("p", { style: alignStyle(node), children }, key);
|
|
606
652
|
case "heading": {
|
|
607
|
-
const
|
|
653
|
+
const rawLevel = Number(node.attrs?.level ?? 2);
|
|
654
|
+
const level = Math.min(Math.max(Number.isFinite(rawLevel) ? rawLevel : 2, 2), 4);
|
|
608
655
|
const idAttr = node.attrs?.id;
|
|
609
656
|
const id = typeof idAttr === "string" && idAttr.length > 0 ? idAttr : void 0;
|
|
610
657
|
const style = alignStyle(node);
|
|
611
|
-
if (level === 1) return /* @__PURE__ */ jsx6("h1", { id, style, children }, key);
|
|
612
658
|
if (level === 2) return /* @__PURE__ */ jsx6("h2", { id, style, children }, key);
|
|
613
|
-
return /* @__PURE__ */ jsx6("h3", { id, style, children }, key);
|
|
659
|
+
if (level === 3) return /* @__PURE__ */ jsx6("h3", { id, style, children }, key);
|
|
660
|
+
return /* @__PURE__ */ jsx6("h4", { id, style, children }, key);
|
|
614
661
|
}
|
|
615
662
|
case "bulletList":
|
|
616
663
|
return /* @__PURE__ */ jsx6("ul", { children }, key);
|