@roottale/cms-renderer-next 0.53.1 → 0.56.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, m as makeCategoryHref } from './blog-filter-qE9Lj49C.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
@@ -4,8 +4,10 @@ import {
4
4
  collectCollectionCategories,
5
5
  defaultCategoryHref,
6
6
  filterPostsByCategory,
7
+ makeCategoryHref,
8
+ storedPostPath,
7
9
  trimSlash
8
- } from "./chunk-HM5VP7SR.js";
10
+ } from "./chunk-GDP2VGJR.js";
9
11
  import {
10
12
  getSectionNodeRenderer
11
13
  } from "./chunk-KSPPYX22.js";
@@ -84,6 +86,8 @@ var DICTS = {
84
86
  relatedPostsTitle: "\uAD00\uB828 \uAE00",
85
87
  postNotFound: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
86
88
  pageNotFound: "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
89
+ 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.",
90
+ 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
91
  tableOfContentsTitle: "\uBAA9\uCC28"
88
92
  },
89
93
  en: {
@@ -93,6 +97,8 @@ var DICTS = {
93
97
  relatedPostsTitle: "Related posts",
94
98
  postNotFound: "Post not found.",
95
99
  pageNotFound: "Page not found.",
100
+ previewExpired: "This preview link has expired. Open the preview again from the admin editor.",
101
+ previewNotice: "Preview \u2014 this content is not public and is hidden from search engines.",
96
102
  tableOfContentsTitle: "Table of contents"
97
103
  }
98
104
  };
@@ -660,20 +666,22 @@ import {
660
666
  resolvePostPath
661
667
  } from "@roottale/cms-core";
662
668
  function defaultPostHref(post) {
663
- return `/blog/${post.slug}`;
669
+ return storedPostPath(post) ?? `/blog/${post.slug}`;
664
670
  }
665
671
  function defaultAuthorHref(slug) {
666
672
  return `/blog/author/${slug}`;
667
673
  }
668
674
  function makeCollectionPostHref(collections) {
669
675
  return (post) => {
676
+ const stored = storedPostPath(post);
677
+ if (stored) return stored;
670
678
  const path = resolvePostPath(post, collections);
671
679
  if (path) return path;
672
680
  const collection = resolvePostCollection(post, collections);
673
681
  if (collection?.detail === false && collection.basePath) {
674
682
  return collection.basePath;
675
683
  }
676
- return defaultPostHref(post);
684
+ return `/blog/${post.slug}`;
677
685
  };
678
686
  }
679
687
 
