@roottale/cms-renderer-next 0.53.1 → 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/dist/server.d.ts CHANGED
@@ -3,7 +3,7 @@ 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-BKCGM0qd.js';
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
7
  import * as react_jsx_runtime from 'react/jsx-runtime';
8
8
 
9
9
  /**
@@ -238,7 +238,16 @@ declare function RootTaleBlogCategories(props: RootTaleBlogCategoriesProps): Pro
238
238
 
239
239
  type RootTaleBlogPostProps = {
240
240
  apiKey: string;
241
- slugOrId: string;
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;
242
251
  baseUrl?: string;
243
252
  notFoundElement?: ReactElement;
244
253
  showTitle?: boolean;
@@ -270,6 +279,21 @@ declare function RootTaleFaqJsonLd({ doc, }: {
270
279
  readonly doc: unknown;
271
280
  }): ReactElement | null;
272
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
+
273
297
  type RootTalePageProps = {
274
298
  apiKey: string;
275
299
  slugOrId: string;
@@ -332,4 +356,4 @@ type PostBodySource = {
332
356
  */
333
357
  declare function resolvePostBodySource(bodyHtml: string | null | undefined): PostBodySource;
334
358
 
335
- 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, RootTaleTableOfContents, type RootTaleTableOfContentsProps, type RootTaleThemeInput, RootTaleThemeProvider, type TocEntry, attachHeadingIds, extractToc, headingToId, renderBlock, renderBlocks, renderBoundBlocks, resolvePostBodySource, themeToCssVars };
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,7 +5,7 @@ import {
5
5
  defaultCategoryHref,
6
6
  filterPostsByCategory,
7
7
  trimSlash
8
- } from "./chunk-HM5VP7SR.js";
8
+ } from "./chunk-FJANIAZN.js";
9
9
  import {
10
10
  getSectionNodeRenderer
11
11
  } from "./chunk-KSPPYX22.js";
@@ -84,6 +84,8 @@ var DICTS = {
84
84
  relatedPostsTitle: "\uAD00\uB828 \uAE00",
85
85
  postNotFound: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
86
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.",
87
89
  tableOfContentsTitle: "\uBAA9\uCC28"
88
90
  },
89
91
  en: {
@@ -93,6 +95,8 @@ var DICTS = {
93
95
  relatedPostsTitle: "Related posts",
94
96
  postNotFound: "Post not found.",
95
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.",
96
100
  tableOfContentsTitle: "Table of contents"
97
101
  }
98
102
  };
