@roottale/cms-renderer-next 0.41.1 → 0.42.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 +54 -0
- package/dist/before-after-client.d.ts +15 -0
- package/dist/before-after-client.js +8 -0
- package/dist/before-after-client.js.map +1 -0
- package/dist/chunk-4LQPZDXZ.js +75 -0
- package/dist/chunk-4LQPZDXZ.js.map +1 -0
- package/dist/chunk-6GQCFFHU.js +540 -0
- package/dist/chunk-6GQCFFHU.js.map +1 -0
- package/dist/chunk-F3G6RVIR.js +220 -0
- package/dist/chunk-F3G6RVIR.js.map +1 -0
- package/dist/chunk-H46JUIHZ.js +79 -0
- package/dist/chunk-H46JUIHZ.js.map +1 -0
- package/dist/chunk-K62RR4UQ.js +53 -0
- package/dist/chunk-K62RR4UQ.js.map +1 -0
- package/dist/chunk-NUUTTSGT.js +916 -0
- package/dist/chunk-NUUTTSGT.js.map +1 -0
- package/dist/chunk-RSNUZ4BF.js +3378 -0
- package/dist/chunk-RSNUZ4BF.js.map +1 -0
- package/dist/chunk-U5AKACPN.js +113 -0
- package/dist/chunk-U5AKACPN.js.map +1 -0
- package/dist/chunk-WWS43XCT.js +206 -0
- package/dist/chunk-WWS43XCT.js.map +1 -0
- package/dist/cms-public.css +164 -51
- package/dist/concerns-client.d.ts +12 -0
- package/dist/concerns-client.js +8 -0
- package/dist/concerns-client.js.map +1 -0
- package/dist/expertise-client.d.ts +10 -0
- package/dist/expertise-client.js +8 -0
- package/dist/expertise-client.js.map +1 -0
- package/dist/hero-carousel-client.d.ts +36 -0
- package/dist/hero-carousel-client.js +8 -0
- package/dist/hero-carousel-client.js.map +1 -0
- package/dist/routes.d.ts +51 -5
- package/dist/routes.js +109 -62
- package/dist/routes.js.map +1 -1
- package/dist/seo-checks.d.ts +87 -0
- package/dist/seo-checks.js +27 -0
- package/dist/seo-checks.js.map +1 -0
- package/dist/server.d.ts +51 -2
- package/dist/server.js +8 -2
- package/dist/site-config-DgYGGwu1.d.ts +3910 -0
- package/dist/spaces-client.d.ts +10 -0
- package/dist/spaces-client.js +8 -0
- package/dist/spaces-client.js.map +1 -0
- package/dist/templates.css +3246 -194
- package/dist/templates.d.ts +20 -1028
- package/dist/templates.js +70 -2
- package/dist/templates.js.map +1 -1
- package/dist/{theme-css-vars-BiyHBsJG.d.ts → theme-css-vars-CD3jcEkp.d.ts} +2 -2
- package/dist/ui-react-motion-client.d.ts +73 -0
- package/dist/ui-react-motion-client.js +14 -0
- package/dist/ui-react-motion-client.js.map +1 -0
- package/dist/validate.d.ts +15 -1
- package/dist/validate.js +806 -17
- package/dist/validate.js.map +1 -1
- package/package.json +18 -6
- package/dist/chunk-GOHP7RWD.js +0 -1647
- package/dist/chunk-GOHP7RWD.js.map +0 -1
- package/dist/chunk-IOYBEH6E.js +0 -271
- package/dist/chunk-IOYBEH6E.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
type SeoCheckLevel = "good" | "warn" | "bad";
|
|
2
|
+
interface SeoCheckResult {
|
|
3
|
+
id: string;
|
|
4
|
+
level: SeoCheckLevel;
|
|
5
|
+
label: string;
|
|
6
|
+
hint: string;
|
|
7
|
+
}
|
|
8
|
+
interface SeoCheckInput {
|
|
9
|
+
title: string;
|
|
10
|
+
seoTitle: string;
|
|
11
|
+
seoDescription: string;
|
|
12
|
+
excerpt: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
/** Tiptap doc JSON (PostsEditor 의 editor.getJSON() 형태). */
|
|
15
|
+
bodyJson: TiptapNode | null | undefined;
|
|
16
|
+
hasFeaturedImage: boolean;
|
|
17
|
+
/** SEO 패널의 canonical URL — noindex 와 겸용 충돌 점검용 (선택). */
|
|
18
|
+
canonical?: string;
|
|
19
|
+
/** SEO 패널의 noindex 설정 (선택). */
|
|
20
|
+
noindex?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface TiptapMark {
|
|
23
|
+
type?: string;
|
|
24
|
+
attrs?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
interface TiptapNode {
|
|
27
|
+
type?: string;
|
|
28
|
+
text?: string;
|
|
29
|
+
attrs?: Record<string, unknown>;
|
|
30
|
+
content?: TiptapNode[];
|
|
31
|
+
marks?: TiptapMark[];
|
|
32
|
+
}
|
|
33
|
+
/** depth-first 로 모든 노드를 방문한다 (루트 포함). */
|
|
34
|
+
declare function walkNodes(node: TiptapNode | null | undefined, visit: (node: TiptapNode) => void): void;
|
|
35
|
+
/** 조건에 맞는 노드를 문서 순서대로 수집한다. */
|
|
36
|
+
declare function collectNodes(node: TiptapNode | null | undefined, predicate: (node: TiptapNode) => boolean): TiptapNode[];
|
|
37
|
+
/** 본문 텍스트 추출 — 블록 경계는 개행으로 구분. */
|
|
38
|
+
declare function extractText(node: TiptapNode | null | undefined): string;
|
|
39
|
+
/** 공백 제외 글자 수 — "본문 분량" 측정 기준. */
|
|
40
|
+
declare function countBodyChars(node: TiptapNode | null | undefined): number;
|
|
41
|
+
/**
|
|
42
|
+
* 사이트별 bounded 임계값 (ADR-0061 §5 content kind). seo-checks 는 KnowledgeKind 를
|
|
43
|
+
* 모르고(§4②) 평범한 숫자만 받는다 — content→thresholds 해석은 호출자(페이지)가.
|
|
44
|
+
* 필드명은 site-knowledge ContentPolicy 와 동일(구조적으로 그대로 전달 가능).
|
|
45
|
+
*/
|
|
46
|
+
interface SeoThresholds {
|
|
47
|
+
titleMaxLen: number;
|
|
48
|
+
descMaxLen: number;
|
|
49
|
+
bodyMinLen: number;
|
|
50
|
+
}
|
|
51
|
+
/** 기본값 = 한국어 SERP 기준 현행 상수. 미전달 시 동작 불변(하위호환). */
|
|
52
|
+
declare const DEFAULT_SEO_THRESHOLDS: SeoThresholds;
|
|
53
|
+
interface BusinessProfileCompleteness {
|
|
54
|
+
hasAddress: boolean;
|
|
55
|
+
hasTelephone: boolean;
|
|
56
|
+
hasOpeningHours: boolean;
|
|
57
|
+
hasGeo: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 비즈니스 프로필 완성도 체크 — `profile` 미전달(사이트 없음 등)이면 결과 자체를
|
|
61
|
+
* 내지 않는다(체크리스트 노이즈 방지, `checkNoindexCanonicalConflict` 와 동일 패턴).
|
|
62
|
+
*/
|
|
63
|
+
declare function checkBusinessProfile(profile: BusinessProfileCompleteness | undefined): SeoCheckResult | null;
|
|
64
|
+
declare function runSeoChecks(input: SeoCheckInput, thresholds?: SeoThresholds): SeoCheckResult[];
|
|
65
|
+
declare const SEO_SCORE_WEIGHTS: Readonly<Record<string, number>>;
|
|
66
|
+
/**
|
|
67
|
+
* 가중합 0~100 점. 가중치가 정의된 id 만 합산(lexicon 등 확장 항목은 자동 제외).
|
|
68
|
+
* 결과 배열에 가중치 있는 항목이 하나도 없으면 0(평가 불가 상태를 0으로 fail-closed).
|
|
69
|
+
*/
|
|
70
|
+
declare function computeSeoScore(results: readonly SeoCheckResult[], weights?: Readonly<Record<string, number>>): number;
|
|
71
|
+
interface ReadabilityThresholds {
|
|
72
|
+
/** 문장 평균 길이(공백 제외 글자 수) 상한. */
|
|
73
|
+
sentenceAvgMax: number;
|
|
74
|
+
/** 문장 최장 길이 상한. */
|
|
75
|
+
sentenceLongMax: number;
|
|
76
|
+
/** 문단 평균 길이 상한. */
|
|
77
|
+
paragraphAvgMax: number;
|
|
78
|
+
/** 소제목 사이(또는 첫 소제목 이전) 최대 글자 수 상한. */
|
|
79
|
+
headingDensityMax: number;
|
|
80
|
+
/** 이 글자 수 이상인데 목록이 없으면 권고. */
|
|
81
|
+
listRecommendBodyMin: number;
|
|
82
|
+
}
|
|
83
|
+
declare const DEFAULT_READABILITY_THRESHOLDS: ReadabilityThresholds;
|
|
84
|
+
/** readability 4종 진입점 — 항목 순서 고정(문장→문단→밀도→목록). */
|
|
85
|
+
declare function runReadabilityChecks(bodyJson: TiptapNode | null | undefined, thresholds?: ReadabilityThresholds): SeoCheckResult[];
|
|
86
|
+
|
|
87
|
+
export { type BusinessProfileCompleteness, DEFAULT_READABILITY_THRESHOLDS, DEFAULT_SEO_THRESHOLDS, type ReadabilityThresholds, SEO_SCORE_WEIGHTS, type SeoCheckInput, type SeoCheckLevel, type SeoCheckResult, type SeoThresholds, type TiptapMark, type TiptapNode, checkBusinessProfile, collectNodes, computeSeoScore, countBodyChars, extractText, runReadabilityChecks, runSeoChecks, walkNodes };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_READABILITY_THRESHOLDS,
|
|
3
|
+
DEFAULT_SEO_THRESHOLDS,
|
|
4
|
+
SEO_SCORE_WEIGHTS,
|
|
5
|
+
checkBusinessProfile,
|
|
6
|
+
collectNodes,
|
|
7
|
+
computeSeoScore,
|
|
8
|
+
countBodyChars,
|
|
9
|
+
extractText,
|
|
10
|
+
runReadabilityChecks,
|
|
11
|
+
runSeoChecks,
|
|
12
|
+
walkNodes
|
|
13
|
+
} from "./chunk-6GQCFFHU.js";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_READABILITY_THRESHOLDS,
|
|
16
|
+
DEFAULT_SEO_THRESHOLDS,
|
|
17
|
+
SEO_SCORE_WEIGHTS,
|
|
18
|
+
checkBusinessProfile,
|
|
19
|
+
collectNodes,
|
|
20
|
+
computeSeoScore,
|
|
21
|
+
countBodyChars,
|
|
22
|
+
extractText,
|
|
23
|
+
runReadabilityChecks,
|
|
24
|
+
runSeoChecks,
|
|
25
|
+
walkNodes
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=seo-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/server.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ 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 { Block, BindingResolver, RouteCollection } from '@roottale/cms-core';
|
|
5
|
-
export { R as RootTaleCssVars, t as themeToCssVars } from './theme-css-vars-
|
|
5
|
+
export { R as RootTaleCssVars, t as themeToCssVars } from './theme-css-vars-CD3jcEkp.js';
|
|
6
|
+
import '@roottale/ui-react/theme';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* `<RootTalePostCard>` — server-only summary card for a single CmsPostContent.
|
|
@@ -74,6 +75,8 @@ declare function attachHeadingIds(doc: Record<string, unknown>, entries: TocEntr
|
|
|
74
75
|
interface RootTaleTableOfContentsProps {
|
|
75
76
|
entries: TocEntry[];
|
|
76
77
|
title?: string;
|
|
78
|
+
/** 명시 `title` 없을 때 기본 제목의 locale. 미지정 = ko("목차"). */
|
|
79
|
+
locale?: string;
|
|
77
80
|
}
|
|
78
81
|
declare function RootTaleTableOfContents(props: RootTaleTableOfContentsProps): ReactElement | null;
|
|
79
82
|
|
|
@@ -116,10 +119,24 @@ declare function RootTaleFloatingCta(props: RootTaleFloatingCtaProps): ReactElem
|
|
|
116
119
|
* PII 는 server 측에서 암호화 후 `inquiries` insert, 302 `?ok=1` / `?err=*` redirect.
|
|
117
120
|
*
|
|
118
121
|
* vertical prop:
|
|
119
|
-
* - 미지정 = "분야 선택" select 노출 (consulting/medical/tax/legal)
|
|
122
|
+
* - 미지정 = "분야 선택" select 노출 (consulting/medical/tax/legal), 필드셋은
|
|
123
|
+
* 기존 B2B 세트 그대로(방문자가 vertical 을 스스로 고르는 범용 폼이라 필드를
|
|
124
|
+
* vertical 별로 분기하지 않음)
|
|
120
125
|
* - 지정 = hidden input + 표시 라벨 + vertical 별 추가 필드
|
|
121
126
|
* (medical = 국외이전 동의 — ADR-0018)
|
|
122
127
|
*
|
|
128
|
+
* vertical="medical" 필드셋 (C5 1차, premium-component-backlog):
|
|
129
|
+
* 환자가 채우는 문의 폼에 B2B 리드 필드("사업체명" 필수·"현재 사이트 URL")를
|
|
130
|
+
* 노출하던 정합성 문제를 수정 — 의료는 이름·전화번호(필수) + 상담 희망
|
|
131
|
+
* 분야/증상·문의 내용(선택)만 노출한다. intake 계약(tenant-api `POST
|
|
132
|
+
* /v1/public/inquiries`, `packages/api-core/src/routes/inquiries.ts`)이
|
|
133
|
+
* `business_name` 을 vertical 불문 hard-required 로 검증하므로(신규 폼 백엔드
|
|
134
|
+
* 금지 — F3 계약 고정), 화면에는 안 보이지만 hidden input 으로 자리표시자
|
|
135
|
+
* `"-"` 를 전송해 계약을 만족시킨다. "상담 희망 분야/증상" 은 기존
|
|
136
|
+
* `consultation_field` 키(어드민 CRM "상담 분야" 표시)로, "문의 내용" 은 기존
|
|
137
|
+
* `message` 키로 매핑 — 둘 다 이미 intake 가 선택 필드로 수용하던 키라 서버
|
|
138
|
+
* 변경이 필요 없다. "현재 사이트 URL" 은 의료에 무의미해 필드 자체를 생략.
|
|
139
|
+
*
|
|
123
140
|
* redirectPath prop:
|
|
124
141
|
* - 외부 사이트가 폼 제출 후 돌아갈 path (API 가 allowlist 검증, ?ok=1 / ?err=*)
|
|
125
142
|
*
|
|
@@ -274,6 +291,19 @@ type RootTaleBlogListProps = {
|
|
|
274
291
|
* `fetchCollections()` 결과를 넘긴다(`collections.md` 참고).
|
|
275
292
|
*/
|
|
276
293
|
collections?: readonly RouteCollection[];
|
|
294
|
+
/**
|
|
295
|
+
* **네거티브** 스코프 — `collections` 와 함께 주면 어느 스트림에도 안 속한
|
|
296
|
+
* ("미소속") 글만 렌더한다(`postsNotInAnyCollection`, `blog-list-collection-scope.md`
|
|
297
|
+
* §2①). `collection`(포지티브) 과 배타적이며 **동시 지정은 throw**. `collections`
|
|
298
|
+
* 없이 `excludeCollections` 만 주는 것도 **throw**(조용한 no-op 금지, fail-closed).
|
|
299
|
+
*
|
|
300
|
+
* **적용 조건 한정** — `/blog` 가 *선언 컬렉션이 아닌*(미소속 글이 `/blog` 를 받는)
|
|
301
|
+
* 사이트에서만 쓴다. **`blog` 자체가 선언 컬렉션인 사이트(예: kjmtax — 글의
|
|
302
|
+
* `collectionKey === "blog"`) 는 반대로 포지티브 `collection="blog"` 가 정경로다.**
|
|
303
|
+
* 그런 사이트에 `excludeCollections` 를 적용하면 블로그 글까지 전부 "소속 글"로
|
|
304
|
+
* 걸려 제외되므로 목록이 비게 된다 — 동시 지정 throw 로는 이 오적용을 못 잡는다.
|
|
305
|
+
*/
|
|
306
|
+
excludeCollections?: boolean;
|
|
277
307
|
/**
|
|
278
308
|
* 카테고리 필터 칩을 목록 위에 렌더. SSR-only — 칩은 `<a>` 링크이고
|
|
279
309
|
* `?category=<slug>` URL 파라미터로 동작한다(client JS 불요). consumer 는
|
|
@@ -301,6 +331,12 @@ type RootTaleBlogListProps = {
|
|
|
301
331
|
* `<RootTaleThemeProvider>` 가 변수를 이미 설정한 경우 유용).
|
|
302
332
|
*/
|
|
303
333
|
theme?: RootTaleThemeInput;
|
|
334
|
+
/**
|
|
335
|
+
* ADR-0052 A안 (W4-6 PR C1) — 언어판 필터(`fetchPosts` 의 `locale` 그대로 전달).
|
|
336
|
+
* 미지정 시 서버가 사이트 기본 locale 반환(하위호환). cms-i18n 기본 문자열
|
|
337
|
+
* (`emptyMessage`/`allCategoriesLabel` 미지정 시)의 locale 해석에도 쓰인다.
|
|
338
|
+
*/
|
|
339
|
+
locale?: string;
|
|
304
340
|
};
|
|
305
341
|
declare function RootTaleBlogList(props: RootTaleBlogListProps): Promise<ReactElement>;
|
|
306
342
|
type RootTaleBlogCategoriesProps = {
|
|
@@ -321,6 +357,8 @@ type RootTaleBlogCategoriesProps = {
|
|
|
321
357
|
collections?: readonly RouteCollection[];
|
|
322
358
|
/** See `RootTaleBlogListProps['theme']`. */
|
|
323
359
|
theme?: RootTaleThemeInput;
|
|
360
|
+
/** See `RootTaleBlogListProps['locale']`. */
|
|
361
|
+
locale?: string;
|
|
324
362
|
};
|
|
325
363
|
declare function RootTaleBlogCategories(props: RootTaleBlogCategoriesProps): Promise<ReactElement>;
|
|
326
364
|
type RootTaleBlogPostProps = {
|
|
@@ -348,8 +386,17 @@ type RootTaleBlogPostProps = {
|
|
|
348
386
|
postHref?: (post: CmsPostContent) => string;
|
|
349
387
|
/** 다중 스트림 라우팅(ADR-0060) — 관련 글 링크 해석에 사용. */
|
|
350
388
|
collections?: readonly RouteCollection[];
|
|
389
|
+
/**
|
|
390
|
+
* W5-2 — 양식(필드 그룹) 스칼라 값을 연관 박스 아래 정보 패널로 렌더.
|
|
391
|
+
* 라벨·타입은 서버 동봉 `fieldsMeta` 를 사용 — 렌더러는 vertical 지식 없음.
|
|
392
|
+
* relationship 계열은 연관 박스가 담당하므로 제외. 표시할 값이 하나도 없으면
|
|
393
|
+
* 패널 자체 미렌더(빈 껍데기 금지). 기본 false — 기존 소비자 마크업 불변.
|
|
394
|
+
*/
|
|
395
|
+
showFieldFacts?: boolean;
|
|
351
396
|
/** See `RootTaleBlogListProps['theme']`. */
|
|
352
397
|
theme?: RootTaleThemeInput;
|
|
398
|
+
/** See `RootTaleBlogListProps['locale']`. `fetchPost`/관련 글 fetch 양쪽에 적용. */
|
|
399
|
+
locale?: string;
|
|
353
400
|
};
|
|
354
401
|
declare function RootTaleBlogPost(props: RootTaleBlogPostProps): Promise<ReactElement>;
|
|
355
402
|
declare function RootTaleFaqJsonLd({ doc, }: {
|
|
@@ -369,6 +416,8 @@ type RootTalePageProps = {
|
|
|
369
416
|
showTitle?: boolean;
|
|
370
417
|
/** See `RootTaleBlogListProps['theme']`. */
|
|
371
418
|
theme?: RootTaleThemeInput;
|
|
419
|
+
/** See `RootTaleBlogListProps['locale']`. */
|
|
420
|
+
locale?: string;
|
|
372
421
|
};
|
|
373
422
|
/**
|
|
374
423
|
* 고정 페이지(posts type='page') 렌더러 — RootTaleBlogPost 의 경량판.
|
package/dist/server.js
CHANGED
|
@@ -17,14 +17,20 @@ import {
|
|
|
17
17
|
renderBlocks,
|
|
18
18
|
renderBoundBlocks,
|
|
19
19
|
themeToCssVars
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RSNUZ4BF.js";
|
|
21
|
+
import "./chunk-U5AKACPN.js";
|
|
22
|
+
import "./chunk-4LQPZDXZ.js";
|
|
23
|
+
import "./chunk-K62RR4UQ.js";
|
|
24
|
+
import "./chunk-H46JUIHZ.js";
|
|
25
|
+
import "./chunk-WWS43XCT.js";
|
|
21
26
|
import {
|
|
22
27
|
collectCategories,
|
|
23
28
|
collectCategoriesWithCounts,
|
|
24
29
|
defaultCategoryHref,
|
|
25
30
|
filterPostsByCategory
|
|
26
31
|
} from "./chunk-FUA5H6JE.js";
|
|
27
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-NUUTTSGT.js";
|
|
33
|
+
import "./chunk-F3G6RVIR.js";
|
|
28
34
|
export {
|
|
29
35
|
RenderTiptap,
|
|
30
36
|
RootTaleBlogCategories,
|