@roottale/cms-renderer-next 0.56.0 → 0.58.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # @roottale/cms-renderer-next
2
2
 
3
+ ## 0.58.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dd82c92: 공통 블록 표시 형태(presentation) — 블록의 모양(본문처럼/카드·배경색·링크 버튼·버튼 색)을 어드민이
8
+ 정해 공개 API `presentation` 으로 내려준다. 사이트는 색·모양을 CSS 에 고정하지 않는다.
9
+ - `@roottale/cms-core`: `readSitePatternPresentation`·`sitePatternPresentationAttributes`·
10
+ `DEFAULT_SITE_PATTERN_PRESENTATION`.
11
+ - `@roottale/cms-client`: `CmsSitePattern.presentation`(구 서버는 기본값), `sitePatternPresentationAttributes`
12
+ (래퍼 data 속성 + `--rt-pattern-bg`·`--rt-pattern-btn-n` CSS 변수).
13
+ - `@roottale/cms-renderer-next`: `RootTaleBlogPost`·`RootTalePostPattern` 이 data 속성·변수를 얹고,
14
+ `cms-public.css` 가 카드/버튼 모양을 그 값으로만 그린다.
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [dd82c92]
19
+ - @roottale/cms-core@0.58.0
20
+ - @roottale/cms-client@0.58.0
21
+
22
+ ## 0.57.0
23
+
24
+ ### Minor Changes
25
+
26
+ - e222471: 본문 예약 내부 링크(`[[internal:키|문구]]`)를 공용 렌더러가 해석한다.
27
+ - `@roottale/cms-core`: 토큰 정규식·탐색(`findInternalContentLinkTokens`), 경로→키
28
+ (`internalContentKeyFromPath`), 발행 글 경로 색인(`internalContentPathIndex`, 옛 slug 포함),
29
+ 텍스트·정화된 HTML 안의 토큰을 링크로 바꾸는 `renderInternalContentLinksInText/InHtml`.
30
+ - `@roottale/cms-renderer-next`: `RenderTiptap`·`RootTaleBlogPost` 에 `internalLinks`
31
+ (Map·함수) 옵션. `RootTaleBlogPost` 는 기본 `"auto"` — 본문에 토큰이 있을 때만
32
+ `fetchInternalContentPathIndex` 로 발행 글 경로 색인을 만들어 링크로 렌더한다.
33
+ 대상이 없는 토큰은 문구만 남기고 `data-rt-internal-link-pending="키"` 를 단다.
34
+ `<code>`·`<pre>`·기존 링크 안은 건드리지 않는다.
35
+ - 문서: FRONT 가 토큰을 직접 해석하지 않아도 되는 경로를 안내한다.
36
+
37
+ - 92d0ad4: 공통 블록(Site Patterns) — 여러 글의 같은 자리(글 하단)에 붙는 재사용 본문.
38
+ - `@roottale/cms-client`: 글 응답 `patternSlots`(자리 key → 블록 key | null, 구 서버는 빈
39
+ 객체), `fetchSitePatterns`(발행 블록 목록, `GET /v1/cms/public/patterns`),
40
+ `selectSitePatternForSlot`, `SITE_PATTERNS_CACHE_TAG`(`SETTINGS_CACHE_TAGS` 에 포함 —
41
+ `theme.updated` 로 함께 갱신), `POST_FOOTER_PATTERN_SLOT`.
42
+ - `@roottale/cms-renderer-next`: `RootTaleBlogPost` 가 `patternSlots.post_footer` 블록을 본문
43
+ 바로 아래에 자동으로 그린다(목록 요청 실패는 블록만 생략). 자체 글 화면용
44
+ `RootTalePostPattern` 서버 컴포넌트와 `.rt-cms-post-pattern` 스타일 추가.
45
+ - `@roottale/cms-core`: 자리 선언·배치 규칙 정규화·`resolvePatternSlots` (플랫폼 서버·어드민과
46
+ 공유하는 순수 규칙).
47
+ - 문서: `theme-and-settings.md` "공통 블록", `api-reference.md` `GET /patterns`·`pattern_slots`.
48
+
49
+ ### Patch Changes
50
+
51
+ - Updated dependencies [e222471]
52
+ - Updated dependencies [92d0ad4]
53
+ - @roottale/cms-core@0.57.0
54
+ - @roottale/cms-client@0.57.0
55
+
3
56
  ## 0.56.0
4
57
 
5
58
  ### Minor Changes
@@ -980,6 +980,64 @@
980
980
  opacity: 0.92;
981
981
  }
982
982
 