@@ -850,7 +854,9 @@ async function RootTaleBlogCategories(props) {
850
854
  // src/server/blog-post.tsx
851
855
  import {
852
856
  fetchPost,
857
+ fetchPostPreview,
853
858
  fetchPosts as fetchPosts2,
859
+ isPreviewExpiredError,
854
860
  resolvePostDisplay as resolvePostDisplay2,
855
861
  selectRelatedPosts
856
862
  } from "@roottale/cms-client/server";
@@ -1538,10 +1544,35 @@ function authorImageRadiusCss(radius) {
1538
1544
  if (radius === "rounded") return "0.875rem";
1539
1545
  return "0";
1540
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
+ }
1541
1570
  async function RootTaleBlogPost(props) {
1542
1571
  const {
1543
1572
  apiKey,
1544
1573
  slugOrId,
1574
+ previewToken,
1575
+ previewExpiredElement,
1545
1576
  baseUrl,
1546
1577
  notFoundElement,
1547
1578
  showTitle = true,
@@ -1556,14 +1587,18 @@ async function RootTaleBlogPost(props) {
1556
1587
  authorHref = defaultAuthorHref,
1557
1588
  breadcrumb
1558
1589
  } = props;
1559
- const [post, themeStyle, blogSettings] = await Promise.all([
1560
- fetchPost({ apiKey, slugOrId, baseUrl, ...locale ? { locale } : {} }),
1590
+ const [lookup, themeStyle, blogSettings] = await Promise.all([
1591
+ lookupPost({ apiKey, baseUrl, slugOrId, previewToken, locale }),
1561
1592
  resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),
1562
1593
  fetchBlogSettingsFailSoft({ apiKey, baseUrl })
1563
1594
  ]);
1564
- if (!post) {
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") {
1565
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") }) });
1566
1600
  }
1601
+ const post = lookup.post;
1567
1602
  const display = resolvePostDisplay2(post, blogSettings, {
1568
1603
  showTableOfContents,
1569
1604
  tableOfContentsTitle
@@ -1703,9 +1738,32 @@ async function RootTaleBlogPost(props) {
1703
1738
  );
1704
1739
  }
1705
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
+
1706
1764
  // src/server/page.tsx
1707
1765
  import { fetchPost as fetchPost2 } from "@roottale/cms-client/server";
1708
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1766
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1709
1767
  async function RootTalePage(props) {
1710
1768
  const {
1711
1769
  apiKey,
@@ -1722,19 +1780,19 @@ async function RootTalePage(props) {
1722
1780
  resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl })
1723
1781
  ]);
1724
1782
  if (!post) {
1725
- return notFoundElement ?? /* @__PURE__ */ jsx18("div", { "data-roottale-cms": "page-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx18("p", { className: "rt-cms-empty", children: t(locale, "pageNotFound") }) });
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") }) });
1726
1784
  }
1727
1785
  const pageBodySource = resolvePostBodySource(post.bodyHtml);
1728
1786
  const bodyJson = pageBodySource.kind === "json" && prepareBody ? await prepareBody(post.bodyJson) : post.bodyJson;
1729
- return /* @__PURE__ */ jsxs14(
1787
+ return /* @__PURE__ */ jsxs15(
1730
1788
  "article",
1731
1789
  {
1732
1790
  "data-roottale-cms": "page",
1733
1791
  className: "rt-cms-article",
1734
1792
  style: themeStyle ?? void 0,
1735
1793
  children: [
1736
- showTitle ? /* @__PURE__ */ jsx18("header", { className: "rt-cms-post-header", children: /* @__PURE__ */ jsx18("h1", { className: "rt-cms-title", children: post.title }) }) : null,
1737
- /* @__PURE__ */ jsx18("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: pageBodySource.kind === "html" ? /* @__PURE__ */ jsx18("div", { dangerouslySetInnerHTML: { __html: pageBodySource.html } }) : /* @__PURE__ */ jsx18(
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(
1738
1796
  RenderTiptap,
1739
1797
  {
1740
1798
  doc: bodyJson,
@@ -1750,7 +1808,7 @@ async function RootTalePage(props) {
1750
1808
 
1751
1809
  // src/server/exposure-slot.tsx
1752
1810
  import { fetchExposure } from "@roottale/cms-client/server";
1753
- import { jsx as jsx19 } from "react/jsx-runtime";
1811
+ import { jsx as jsx20 } from "react/jsx-runtime";
1754
1812
  async function RootTaleExposureSlot(props) {
1755
1813
  const campaign = await fetchExposure({
1756
1814
  apiKey: props.apiKey,
@@ -1761,7 +1819,7 @@ async function RootTaleExposureSlot(props) {
1761
1819
  revalidate: props.revalidate
1762
1820
  });
1763
1821
  if (!campaign || !props.allowedVariants.includes(campaign.content.variant)) return null;
1764
- return /* @__PURE__ */ jsx19(RootTaleExposure, { campaign, allowedVariants: props.allowedVariants });
1822
+ return /* @__PURE__ */ jsx20(RootTaleExposure, { campaign, allowedVariants: props.allowedVariants });
1765
1823
  }
1766
1824
  export {
1767
1825
  RenderTiptap,
@@ -1774,6 +1832,7 @@ export {
1774
1832
  RootTaleLeadForm,
1775
1833
  RootTalePage,
1776
1834
  RootTalePostCard,
1835
+ RootTalePreviewNotice,
1777
1836
  RootTaleTableOfContents,
1778
1837
  RootTaleThemeProvider,
1779
1838
  attachHeadingIds,