@@ -850,7 +858,9 @@ async function RootTaleBlogCategories(props) {
850
858
  // src/server/blog-post.tsx
851
859
  import {
852
860
  fetchPost,
861
+ fetchPostPreview,
853
862
  fetchPosts as fetchPosts2,
863
+ isPreviewExpiredError,
854
864
  resolvePostDisplay as resolvePostDisplay2,
855
865
  selectRelatedPosts
856
866
  } from "@roottale/cms-client/server";
@@ -1538,10 +1548,35 @@ function authorImageRadiusCss(radius) {
1538
1548
  if (radius === "rounded") return "0.875rem";
1539
1549
  return "0";
1540
1550
  }
1551
+ async function lookupPost(input) {
1552
+ if (input.previewToken) {
1553
+ try {
1554
+ const preview = await fetchPostPreview({
1555
+ apiKey: input.apiKey,
1556
+ baseUrl: input.baseUrl,
1557
+ token: input.previewToken
1558
+ });
1559
+ return preview ? { kind: "post", post: preview } : { kind: "missing" };
1560
+ } catch (error) {
1561
+ if (isPreviewExpiredError(error)) return { kind: "preview-expired" };
1562
+ throw error;
1563
+ }
1564
+ }
1565
+ if (!input.slugOrId) return { kind: "missing" };
1566
+ const post = await fetchPost({
1567
+ apiKey: input.apiKey,
1568
+ slugOrId: input.slugOrId,
1569
+ baseUrl: input.baseUrl,
1570
+ ...input.locale ? { locale: input.locale } : {}
1571
+ });
1572
+ return post ? { kind: "post", post } : { kind: "missing" };
1573
+ }
1541
1574
  async function RootTaleBlogPost(props) {
1542
1575
  const {
1543
1576
  apiKey,
1544
1577
  slugOrId,
1578
+ previewToken,
1579
+ previewExpiredElement,
1545
1580
  baseUrl,
1546
1581
  notFoundElement,
1547
1582
  showTitle = true,
@@ -1556,14 +1591,18 @@ async function RootTaleBlogPost(props) {
1556
1591
  authorHref = defaultAuthorHref,
1557
1592
  breadcrumb
1558
1593
  } = props;
1559
- const [post, themeStyle, blogSettings] = await Promise.all([
1560
- fetchPost({ apiKey, slugOrId, baseUrl, ...locale ? { locale } : {} }),
1594
+ const [lookup, themeStyle, blogSettings] = await Promise.all([
1595
+ lookupPost({ apiKey, baseUrl, slugOrId, previewToken, locale }),
1561
1596
  resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl }),
1562
1597
  fetchBlogSettingsFailSoft({ apiKey, baseUrl })
1563
1598
  ]);
1564
- if (!post) {
1599
+ if (lookup.kind === "preview-expired") {
1600
+ 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") }) });
1601
+ }
1602
+ if (lookup.kind === "missing") {
1565
1603
  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
1604
  }
1605
+ const post = lookup.post;
1567
1606
  const display = resolvePostDisplay2(post, blogSettings, {
1568
1607
  showTableOfContents,
1569
1608
  tableOfContentsTitle
@@ -1703,9 +1742,32 @@ async function RootTaleBlogPost(props) {
1703
1742
  );
1704
1743
  }
1705
1744
 
1745
+ // src/server/preview-notice.tsx
1746
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1747
+ function formatExpiry(locale, iso) {
1748
+ const date = new Date(iso);
1749
+ if (Number.isNaN(date.getTime())) return null;
1750
+ const time = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "ko-KR", {
1751
+ hour: "2-digit",
1752
+ minute: "2-digit"
1753
+ }).format(date);
1754
+ return locale === "en" ? `Valid until ${time}.` : `${time}\uAE4C\uC9C0 \uBCFC \uC218 \uC788\uC5B4\uC694.`;
1755
+ }
1756
+ function RootTalePreviewNotice({
1757
+ locale,
1758
+ message,
1759
+ expiresAt
1760
+ }) {
1761
+ const expiry = expiresAt ? formatExpiry(locale, expiresAt) : null;
1762
+ return /* @__PURE__ */ jsxs14("div", { "data-roottale-cms": "preview-notice", className: "rt-cms-preview-notice", role: "status", children: [
1763
+ /* @__PURE__ */ jsx18("span", { children: message ?? t(locale, "previewNotice") }),
1764
+ expiry ? /* @__PURE__ */ jsx18("span", { className: "rt-cms-preview-notice__expiry", children: expiry }) : null
1765
+ ] });
1766
+ }
1767
+
1706
1768
  // src/server/page.tsx
1707
1769
  import { fetchPost as fetchPost2 } from "@roottale/cms-client/server";
1708
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1770
+ import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1709
1771
  async function RootTalePage(props) {
1710
1772
  const {
1711
1773
  apiKey,
@@ -1722,19 +1784,19 @@ async function RootTalePage(props) {
1722
1784
  resolveThemeStyleFailSoft({ themeProp, apiKey, baseUrl })
1723
1785
  ]);
1724
1786
  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") }) });
1787
+ 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
1788
  }
1727
1789
  const pageBodySource = resolvePostBodySource(post.bodyHtml);
1728
1790
  const bodyJson = pageBodySource.kind === "json" && prepareBody ? await prepareBody(post.bodyJson) : post.bodyJson;
1729
- return /* @__PURE__ */ jsxs14(
1791
+ return /* @__PURE__ */ jsxs15(
1730
1792
  "article",
1731
1793
  {
1732
1794
  "data-roottale-cms": "page",
1733
1795
  className: "rt-cms-article",
1734
1796
  style: themeStyle ?? void 0,
1735
1797
  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(
1798
+ showTitle ? /* @__PURE__ */ jsx19("header", { className: "rt-cms-post-header", children: /* @__PURE__ */ jsx19("h1", { className: "rt-cms-title", children: post.title }) }) : null,
1799
+ /* @__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
1800
  RenderTiptap,
1739
1801
  {
1740
1802
  doc: bodyJson,
@@ -1750,7 +1812,7 @@ async function RootTalePage(props) {
1750
1812
 
1751
1813
  // src/server/exposure-slot.tsx
1752
1814
  import { fetchExposure } from "@roottale/cms-client/server";
1753
- import { jsx as jsx19 } from "react/jsx-runtime";
1815
+ import { jsx as jsx20 } from "react/jsx-runtime";
1754
1816
  async function RootTaleExposureSlot(props) {
1755
1817
  const campaign = await fetchExposure({
1756
1818
  apiKey: props.apiKey,
@@ -1761,7 +1823,7 @@ async function RootTaleExposureSlot(props) {
1761
1823
  revalidate: props.revalidate
1762
1824
  });
1763
1825
  if (!campaign || !props.allowedVariants.includes(campaign.content.variant)) return null;
1764
- return /* @__PURE__ */ jsx19(RootTaleExposure, { campaign, allowedVariants: props.allowedVariants });
1826
+ return /* @__PURE__ */ jsx20(RootTaleExposure, { campaign, allowedVariants: props.allowedVariants });
1765
1827
  }
1766
1828
  export {
1767
1829
  RenderTiptap,
@@ -1774,6 +1836,7 @@ export {
1774
1836
  RootTaleLeadForm,
1775
1837
  RootTalePage,
1776
1838
  RootTalePostCard,
1839
+ RootTalePreviewNotice,
1777
1840
  RootTaleTableOfContents,
1778
1841
  RootTaleThemeProvider,
1779
1842
  attachHeadingIds,
@@ -1783,6 +1846,7 @@ export {
1783
1846
  extractToc,
1784
1847
  filterPostsByCategory,
1785
1848
  headingToId,
1849
+ makeCategoryHref,
1786
1850
  renderBlock,
1787
1851
  renderBlocks,
1788
1852
  renderBoundBlocks,