@roottale/cms-renderer-next 0.53.0 → 0.55.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 +52 -0
- package/README.md +10 -2
- package/dist/{blog-filter-BKCGM0qd.d.ts → blog-filter-BQNQL3Mk.d.ts} +5 -0
- package/dist/{chunk-HM5VP7SR.js → chunk-FJANIAZN.js} +1 -1
- package/dist/chunk-FJANIAZN.js.map +1 -0
- package/dist/chunk-GVKENK6H.js +75 -0
- package/dist/chunk-GVKENK6H.js.map +1 -0
- package/dist/cms-public.css +50 -0
- package/dist/exposure-client.d.ts +9 -0
- package/dist/exposure-client.js +8 -0
- package/dist/exposure-client.js.map +1 -0
- package/dist/routes.d.ts +18 -3
- package/dist/routes.js +35 -14
- package/dist/routes.js.map +1 -1
- package/dist/server.d.ts +71 -3
- package/dist/server.js +173 -29
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-HM5VP7SR.js.map +0 -1
package/dist/server.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { CmsPostContent, RootTaleTheme, CmsPostType } from '@roottale/cms-client
|
|
|
3
3
|
export { RootTaleTheme } from '@roottale/cms-client/server';
|
|
4
4
|
import { RootTaleThemeStyle } from '@roottale/ui-react/theme';
|
|
5
5
|
import { Block, BindingResolver, RouteCollection } from '@roottale/cms-core';
|
|
6
|
-
export { a as BlogCategory, B as BlogCategoryWithCount, c as collectCategories, b as collectCategoriesWithCounts, d as defaultCategoryHref, f as filterPostsByCategory } from './blog-filter-
|
|
6
|
+
export { a as BlogCategory, B as BlogCategoryWithCount, c as collectCategories, b as collectCategoriesWithCounts, d as defaultCategoryHref, f as filterPostsByCategory } from './blog-filter-BQNQL3Mk.js';
|
|
7
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `<RootTalePostCard>` — server-only summary card for a single CmsPostContent.
|
|
@@ -237,7 +238,16 @@ declare function RootTaleBlogCategories(props: RootTaleBlogCategoriesProps): Pro
|
|
|
237
238
|
|
|
238
239
|
type RootTaleBlogPostProps = {
|
|
239
240
|
apiKey: string;
|
|
240
|
-
|
|
241
|
+
/** 발행 글 slug 또는 id. `previewToken` 을 줄 때는 생략할 수 있다. */
|
|
242
|
+
slugOrId?: string;
|
|
243
|
+
/**
|
|
244
|
+
* 어드민 편집기가 발급한 미리보기 토큰(URL `?token=`). 주면 `slugOrId` 대신
|
|
245
|
+
* `fetchPostPreview` 로 초안을 받아 **같은 템플릿**으로 그린다 — 미리보기 = 결과물.
|
|
246
|
+
* 만료(410)면 `previewExpiredElement`, 없으면(404) `notFoundElement`.
|
|
247
|
+
*/
|
|
248
|
+
previewToken?: string;
|
|
249
|
+
/** 미리보기 토큰이 만료됐을 때 대신 그릴 요소(기본: 만료 안내 문구). */
|
|
250
|
+
previewExpiredElement?: ReactElement;
|
|
241
251
|
baseUrl?: string;
|
|
242
252
|
notFoundElement?: ReactElement;
|
|
243
253
|
showTitle?: boolean;
|
|
@@ -269,6 +279,21 @@ declare function RootTaleFaqJsonLd({ doc, }: {
|
|
|
269
279
|
readonly doc: unknown;
|
|
270
280
|
}): ReactElement | null;
|
|
271
281
|
|
|
282
|
+
interface RootTalePreviewNoticeProps {
|
|
283
|
+
/** `ko`(기본) · `en`. */
|
|
284
|
+
locale?: string;
|
|
285
|
+
/** 기본 안내 문구를 바꾸고 싶을 때. */
|
|
286
|
+
message?: string;
|
|
287
|
+
/** 토큰 만료 시각(ISO) — 주면 "HH:MM까지" 를 덧붙인다. */
|
|
288
|
+
expiresAt?: string;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 미리보기 화면 상단 안내 띠. 본문 템플릿은 발행 화면과 완전히 같게 두고, "이건
|
|
292
|
+
* 미리보기다" 라는 신호만 이 띠 하나로 준다. 검색엔진 색인 차단은 metadata
|
|
293
|
+
* (`buildPreviewMetadata`) 몫이며, 이 컴포넌트는 사람용 안내다.
|
|
294
|
+
*/
|
|
295
|
+
declare function RootTalePreviewNotice({ locale, message, expiresAt, }: RootTalePreviewNoticeProps): ReactElement;
|
|
296
|
+
|
|
272
297
|
type RootTalePageProps = {
|
|
273
298
|
apiKey: string;
|
|
274
299
|
slugOrId: string;
|
|
@@ -277,9 +302,23 @@ type RootTalePageProps = {
|
|
|
277
302
|
showTitle?: boolean;
|
|
278
303
|
theme?: RootTaleThemeInput;
|
|
279
304
|
locale?: string;
|
|
305
|
+
/** 페이지 JSON을 렌더하기 직전 콘텐츠 참조를 실제 섹션 props로 바꾸는 서버 함수. */
|
|
306
|
+
prepareBody?: (document: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
280
307
|
};
|
|
281
308
|
declare function RootTalePage(props: RootTalePageProps): Promise<ReactElement>;
|
|
282
309
|
|
|
310
|
+
interface RootTaleExposureSlotProps {
|
|
311
|
+
readonly apiKey: string;
|
|
312
|
+
readonly baseUrl?: string;
|
|
313
|
+
readonly siteId?: string;
|
|
314
|
+
readonly slotKey: string;
|
|
315
|
+
readonly path: string;
|
|
316
|
+
readonly allowedVariants: readonly string[];
|
|
317
|
+
readonly revalidate?: number;
|
|
318
|
+
}
|
|
319
|
+
/** FRONT가 코드로 만든 슬롯 안에서만 활성 배너·팝업을 렌더한다. */
|
|
320
|
+
declare function RootTaleExposureSlot(props: RootTaleExposureSlotProps): Promise<react_jsx_runtime.JSX.Element | null>;
|
|
321
|
+
|
|
283
322
|
type RenderTiptapProps = {
|
|
284
323
|
readonly doc: Record<string, unknown>;
|
|
285
324
|
readonly fields?: Record<string, unknown> | null;
|
|
@@ -288,4 +327,33 @@ type RenderTiptapProps = {
|
|
|
288
327
|
};
|
|
289
328
|
declare function RenderTiptap({ doc, fields, apiKey, baseUrl, }: RenderTiptapProps): ReactElement;
|
|
290
329
|
|
|
291
|
-
|
|
330
|
+
/**
|
|
331
|
+
* 본문 렌더 소스 결정 — HTML 정본 우선, 없으면 Block JSON 폴백.
|
|
332
|
+
*
|
|
333
|
+
* 배경: 외부에서 이관한 글은 **HTML 이 정본**이고 body_json 은 그 파생/대체본이다.
|
|
334
|
+
* 파생본은 표·중첩 목록 같은 구조에서 원문과 미묘하게 달라질 수 있으므로, 원문이
|
|
335
|
+
* 남아 있으면 그것을 보여주는 편이 정확하다. 반대로 에디터에서 편집된 글은
|
|
336
|
+
* body_html 이 비워지므로(저장 경로가 무효화) 자동으로 JSON 경로로 돌아온다.
|
|
337
|
+
*
|
|
338
|
+
* 안전성: HTML 은 신뢰하지 않는다. 최종 sink 는 공용 `sanitizeHtml` 하나뿐이며
|
|
339
|
+
* (cms-core), 렌더 직전 여기서 통과시킨다 — 저장 시점에 정제됐더라도 다시 한다
|
|
340
|
+
* (저장 경로가 여럿이고 sanitizer 규칙은 갱신되므로, 방어는 렌더 시점 기준).
|
|
341
|
+
*
|
|
342
|
+
* sanitize 결과가 실질적으로 비면(태그만 남고 내용이 없음) HTML 을 쓰지 않고
|
|
343
|
+
* JSON 으로 폴백한다 — 빈 화면보다 파생본이 낫다.
|
|
344
|
+
*/
|
|
345
|
+
type PostBodySource = {
|
|
346
|
+
readonly kind: "html";
|
|
347
|
+
readonly html: string;
|
|
348
|
+
} | {
|
|
349
|
+
readonly kind: "json";
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* 렌더에 쓸 본문 소스를 고른다.
|
|
353
|
+
*
|
|
354
|
+
* @param bodyHtml 원문 HTML 정본 (null/미포함 = 구 서버이거나 JSON 이 정본).
|
|
355
|
+
* @returns `kind: "html"` 이면 `html` 은 **이미 sanitize 된** 값이라 그대로 주입 가능.
|
|
356
|
+
*/
|
|
357
|
+
declare function resolvePostBodySource(bodyHtml: string | null | undefined): PostBodySource;
|
|
358
|
+
|
|
359
|
+
export { type BlogPostBreadcrumbOptions, type CtaButton, type CtaButtonType, type FloatingCtaPosition, type LeadFormVertical, type PostBodySource, RenderTiptap, RootTaleBlogCategories, type RootTaleBlogCategoriesProps, RootTaleBlogList, type RootTaleBlogListProps, RootTaleBlogPost, type RootTaleBlogPostProps, type RootTaleCssVars, RootTaleExposureSlot, type RootTaleExposureSlotProps, RootTaleFaqJsonLd, RootTaleFloatingCta, type RootTaleFloatingCtaProps, RootTaleLeadForm, type RootTaleLeadFormProps, RootTalePage, type RootTalePageProps, RootTalePostCard, type RootTalePostCardProps, RootTalePreviewNotice, type RootTalePreviewNoticeProps, RootTaleTableOfContents, type RootTaleTableOfContentsProps, type RootTaleThemeInput, RootTaleThemeProvider, type TocEntry, attachHeadingIds, extractToc, headingToId, renderBlock, renderBlocks, renderBoundBlocks, resolvePostBodySource, themeToCssVars };
|
package/dist/server.js
CHANGED
|
@@ -5,13 +5,16 @@ import {
|
|
|
5
5
|
defaultCategoryHref,
|
|
6
6
|
filterPostsByCategory,
|
|
7
7
|
trimSlash
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FJANIAZN.js";
|
|
9
9
|
import {
|
|
10
10
|
getSectionNodeRenderer
|
|
11
11
|
} from "./chunk-KSPPYX22.js";
|
|
12
12
|
import {
|
|
13
13
|
LeadFormMeta
|
|
14
14
|
} from "./chunk-VG6UUF4D.js";
|
|
15
|
+
import {
|
|
16
|
+
RootTaleExposure
|
|
17
|
+
} from "./chunk-GVKENK6H.js";
|
|
15
18
|
|
|
16
19
|
// src/PostCard.tsx
|
|
17
20
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -81,6 +84,8 @@ var DICTS = {
|
|
|
81
84
|
relatedPostsTitle: "\uAD00\uB828 \uAE00",
|
|
82
85
|
postNotFound: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
83
86
|
pageNotFound: "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
87
|
+
previewExpired: "\uBBF8\uB9AC\uBCF4\uAE30 \uB9C1\uD06C\uAC00 \uB9CC\uB8CC\uB410\uC5B4\uC694. \uAD00\uB9AC\uC790 \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uBBF8\uB9AC\uBCF4\uAE30\uB97C \uB2E4\uC2DC \uC5F4\uC5B4 \uC8FC\uC138\uC694.",
|
|
88
|
+
previewNotice: "\uBBF8\uB9AC\uBCF4\uAE30 \u2014 \uC544\uC9C1 \uACF5\uAC1C\uB418\uC9C0 \uC54A\uC740 \uB0B4\uC6A9\uC774\uBA70 \uAC80\uC0C9\uC5D0 \uB178\uCD9C\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
84
89
|
tableOfContentsTitle: "\uBAA9\uCC28"
|
|
85
90
|
},
|
|
86
91
|
en: {
|
|
@@ -90,6 +95,8 @@ var DICTS = {
|
|
|
90
95
|
relatedPostsTitle: "Related posts",
|
|
91
96
|
postNotFound: "Post not found.",
|
|
92
97
|
pageNotFound: "Page not found.",
|
|
98
|
+
previewExpired: "This preview link has expired. Open the preview again from the admin editor.",
|
|
99
|
+
previewNotice: "Preview \u2014 this content is not public and is hidden from search engines.",
|
|
93
100
|
tableOfContentsTitle: "Table of contents"
|
|
94
101
|
}
|
|
95
102
|
};
|
|
@@ -623,7 +630,6 @@ function attachHeadingIds(doc, entries) {
|
|
|
623
630
|
|
|
624
631
|
// src/server/blog-list.tsx
|
|
625
632
|
import {
|
|
626
|
-
fetchPosts,
|
|
627
633
|
resolvePostDisplay
|
|
628
634
|
} from "@roottale/cms-client/server";
|
|
629
635
|
import {
|
|
@@ -640,6 +646,18 @@ async function fetchBlogSettingsFailSoft(input) {
|
|
|
640
646
|
return fetchBlogSettings(input).catch(() => DEFAULT_BLOG_SETTINGS);
|
|
641
647
|
}
|
|
642
648
|
|
|
649
|
+
// src/server/fetch-posts-fail-soft.ts
|
|
650
|
+
import {
|
|
651
|
+
fetchPosts
|
|
652
|
+
} from "@roottale/cms-client/server";
|
|
653
|
+
async function fetchPostsFailSoft(options) {
|
|
654
|
+
try {
|
|
655
|
+
return await fetchPosts(options);
|
|
656
|
+
} catch {
|
|
657
|
+
return { items: [], hasMore: false, nextCursor: null };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
643
661
|
// src/server/post-links.ts
|
|
644
662
|
import {
|
|
645
663
|
resolvePostCollection,
|
|
@@ -726,7 +744,7 @@ async function RootTaleBlogList(props) {
|
|
|
726
744
|
} = props;
|
|
727
745
|
const postHref = props.postHref ?? (collections ? makeCollectionPostHref(collections) : defaultPostHref);
|
|
728
746
|
const [page, themeStyle, blogSettings] = await Promise.all([
|
|
729
|
-
|
|
747
|
+
fetchPostsFailSoft({
|
|
730
748
|
apiKey,
|
|
731
749
|
baseUrl,
|
|
732
750
|
limit,
|
|
@@ -787,9 +805,6 @@ async function RootTaleBlogList(props) {
|
|
|
787
805
|
}
|
|
788
806
|
|
|
789
807
|
// src/server/blog-categories.tsx
|
|
790
|
-
import {
|
|
791
|
-
fetchPosts as fetchPosts2
|
|
792
|
-
} from "@roottale/cms-client/server";
|
|
793
808
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
794
809
|
async function RootTaleBlogCategories(props) {
|
|
795
810
|
const {
|
|
@@ -804,7 +819,7 @@ async function RootTaleBlogCategories(props) {
|
|
|
804
819
|
theme: themeProp
|
|
805
820
|
} = props;
|
|
806
821
|
const [page, themeStyle] = await Promise.all([
|
|
807
|
-
|
|
822
|
+
fetchPostsFailSoft({
|
|
808
823
|
apiKey,
|
|
809
824
|
baseUrl,
|
|
810
825
|
limit,
|
|
@@ -839,7 +854,9 @@ async function RootTaleBlogCategories(props) {
|
|
|
839
854
|
// src/server/blog-post.tsx
|
|
840
855
|
import {
|
|
841
856
|
fetchPost,
|
|
842
|
-
|
|
857
|
+
fetchPostPreview,
|
|
858
|
+
fetchPosts as fetchPosts2,
|
|
859
|
+
isPreviewExpiredError,
|
|
843
860
|
resolvePostDisplay as resolvePostDisplay2,
|
|
844
861
|
selectRelatedPosts
|
|
845
862
|
} from "@roottale/cms-client/server";
|
|
@@ -931,12 +948,30 @@ function renderFieldFacts(post) {
|
|
|
931
948
|
] }, meta.name)) }) });
|
|
932
949
|
}
|
|
933
950
|
|
|
951
|
+
// src/server/post-body-source.ts
|
|
952
|
+
import { sanitizeHtml as sanitizeHtml2 } from "@roottale/cms-core";
|
|
953
|
+
function hasVisibleContent(html) {
|
|
954
|
+
if (/<(?:img|hr|br|iframe|video|audio)\b/i.test(html)) return true;
|
|
955
|
+
return html.replace(/<[^>]*>/g, "").replace(/ /gi, " ").trim().length > 0;
|
|
956
|
+
}
|
|
957
|
+
function resolvePostBodySource(bodyHtml) {
|
|
958
|
+
if (typeof bodyHtml !== "string") return { kind: "json" };
|
|
959
|
+
const trimmed = bodyHtml.trim();
|
|
960
|
+
if (trimmed.length === 0) return { kind: "json" };
|
|
961
|
+
const sanitized = sanitizeHtml2(trimmed);
|
|
962
|
+
if (!hasVisibleContent(sanitized)) return { kind: "json" };
|
|
963
|
+
return { kind: "html", html: sanitized };
|
|
964
|
+
}
|
|
965
|
+
|
|
934
966
|
// src/server/tiptap-node-renderer.tsx
|
|
967
|
+
import { Fragment as Fragment4 } from "react";
|
|
935
968
|
import {
|
|
936
969
|
bindingSrcValue,
|
|
937
970
|
bindingTextValue,
|
|
938
971
|
boundFieldValue,
|
|
939
|
-
|
|
972
|
+
ensureImportedHtmlDocumentRoots,
|
|
973
|
+
normalizeImageAlt,
|
|
974
|
+
scopeImportedCss
|
|
940
975
|
} from "@roottale/cms-core";
|
|
941
976
|
|
|
942
977
|
// src/server/tiptap-embed-renderer.tsx
|
|
@@ -1151,6 +1186,33 @@ function renderTiptapNode(value, key, context) {
|
|
|
1151
1186
|
key
|
|
1152
1187
|
);
|
|
1153
1188
|
}
|
|
1189
|
+
case "importedHtml": {
|
|
1190
|
+
const rawHtml = node.attrs?.html;
|
|
1191
|
+
if (typeof rawHtml !== "string" || rawHtml.length === 0) return null;
|
|
1192
|
+
const scope = typeof node.attrs?.scope === "string" ? node.attrs.scope : "";
|
|
1193
|
+
const assetUrls = Array.isArray(node.attrs?.assetUrls) ? node.attrs.assetUrls.filter(
|
|
1194
|
+
(value2) => typeof value2 === "string" && /^https:\/\//i.test(value2)
|
|
1195
|
+
) : [];
|
|
1196
|
+
const scopedCss = scopeImportedCss(
|
|
1197
|
+
typeof node.attrs?.css === "string" ? node.attrs.css : "",
|
|
1198
|
+
scope,
|
|
1199
|
+
assetUrls
|
|
1200
|
+
);
|
|
1201
|
+
return /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1202
|
+
scopedCss ? /* @__PURE__ */ jsx12("style", { "data-rt-imported-style": scope, children: scopedCss }) : null,
|
|
1203
|
+
/* @__PURE__ */ jsx12(
|
|
1204
|
+
"div",
|
|
1205
|
+
{
|
|
1206
|
+
className: "rt-imported-html",
|
|
1207
|
+
"data-rt-import-scope": scopedCss ? scope : void 0,
|
|
1208
|
+
style: { position: "relative", isolation: "isolate" },
|
|
1209
|
+
dangerouslySetInnerHTML: {
|
|
1210
|
+
__html: ensureImportedHtmlDocumentRoots(rawHtml, assetUrls)
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
)
|
|
1214
|
+
] }, key);
|
|
1215
|
+
}
|
|
1154
1216
|
case "columns":
|
|
1155
1217
|
return /* @__PURE__ */ jsx12("div", { className: "rt-columns", children }, key);
|
|
1156
1218
|
case "column":
|
|
@@ -1217,7 +1279,7 @@ function renderTiptapNode(value, key, context) {
|
|
|
1217
1279
|
}
|
|
1218
1280
|
|
|
1219
1281
|
// src/server/render-tiptap.tsx
|
|
1220
|
-
import { Fragment as
|
|
1282
|
+
import { Fragment as Fragment5, jsx as jsx13 } from "react/jsx-runtime";
|
|
1221
1283
|
function isDevelopmentRuntime() {
|
|
1222
1284
|
const processValue = Reflect.get(globalThis, "process");
|
|
1223
1285
|
if (!isRecord(processValue)) return true;
|
|
@@ -1252,7 +1314,7 @@ function RenderTiptap({
|
|
|
1252
1314
|
fields: fields ?? null,
|
|
1253
1315
|
section: createSectionContextWithBlogListNetworkFetch(apiKey, baseUrl)
|
|
1254
1316
|
};
|
|
1255
|
-
return /* @__PURE__ */ jsx13(
|
|
1317
|
+
return /* @__PURE__ */ jsx13(Fragment5, { children: contentNodes(renderDoc).map((value, index) => {
|
|
1256
1318
|
const node = parseTiptapNode(value);
|
|
1257
1319
|
if (node?.type !== "tableOfContents") {
|
|
1258
1320
|
return renderTiptapNode(value, index, context);
|
|
@@ -1270,7 +1332,7 @@ function RenderTiptap({
|
|
|
1270
1332
|
}
|
|
1271
1333
|
|
|
1272
1334
|
// src/server/blog-post-body.tsx
|
|
1273
|
-
import { Fragment as
|
|
1335
|
+
import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1274
1336
|
function BlogPostBody(props) {
|
|
1275
1337
|
const {
|
|
1276
1338
|
doc,
|
|
@@ -1279,10 +1341,17 @@ function BlogPostBody(props) {
|
|
|
1279
1341
|
baseUrl,
|
|
1280
1342
|
toc,
|
|
1281
1343
|
tocTitle,
|
|
1282
|
-
tocPosition
|
|
1344
|
+
tocPosition,
|
|
1345
|
+
bodyHtml
|
|
1283
1346
|
} = props;
|
|
1284
1347
|
const tableOfContents = toc.length > 0 ? /* @__PURE__ */ jsx14(RootTaleTableOfContents, { entries: toc, title: tocTitle }) : null;
|
|
1285
|
-
const
|
|
1348
|
+
const source = resolvePostBodySource(bodyHtml);
|
|
1349
|
+
const body = /* @__PURE__ */ jsx14("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: source.kind === "html" ? (
|
|
1350
|
+
// source.html 은 resolvePostBodySource 가 공용 sanitizeHtml 로 정제한 값이다
|
|
1351
|
+
// (이 컴포넌트에서 한 번 더 정제하지 않는 이유 — sink 가 한 곳이어야 눈으로
|
|
1352
|
+
// 추적 가능하고, 이중 정제는 안전을 더 만들지 않으면서 비용만 든다).
|
|
1353
|
+
/* @__PURE__ */ jsx14("div", { dangerouslySetInnerHTML: { __html: source.html } })
|
|
1354
|
+
) : /* @__PURE__ */ jsx14(
|
|
1286
1355
|
RenderTiptap,
|
|
1287
1356
|
{
|
|
1288
1357
|
doc,
|
|
@@ -1297,7 +1366,7 @@ function BlogPostBody(props) {
|
|
|
1297
1366
|
/* @__PURE__ */ jsx14("aside", { className: "rt-cms-toc-side", children: tableOfContents })
|
|
1298
1367
|
] });
|
|
1299
1368
|
}
|
|
1300
|
-
return /* @__PURE__ */ jsxs10(
|
|
1369
|
+
return /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
1301
1370
|
tableOfContents,
|
|
1302
1371
|
body
|
|
1303
1372
|
] });
|
|
@@ -1311,7 +1380,7 @@ import {
|
|
|
1311
1380
|
import {
|
|
1312
1381
|
resolvePostCollection as resolvePostCollection2
|
|
1313
1382
|
} from "@roottale/cms-core";
|
|
1314
|
-
import { Fragment as
|
|
1383
|
+
import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1315
1384
|
function absoluteUrl(siteUrl, path) {
|
|
1316
1385
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1317
1386
|
return `${trimSlash(siteUrl)}${normalizedPath}`;
|
|
@@ -1346,7 +1415,7 @@ function renderBlogPostBreadcrumb(input) {
|
|
|
1346
1415
|
url: absoluteUrl(siteUrl, crumb.href)
|
|
1347
1416
|
}))
|
|
1348
1417
|
) : null;
|
|
1349
|
-
return /* @__PURE__ */ jsxs11(
|
|
1418
|
+
return /* @__PURE__ */ jsxs11(Fragment7, { children: [
|
|
1350
1419
|
/* @__PURE__ */ jsx15("nav", { "aria-label": "\uBE0C\uB808\uB4DC\uD06C\uB7FC", className: "rt-cms-breadcrumb", children: /* @__PURE__ */ jsx15("ol", { children: crumbs.map(
|
|
1351
1420
|
(crumb, index) => index === crumbs.length - 1 ? /* @__PURE__ */ jsx15("li", { "aria-current": "page", children: crumb.label }, index) : /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsx15("a", { href: crumb.href, children: crumb.label }) }, index)
|
|
1352
1421
|
) }) }),
|
|
@@ -1426,7 +1495,7 @@ function RootTaleFaqJsonLd({
|
|
|
1426
1495
|
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1427
1496
|
async function fetchRelatedPostsFailSoft(input) {
|
|
1428
1497
|
if (input.count <= 0) return [];
|
|
1429
|
-
const candidates = await
|
|
1498
|
+
const candidates = await fetchPosts2({
|
|
1430
1499
|
apiKey: input.apiKey,
|
|
1431
1500
|
baseUrl: input.baseUrl,
|
|
1432
1501
|
type: "post",
|
|
@@ -1475,10 +1544,35 @@ function authorImageRadiusCss(radius) {
|
|
|
1475
1544
|
if (radius === "rounded") return "0.875rem";
|
|
1476
1545
|
return "0";
|
|
1477
1546
|
}
|
|
1547
|
+
async function lookupPost(input) {
|
|
1548
|
+
if (input.previewToken) {
|
|
1549
|
+
try {
|
|
1550
|
+
const preview = await fetchPostPreview({
|
|
1551
|
+
apiKey: input.apiKey,
|
|
1552
|
+
baseUrl: input.baseUrl,
|
|
1553
|
+
token: input.previewToken
|
|
1554
|
+
});
|
|
1555
|
+
return preview ? { kind: "post", post: preview } : { kind: "missing" };
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
if (isPreviewExpiredError(error)) return { kind: "preview-expired" };
|
|
1558
|
+
throw error;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (!input.slugOrId) return { kind: "missing" };
|
|
1562
|
+
const post = await fetchPost({
|
|
1563
|
+
apiKey: input.apiKey,
|
|
1564
|
+
slugOrId: input.slugOrId,
|
|
1565
|
+
baseUrl: input.baseUrl,
|
|
1566
|
+
...input.locale ? { locale: input.locale } : {}
|
|
1567
|
+
});
|
|
1568
|
+
return post ? { kind: "post", post } : { kind: "missing" };
|
|
1569
|
+
}
|
|
1478
1570
|
async function RootTaleBlogPost(props) {
|
|
1479
1571
|
const {
|
|
1480
1572
|
apiKey,
|
|
1481
1573
|
slugOrId,
|
|
1574
|
+
previewToken,
|
|
1575
|
+
previewExpiredElement,
|
|
1482
1576
|
baseUrl,
|
|
1483
1577
|
notFoundElement,
|
|
1484
1578
|
showTitle = true,
|
|
@@ -1493,14 +1587,18 @@ async function RootTaleBlogPost(props) {
|
|
|
1493
1587
|
authorHref = defaultAuthorHref,
|
|
1494
1588
|
breadcrumb
|
|
1495
1589
|
} = props;
|
|
1496
|
-
const [
|
|
1497
|
-
|
|
1590
|
+
const [lookup, themeStyle, blogSettings] = await Promise.all([
|
|
1591
|
+
lookupPost({ apiKey, baseUrl, slugOrId, previewToken, locale }),
|
|
1498
1592
|
resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),
|
|
1499
1593
|
fetchBlogSettingsFailSoft({ apiKey, baseUrl })
|
|
1500
1594
|
]);
|
|
1501
|
-
if (
|
|
1595
|
+
if (lookup.kind === "preview-expired") {
|
|
1596
|
+
return previewExpiredElement ?? /* @__PURE__ */ jsx17("div", { "data-roottale-cms": "post-preview-expired", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx17("p", { className: "rt-cms-empty", children: t(locale, "previewExpired") }) });
|
|
1597
|
+
}
|
|
1598
|
+
if (lookup.kind === "missing") {
|
|
1502
1599
|
return notFoundElement ?? /* @__PURE__ */ jsx17("div", { "data-roottale-cms": "post-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx17("p", { className: "rt-cms-empty", children: t(locale, "postNotFound") }) });
|
|
1503
1600
|
}
|
|
1601
|
+
const post = lookup.post;
|
|
1504
1602
|
const display = resolvePostDisplay2(post, blogSettings, {
|
|
1505
1603
|
showTableOfContents,
|
|
1506
1604
|
tableOfContentsTitle
|
|
@@ -1611,7 +1709,8 @@ async function RootTaleBlogPost(props) {
|
|
|
1611
1709
|
baseUrl,
|
|
1612
1710
|
toc,
|
|
1613
1711
|
tocTitle: display.tocTitle,
|
|
1614
|
-
tocPosition: display.tocPosition
|
|
1712
|
+
tocPosition: display.tocPosition,
|
|
1713
|
+
bodyHtml: post.bodyHtml
|
|
1615
1714
|
}
|
|
1616
1715
|
),
|
|
1617
1716
|
showAuthorCard && authorName ? /* @__PURE__ */ jsxs13("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
|
|
@@ -1639,9 +1738,32 @@ async function RootTaleBlogPost(props) {
|
|
|
1639
1738
|
);
|
|
1640
1739
|
}
|
|
1641
1740
|
|
|
1741
|
+
// src/server/preview-notice.tsx
|
|
1742
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1743
|
+
function formatExpiry(locale, iso) {
|
|
1744
|
+
const date = new Date(iso);
|
|
1745
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
1746
|
+
const time = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "ko-KR", {
|
|
1747
|
+
hour: "2-digit",
|
|
1748
|
+
minute: "2-digit"
|
|
1749
|
+
}).format(date);
|
|
1750
|
+
return locale === "en" ? `Valid until ${time}.` : `${time}\uAE4C\uC9C0 \uBCFC \uC218 \uC788\uC5B4\uC694.`;
|
|
1751
|
+
}
|
|
1752
|
+
function RootTalePreviewNotice({
|
|
1753
|
+
locale,
|
|
1754
|
+
message,
|
|
1755
|
+
expiresAt
|
|
1756
|
+
}) {
|
|
1757
|
+
const expiry = expiresAt ? formatExpiry(locale, expiresAt) : null;
|
|
1758
|
+
return /* @__PURE__ */ jsxs14("div", { "data-roottale-cms": "preview-notice", className: "rt-cms-preview-notice", role: "status", children: [
|
|
1759
|
+
/* @__PURE__ */ jsx18("span", { children: message ?? t(locale, "previewNotice") }),
|
|
1760
|
+
expiry ? /* @__PURE__ */ jsx18("span", { className: "rt-cms-preview-notice__expiry", children: expiry }) : null
|
|
1761
|
+
] });
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1642
1764
|
// src/server/page.tsx
|
|
1643
1765
|
import { fetchPost as fetchPost2 } from "@roottale/cms-client/server";
|
|
1644
|
-
import { jsx as
|
|
1766
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1645
1767
|
async function RootTalePage(props) {
|
|
1646
1768
|
const {
|
|
1647
1769
|
apiKey,
|
|
@@ -1650,27 +1772,30 @@ async function RootTalePage(props) {
|
|
|
1650
1772
|
notFoundElement,
|
|
1651
1773
|
showTitle = true,
|
|
1652
1774
|
theme: themeProp,
|
|
1653
|
-
locale
|
|
1775
|
+
locale,
|
|
1776
|
+
prepareBody
|
|
1654
1777
|
} = props;
|
|
1655
1778
|
const [post, themeStyle] = await Promise.all([
|
|
1656
1779
|
fetchPost2({ apiKey, slugOrId, baseUrl, ...locale ? { locale } : {} }),
|
|
1657
1780
|
resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl })
|
|
1658
1781
|
]);
|
|
1659
1782
|
if (!post) {
|
|
1660
|
-
return notFoundElement ?? /* @__PURE__ */
|
|
1783
|
+
return notFoundElement ?? /* @__PURE__ */ jsx19("div", { "data-roottale-cms": "page-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx19("p", { className: "rt-cms-empty", children: t(locale, "pageNotFound") }) });
|
|
1661
1784
|
}
|
|
1662
|
-
|
|
1785
|
+
const pageBodySource = resolvePostBodySource(post.bodyHtml);
|
|
1786
|
+
const bodyJson = pageBodySource.kind === "json" && prepareBody ? await prepareBody(post.bodyJson) : post.bodyJson;
|
|
1787
|
+
return /* @__PURE__ */ jsxs15(
|
|
1663
1788
|
"article",
|
|
1664
1789
|
{
|
|
1665
1790
|
"data-roottale-cms": "page",
|
|
1666
1791
|
className: "rt-cms-article",
|
|
1667
1792
|
style: themeStyle ?? void 0,
|
|
1668
1793
|
children: [
|
|
1669
|
-
showTitle ? /* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
1794
|
+
showTitle ? /* @__PURE__ */ jsx19("header", { className: "rt-cms-post-header", children: /* @__PURE__ */ jsx19("h1", { className: "rt-cms-title", children: post.title }) }) : null,
|
|
1795
|
+
/* @__PURE__ */ jsx19("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: pageBodySource.kind === "html" ? /* @__PURE__ */ jsx19("div", { dangerouslySetInnerHTML: { __html: pageBodySource.html } }) : /* @__PURE__ */ jsx19(
|
|
1671
1796
|
RenderTiptap,
|
|
1672
1797
|
{
|
|
1673
|
-
doc:
|
|
1798
|
+
doc: bodyJson,
|
|
1674
1799
|
fields: post.fields,
|
|
1675
1800
|
apiKey,
|
|
1676
1801
|
baseUrl
|
|
@@ -1680,16 +1805,34 @@ async function RootTalePage(props) {
|
|
|
1680
1805
|
}
|
|
1681
1806
|
);
|
|
1682
1807
|
}
|
|
1808
|
+
|
|
1809
|
+
// src/server/exposure-slot.tsx
|
|
1810
|
+
import { fetchExposure } from "@roottale/cms-client/server";
|
|
1811
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1812
|
+
async function RootTaleExposureSlot(props) {
|
|
1813
|
+
const campaign = await fetchExposure({
|
|
1814
|
+
apiKey: props.apiKey,
|
|
1815
|
+
baseUrl: props.baseUrl,
|
|
1816
|
+
siteId: props.siteId,
|
|
1817
|
+
slotKey: props.slotKey,
|
|
1818
|
+
path: props.path,
|
|
1819
|
+
revalidate: props.revalidate
|
|
1820
|
+
});
|
|
1821
|
+
if (!campaign || !props.allowedVariants.includes(campaign.content.variant)) return null;
|
|
1822
|
+
return /* @__PURE__ */ jsx20(RootTaleExposure, { campaign, allowedVariants: props.allowedVariants });
|
|
1823
|
+
}
|
|
1683
1824
|
export {
|
|
1684
1825
|
RenderTiptap,
|
|
1685
1826
|
RootTaleBlogCategories,
|
|
1686
1827
|
RootTaleBlogList,
|
|
1687
1828
|
RootTaleBlogPost,
|
|
1829
|
+
RootTaleExposureSlot,
|
|
1688
1830
|
RootTaleFaqJsonLd,
|
|
1689
1831
|
RootTaleFloatingCta,
|
|
1690
1832
|
RootTaleLeadForm,
|
|
1691
1833
|
RootTalePage,
|
|
1692
1834
|
RootTalePostCard,
|
|
1835
|
+
RootTalePreviewNotice,
|
|
1693
1836
|
RootTaleTableOfContents,
|
|
1694
1837
|
RootTaleThemeProvider,
|
|
1695
1838
|
attachHeadingIds,
|
|
@@ -1702,6 +1845,7 @@ export {
|
|
|
1702
1845
|
renderBlock,
|
|
1703
1846
|
renderBlocks,
|
|
1704
1847
|
renderBoundBlocks,
|
|
1848
|
+
resolvePostBodySource,
|
|
1705
1849
|
themeToCssVars2 as themeToCssVars
|
|
1706
1850
|
};
|
|
1707
1851
|
//# sourceMappingURL=server.js.map
|