983
+ /* 공통 블록(post_footer) — 본문 바로 아래, 본문과 같은 폭. 모양은 어드민 표시 형태
984
+ (data-pattern-layout / data-pattern-link-style / --rt-pattern-bg / --rt-pattern-btn-n)를 따른다. */
985
+ [data-roottale-cms] :where(.rt-cms-post-pattern) {
986
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
987
+ margin-inline: auto;
988
+ margin-block-start: var(--rt-spacing-6, 1.5rem);
989
+ }
990
+
991
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-layout="plain"]) {
992
+ padding-block-start: var(--rt-spacing-5, 1.25rem);
993
+ border-block-start: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
994
+ }
995
+
996
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-layout="card"]) {
997
+ padding: var(--rt-spacing-5, 1.25rem);
998
+ background: var(--rt-pattern-bg, var(--rt-color-muted, #f7f8f7));
999
+ border-radius: var(--rt-radius-lg, 0.75rem);
1000
+ }
1001
+
1002
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-layout="card"] > :first-child) {
1003
+ margin-block-start: 0;
1004
+ }
1005
+
1006
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child:has(> a)) {
1007
+ display: flex;
1008
+ flex-wrap: wrap;
1009
+ gap: var(--rt-spacing-2, 0.5rem);
1010
+ margin-block-start: var(--rt-spacing-4, 1rem);
1011
+ }
1012
+
1013
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child > a) {
1014
+ display: inline-flex;
1015
+ align-items: center;
1016
+ padding: 0.625rem 1.25rem;
1017
+ background: var(--rt-pattern-btn-1, #03c75a);
1018
+ color: #fff;
1019
+ font-weight: 600;
1020
+ text-decoration: none;
1021
+ border-radius: var(--rt-radius-sm, 0.25rem);
1022
+ white-space: nowrap;
1023
+ }
1024
+
1025
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child > a:nth-of-type(2)) {
1026
+ background: var(--rt-pattern-btn-2, var(--rt-pattern-btn-1));
1027
+ }
1028
+
1029
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child > a:nth-of-type(3)) {
1030
+ background: var(--rt-pattern-btn-3, var(--rt-pattern-btn-2, var(--rt-pattern-btn-1)));
1031
+ }
1032
+
1033
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child > a:nth-of-type(n + 4)) {
1034
+ background: var(--rt-pattern-btn-4, var(--rt-pattern-btn-3, var(--rt-pattern-btn-2, var(--rt-pattern-btn-1))));
1035
+ }
1036
+
1037
+ [data-roottale-cms] :where(.rt-cms-post-pattern[data-pattern-link-style="buttons"] > p:last-child > br) {
1038
+ display: none;
1039
+ }
1040
+
983
1041
  /* FAQ 블록 (roottale/faq) — <details> 아코디언. 0 JS. */
984
1042
  [data-roottale-cms] :where(.rt-cms-faq) {
985
1043
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
package/dist/routes.js CHANGED
@@ -925,7 +925,7 @@ function createFleetInfoRoute(config = {}) {
925
925
  site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
926
926
  framework: "next",
927
927
  cms_client: CMS_CLIENT_VERSION,
928
- cms_renderer: true ? "0.56.0" : "dev",
928
+ cms_renderer: true ? "0.58.0" : "dev",
929
929
  build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
930
930
  },
931
931
  { headers: { "cache-control": "public, max-age=300" } }
package/dist/server.d.ts CHANGED
@@ -2,7 +2,7 @@ import { ReactElement, ReactNode } from 'react';
2
2
  import { CmsPostContent, RootTaleTheme, CmsPostType } from '@roottale/cms-client/server';
3
3
  export { RootTaleTheme } from '@roottale/cms-client/server';
4
4
  import { RootTaleThemeStyle } from '@roottale/ui-react/theme';
5
- import { Block, BindingResolver, RouteCollection } from '@roottale/cms-core';
5
+ import { Block, BindingResolver, RouteCollection, InternalContentLinkResolver } from '@roottale/cms-core';
6
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
 
@@ -262,6 +262,13 @@ type RootTaleBlogPostProps = {
262
262
  locale?: string;
263
263
  authorHref?: (slug: string) => string;
264
264
  breadcrumb?: BlogPostBreadcrumbOptions;
265
+ /**
266
+ * 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석. 기본 `"auto"` = 본문에 토큰이
267
+ * 있을 때만 발행 글 경로 색인(`fetchInternalContentPathIndex`)을 만들어 링크로
268
+ * 렌더한다. `false` 면 토큰을 텍스트 그대로 둔다. 미리 만든 색인(Map)이나
269
+ * 함수를 주면 그것을 쓴다.
270
+ */
271
+ internalLinks?: "auto" | false | InternalContentLinkResolver | ReadonlyMap<string, string>;
265
272
  };
266
273
  interface BlogPostBreadcrumbOptions {
267
274
  siteUrl?: string;
@@ -279,6 +286,21 @@ declare function RootTaleFaqJsonLd({ doc, }: {
279
286
  readonly doc: unknown;
280
287
  }): ReactElement | null;
281
288
 
289
+ interface RootTalePostPatternProps {
290
+ readonly apiKey: string;
291
+ readonly baseUrl?: string;
292
+ /** `fetchPost`/`fetchPosts` 로 받은 글(또는 `patternSlots` 만 있는 객체). */
293
+ readonly post: Pick<CmsPostContent, "patternSlots">;
294
+ /** 자리 key. 기본 글 하단(`post_footer`). */
295
+ readonly slot?: string;
296
+ }
297
+ /**
298
+ * 자체 글 화면을 가진 사이트가 글 하단 공통 블록만 가져다 쓰는 서버 컴포넌트.
299
+ * `RootTaleBlogPost` 는 이미 본문 아래에 같은 블록을 그리므로 둘을 함께 쓰지 않는다.
300
+ */
301
+ declare function RootTalePostPattern(props: RootTalePatternProps): Promise<ReactElement | null>;
302
+ type RootTalePatternProps = RootTalePostPatternProps;
303
+
282
304
  interface RootTalePreviewNoticeProps {
283
305
  /** `ko`(기본) · `en`. */
284
306
  locale?: string;
@@ -319,13 +341,36 @@ interface RootTaleExposureSlotProps {
319
341
  /** FRONT가 코드로 만든 슬롯 안에서만 활성 배너·팝업을 렌더한다. */
320
342
  declare function RootTaleExposureSlot(props: RootTaleExposureSlotProps): Promise<react_jsx_runtime.JSX.Element | null>;
321
343
 
344
+ type InternalLinksSource = InternalContentLinkResolver | ReadonlyMap<string, string> | null;
322
345
  type RenderTiptapProps = {
323
346
  readonly doc: Record<string, unknown>;
324
347
  readonly fields?: Record<string, unknown> | null;
325
348
  readonly apiKey?: string;
326
349
  readonly baseUrl?: string;
350
+ /**
351
+ * 본문 예약 내부 링크(`[[internal:키|문구]]`) 해석 — 키 → 공개 경로.
352
+ * `fetchInternalContentPathIndex` 결과(Map)나 함수. 없으면 토큰을 텍스트 그대로 둔다.
353
+ */
354
+ readonly internalLinks?: InternalLinksSource;
327
355
  };
328
- declare function RenderTiptap({ doc, fields, apiKey, baseUrl, }: RenderTiptapProps): ReactElement;
356
+ declare function RenderTiptap({ doc, fields, apiKey, baseUrl, internalLinks, }: RenderTiptapProps): ReactElement;
357
+
358
+ interface FetchInternalContentPathIndexOptions {
359
+ readonly apiKey: string;
360
+ readonly baseUrl?: string;
361
+ readonly locale?: string;
362
+ /** ISR revalidate 초. */
363
+ readonly revalidate?: number;
364
+ /** 안전장치 — 이 이상 페이지는 읽지 않는다(100건/페이지). 기본 20 = 2,000건. */
365
+ readonly maxPages?: number;
366
+ }
367
+ /**
368
+ * 발행 글 전부의 경로를 모아 키 색인을 만든다. 한 페이지라도 실패하면 그때까지
369
+ * 모은 것으로 답한다(fail-soft — 링크가 몇 개 문구로 남는 편이 500 보다 낫다).
370
+ */
371
+ declare function fetchInternalContentPathIndex(options: FetchInternalContentPathIndexOptions): Promise<Map<string, string>>;
372
+ /** 본문(JSON 또는 HTML)에 토큰이 있는지 — 색인을 만들 필요가 있는지 미리 본다. */
373
+ declare function bodyHasInternalContentLinkTokens(doc: Record<string, unknown> | null | undefined, html?: string | null): boolean;
329
374
 
330
375
  /**
331
376
  * 본문 렌더 소스 결정 — HTML 정본 우선, 없으면 Block JSON 폴백.
@@ -356,4 +401,4 @@ type PostBodySource = {
356
401
  */
357
402
  declare function resolvePostBodySource(bodyHtml: string | null | undefined): PostBodySource;
358
403
 
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 };
404
+ export { type BlogPostBreadcrumbOptions, type CtaButton, type CtaButtonType, type FetchInternalContentPathIndexOptions, type FloatingCtaPosition, type InternalLinksSource, 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, RootTalePostPattern, type RootTalePostPatternProps, RootTalePreviewNotice, type RootTalePreviewNoticeProps, RootTaleTableOfContents, type RootTaleTableOfContentsProps, type RootTaleThemeInput, RootTaleThemeProvider, type TocEntry, attachHeadingIds, bodyHasInternalContentLinkTokens, extractToc, fetchInternalContentPathIndex, headingToId, renderBlock, renderBlocks, renderBoundBlocks, resolvePostBodySource, themeToCssVars };