@roottale/cms-client 0.25.0 → 0.33.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 +23 -0
- package/dist/server.d.ts +283 -240
- package/dist/server.js +562 -512
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @roottale/cms-client
|
|
2
2
|
|
|
3
|
+
## 0.33.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2e6cbd0: ADR-0060 Amendment 1 (Option A) — 글의 섹션을 명시 필드로
|
|
8
|
+
|
|
9
|
+
`resolvePostCollection` 이 글의 명시 `collectionKey` 를 카테고리 slug 파생보다
|
|
10
|
+
우선 사용한다. `RoutablePost`·`CmsPostContent` 에 `collectionKey` 추가, 공개 API
|
|
11
|
+
post 페이로드에 `collection_key` 포함. 미설정/구 서버면 기존 카테고리 파생으로
|
|
12
|
+
fallback(완전 하위호환). sitemap/feed/가드/아카이브가 명시 섹션을 따른다.
|
|
13
|
+
|
|
14
|
+
## 0.31.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- b68ed00: 콘텐츠 Collections 공개 API + 클라이언트 (ADR-0060 P2.1)
|
|
19
|
+
|
|
20
|
+
`fetchCollections()` + `CmsRouteCollection` 추가 — 사이트가 `/v1/cms/public/collections`
|
|
21
|
+
에서 콘텐츠 스트림(공지/블로그 등)을 가져온다. 응답은 cms-core `RouteCollection` 과
|
|
22
|
+
구조 호환이라 `createSitemap`/`createFeedRoute` 의 `collections` 에 그대로 넘길 수 있다.
|
|
23
|
+
미설정이면 빈 배열(레거시 단일 /blog). collections SSoT = 플랫폼 per-site `settings`
|
|
24
|
+
테이블 key `content_collections`(마이그레이션 불필요 — 기존 KV).
|
|
25
|
+
|
|
3
26
|
## 0.25.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/dist/server.d.ts
CHANGED
|
@@ -1,212 +1,6 @@
|
|
|
1
1
|
import { InquiryAttribution } from './attribution.js';
|
|
2
2
|
export { parseAttributionJson } from './attribution.js';
|
|
3
3
|
|
|
4
|
-
interface RssItemInput {
|
|
5
|
-
title: string;
|
|
6
|
-
/** 글 절대 URL. */
|
|
7
|
-
link: string;
|
|
8
|
-
/** 고유 식별자. 미지정 시 link 사용. */
|
|
9
|
-
guid?: string;
|
|
10
|
-
description?: string | null;
|
|
11
|
-
/** 발행 시각. */
|
|
12
|
-
pubDate?: string | Date | null;
|
|
13
|
-
}
|
|
14
|
-
interface RssFeedInput {
|
|
15
|
-
/** 사이트/피드 제목. */
|
|
16
|
-
title: string;
|
|
17
|
-
/** 사이트 홈 URL. */
|
|
18
|
-
link: string;
|
|
19
|
-
/** 이 피드 자체의 URL (atom:self). */
|
|
20
|
-
feedUrl: string;
|
|
21
|
-
description?: string;
|
|
22
|
-
/** 기본 "ko-KR". */
|
|
23
|
-
language?: string;
|
|
24
|
-
items: RssItemInput[];
|
|
25
|
-
}
|
|
26
|
-
/** RSS 2.0 XML 문자열 생성. */
|
|
27
|
-
declare function generateRssXml(input: RssFeedInput): string;
|
|
28
|
-
/** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */
|
|
29
|
-
declare function rssItemsFromPosts(posts: CmsPostContent[], opts: {
|
|
30
|
-
postUrl: (post: CmsPostContent) => string;
|
|
31
|
-
}): RssItemInput[];
|
|
32
|
-
type JsonLd = Record<string, unknown>;
|
|
33
|
-
interface ArticleSchemaInput {
|
|
34
|
-
headline: string;
|
|
35
|
-
url: string;
|
|
36
|
-
description?: string | null;
|
|
37
|
-
image?: string | null;
|
|
38
|
-
datePublished?: string | Date | null;
|
|
39
|
-
dateModified?: string | Date | null;
|
|
40
|
-
authorName?: string | null;
|
|
41
|
-
publisherName?: string;
|
|
42
|
-
publisherLogo?: string;
|
|
43
|
-
}
|
|
44
|
-
declare function articleSchema(input: ArticleSchemaInput): JsonLd;
|
|
45
|
-
declare function breadcrumbSchema(items: {
|
|
46
|
-
name: string;
|
|
47
|
-
url: string;
|
|
48
|
-
}[]): JsonLd;
|
|
49
|
-
declare function organizationSchema(input: {
|
|
50
|
-
name: string;
|
|
51
|
-
url: string;
|
|
52
|
-
logo?: string;
|
|
53
|
-
sameAs?: string[];
|
|
54
|
-
}): JsonLd;
|
|
55
|
-
/**
|
|
56
|
-
* `localBusinessSchema` 입력 — `fetchBusinessProfile` 의 반환값
|
|
57
|
-
* (RootTaleBusinessProfile) 을 그대로 받도록 느슨하게 정의 (null/undefined 모두
|
|
58
|
-
* 허용). 수동 구성도 가능.
|
|
59
|
-
*/
|
|
60
|
-
interface LocalBusinessProfileInput {
|
|
61
|
-
name: string;
|
|
62
|
-
legalName?: string | null;
|
|
63
|
-
/** Schema.org LocalBusiness 하위 타입. 미지정 시 "LocalBusiness". */
|
|
64
|
-
businessType?: string | null;
|
|
65
|
-
telephone?: string | null;
|
|
66
|
-
email?: string | null;
|
|
67
|
-
address?: {
|
|
68
|
-
streetAddress: string;
|
|
69
|
-
addressLocality: string;
|
|
70
|
-
addressRegion?: string | null;
|
|
71
|
-
postalCode?: string | null;
|
|
72
|
-
} | null;
|
|
73
|
-
geo?: {
|
|
74
|
-
latitude: number;
|
|
75
|
-
longitude: number;
|
|
76
|
-
} | null;
|
|
77
|
-
openingHours?: {
|
|
78
|
-
days: string[];
|
|
79
|
-
opens: string;
|
|
80
|
-
closes: string;
|
|
81
|
-
}[] | null;
|
|
82
|
-
priceRange?: string | null;
|
|
83
|
-
areaServed?: string[] | null;
|
|
84
|
-
/** 외부 프로필 URL — 값들이 sameAs 로 들어간다 (네이버플레이스·구글 등). */
|
|
85
|
-
profiles?: Record<string, string | null | undefined> | null;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* LocalBusiness JSON-LD — admin "운영 > 비즈니스 프로필" 의
|
|
89
|
-
* `fetchBusinessProfile` 결과로 layout 에 1회 렌더한다 (로컬 SEO).
|
|
90
|
-
*
|
|
91
|
-
* - `@type` 은 `[businessType, "LocalBusiness"]` 중복 제거 배열.
|
|
92
|
-
* - `profiles` 의 URL 들은 `sameAs` 로 — 검색엔진이 네이버플레이스/구글
|
|
93
|
-
* 비즈니스 프로필과 동일 사업장임을 연결한다.
|
|
94
|
-
*/
|
|
95
|
-
declare function localBusinessSchema(profile: LocalBusinessProfileInput | RootTaleBusinessProfile, opts: {
|
|
96
|
-
url: string;
|
|
97
|
-
image?: string | null;
|
|
98
|
-
}): JsonLd;
|
|
99
|
-
declare function websiteSchema(input: {
|
|
100
|
-
name: string;
|
|
101
|
-
url: string;
|
|
102
|
-
searchUrl?: string;
|
|
103
|
-
}): JsonLd;
|
|
104
|
-
declare function faqSchema(qa: {
|
|
105
|
-
question: string;
|
|
106
|
-
answer: string;
|
|
107
|
-
}[]): JsonLd;
|
|
108
|
-
/** JSON-LD 객체 → `<script type="application/ld+json">` HTML 문자열 (XSS-safe). */
|
|
109
|
-
declare function jsonLdScript(schema: JsonLd | JsonLd[]): string;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.
|
|
113
|
-
*
|
|
114
|
-
* 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도
|
|
115
|
-
* 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /
|
|
116
|
-
* Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지
|
|
117
|
-
* (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.
|
|
118
|
-
*
|
|
119
|
-
* 사용 예 (Next.js server action):
|
|
120
|
-
* ```ts
|
|
121
|
-
* "use server";
|
|
122
|
-
* import { submitInquiry } from "@roottale/cms-client/server";
|
|
123
|
-
*
|
|
124
|
-
* const result = await submitInquiry({
|
|
125
|
-
* apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키
|
|
126
|
-
* fields: {
|
|
127
|
-
* vertical: "tax",
|
|
128
|
-
* contactName: name,
|
|
129
|
-
* businessName: company,
|
|
130
|
-
* email,
|
|
131
|
-
* phone,
|
|
132
|
-
* message,
|
|
133
|
-
* privacyConsent: true,
|
|
134
|
-
* },
|
|
135
|
-
* });
|
|
136
|
-
* if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착
|
|
137
|
-
* else showError(result.message);
|
|
138
|
-
* ```
|
|
139
|
-
*
|
|
140
|
-
* intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
type InquiryVertical = "consulting" | "medical" | "tax" | "legal";
|
|
144
|
-
/** intake API 의 `err` 코드 → 한국어 사용자 메시지. */
|
|
145
|
-
declare const INQUIRY_ERROR_MESSAGES: Record<string, string>;
|
|
146
|
-
interface SubmitInquiryFields {
|
|
147
|
-
vertical: InquiryVertical;
|
|
148
|
-
contactName: string;
|
|
149
|
-
businessName: string;
|
|
150
|
-
email: string;
|
|
151
|
-
phone: string;
|
|
152
|
-
/** 자유 문의 내용 (선택). */
|
|
153
|
-
message?: string;
|
|
154
|
-
/** 상담 분야 라벨 등 부가 메타 (선택). */
|
|
155
|
-
consultationField?: string;
|
|
156
|
-
/** 현재 사이트 URL (선택). */
|
|
157
|
-
currentSiteUrl?: string;
|
|
158
|
-
/** 개인정보 수집·이용 동의 (필수 true). */
|
|
159
|
-
privacyConsent: boolean;
|
|
160
|
-
/** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */
|
|
161
|
-
overseasTransferConsent?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
* "patient"(고객사 방문자 문의, 기본) | "sales"(RootTale 자체 B2B 영업 리드).
|
|
164
|
-
* sales 는 medical 국외이전 동의 게이트 면제.
|
|
165
|
-
*/
|
|
166
|
-
leadKind?: "patient" | "sales";
|
|
167
|
-
/**
|
|
168
|
-
* 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가
|
|
169
|
-
* 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.
|
|
170
|
-
* 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.
|
|
171
|
-
*/
|
|
172
|
-
extras?: Record<string, string>;
|
|
173
|
-
/**
|
|
174
|
-
* 유입 first-touch 어트리뷰션 (ADR-0048 §2, 선택). 브라우저에서
|
|
175
|
-
* `readAttribution()`(`@roottale/cms-client/attribution`)으로 읽어 Server
|
|
176
|
-
* Action 인자로 넘긴 값을 그대로 전달하면 된다. CRM 에 "이 문의가 어느
|
|
177
|
-
* 글/링크/QR 에서 왔는지"가 표시된다. null/undefined 면 미동반 제출.
|
|
178
|
-
*/
|
|
179
|
-
attribution?: InquiryAttribution | null;
|
|
180
|
-
}
|
|
181
|
-
interface SubmitInquiryOptions {
|
|
182
|
-
/** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */
|
|
183
|
-
apiKey: string;
|
|
184
|
-
/** API base URL 오버라이드. 기본 `https://api.roottale.com`. */
|
|
185
|
-
baseUrl?: string;
|
|
186
|
-
fields: SubmitInquiryFields;
|
|
187
|
-
signal?: AbortSignal;
|
|
188
|
-
}
|
|
189
|
-
type SubmitInquiryResult = {
|
|
190
|
-
ok: true;
|
|
191
|
-
} | {
|
|
192
|
-
ok: false;
|
|
193
|
-
code: string | null;
|
|
194
|
-
message: string;
|
|
195
|
-
};
|
|
196
|
-
/** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */
|
|
197
|
-
declare function inquiryErrorMessage(code: string | null | undefined): string;
|
|
198
|
-
/**
|
|
199
|
-
* intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.
|
|
200
|
-
* 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.
|
|
201
|
-
*/
|
|
202
|
-
declare function parseInquiryRedirect(location: string | null, base?: string): SubmitInquiryResult;
|
|
203
|
-
declare function formatKoreanPhoneNumber(input: string): string;
|
|
204
|
-
/**
|
|
205
|
-
* 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).
|
|
206
|
-
* 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.
|
|
207
|
-
*/
|
|
208
|
-
declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
|
|
209
|
-
|
|
210
4
|
/**
|
|
211
5
|
* `@roottale/cms-client/server` — RootTale CMS Public API server-side fetch client.
|
|
212
6
|
*
|
|
@@ -243,6 +37,11 @@ interface CmsPostContent {
|
|
|
243
37
|
siteId: string;
|
|
244
38
|
/** WP post type. */
|
|
245
39
|
type: CmsPostType;
|
|
40
|
+
/**
|
|
41
|
+
* ADR-0060 Amendment 1 — 글의 명시 섹션(content collection) key. 라우팅/스트림
|
|
42
|
+
* 판정에 카테고리 파생보다 우선. 구 서버/미설정이면 null.
|
|
43
|
+
*/
|
|
44
|
+
collectionKey: string | null;
|
|
246
45
|
/** Lowercase kebab-case slug (1-120 chars). */
|
|
247
46
|
slug: string;
|
|
248
47
|
title: string;
|
|
@@ -318,6 +117,12 @@ interface FetchPostOptions {
|
|
|
318
117
|
siteId?: string;
|
|
319
118
|
signal?: AbortSignal;
|
|
320
119
|
}
|
|
120
|
+
declare class CmsApiError extends Error {
|
|
121
|
+
readonly status: number;
|
|
122
|
+
readonly code: string;
|
|
123
|
+
constructor(message: string, status: number, code: string);
|
|
124
|
+
}
|
|
125
|
+
|
|
321
126
|
/**
|
|
322
127
|
* 외부 사이트가 cms-renderer 에 주입할 RootTale 디자인 토큰.
|
|
323
128
|
* admin /design (mysite.roottale.com) 에서 저장한 값과 동일한 shape.
|
|
@@ -390,6 +195,9 @@ interface FetchAnalyticsOptions {
|
|
|
390
195
|
* 동의는 항상 fail-closed(런타임이 처리)이므로 키 노출 위험 없음(public id).
|
|
391
196
|
*/
|
|
392
197
|
declare function fetchAnalyticsConfig(options: FetchAnalyticsOptions): Promise<SiteAnalyticsTags>;
|
|
198
|
+
declare function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage>;
|
|
199
|
+
declare function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null>;
|
|
200
|
+
|
|
393
201
|
/**
|
|
394
202
|
* 블로그 표시 설정 — admin /design 의 "블로그 표시" 에서 저장한 사이트 기본값.
|
|
395
203
|
* renderer 가 prop 미지정 시 이 값을 기본으로 적용 (글별 override 는 post 의
|
|
@@ -430,8 +238,69 @@ interface FetchBlogSettingsOptions {
|
|
|
430
238
|
* 호출자(renderer)가 안전 기본값으로 fallback.
|
|
431
239
|
*/
|
|
432
240
|
declare function fetchBlogSettings(options: FetchBlogSettingsOptions): Promise<RootTaleBlogSettings>;
|
|
241
|
+
/**
|
|
242
|
+
* 콘텐츠 스트림 1개 (camelCase). cms-core `RouteCollection` 과 구조 호환 —
|
|
243
|
+
* `createSitemap`/`createFeedRoute` 의 `collections` 에 그대로 넘길 수 있다.
|
|
244
|
+
*/
|
|
245
|
+
interface CmsRouteCollection {
|
|
246
|
+
key: string;
|
|
247
|
+
label: string;
|
|
248
|
+
basePath: string;
|
|
249
|
+
layout: string;
|
|
250
|
+
categories: string[];
|
|
251
|
+
feed: boolean;
|
|
252
|
+
archives: boolean;
|
|
253
|
+
og: boolean;
|
|
254
|
+
requireSubcategory: boolean;
|
|
255
|
+
pinning: boolean;
|
|
256
|
+
icon: string | null;
|
|
257
|
+
}
|
|
258
|
+
interface FetchCollectionsOptions {
|
|
259
|
+
apiKey: string;
|
|
260
|
+
baseUrl?: string;
|
|
261
|
+
siteId?: string;
|
|
262
|
+
signal?: AbortSignal;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 사이트의 콘텐츠 Collections (공지/블로그 등 스트림 분리)를 가져온다. 미설정이면
|
|
266
|
+
* 빈 배열(레거시 단일 /blog). 네트워크/서버 에러는 throw — 호출자가 빈 배열로
|
|
267
|
+
* fail-soft 하도록 한다(렌더가 collections 때문에 죽지 않는 것이 계약).
|
|
268
|
+
*/
|
|
269
|
+
declare function fetchCollections(options: FetchCollectionsOptions): Promise<CmsRouteCollection[]>;
|
|
270
|
+
/**
|
|
271
|
+
* fetch 실패/미설정 시 안전 기본값 — 빈 배열(레거시 단일 /blog). 사이트가
|
|
272
|
+
* `fetchCollections().catch(() => fetchCollectionsSafeDefault())` 로 쓴다.
|
|
273
|
+
*/
|
|
274
|
+
declare const fetchCollectionsSafeDefault: CmsRouteCollection[];
|
|
433
275
|
/** fetch 실패/미설정 시 사용할 안전 기본값 (기존 렌더러 동작). */
|
|
434
276
|
declare const DEFAULT_BLOG_SETTINGS: RootTaleBlogSettings;
|
|
277
|
+
interface ResolvedPostDisplay {
|
|
278
|
+
showTableOfContents: boolean;
|
|
279
|
+
showAuthor: boolean;
|
|
280
|
+
showDate: boolean;
|
|
281
|
+
showAuthorCard: boolean;
|
|
282
|
+
tocTitle: string;
|
|
283
|
+
authorCardEyebrow: string;
|
|
284
|
+
authorCardDescription: string | null;
|
|
285
|
+
authorProfileName: string | null;
|
|
286
|
+
authorProfileBio: string | null;
|
|
287
|
+
authorProfileImageUrl: string | null;
|
|
288
|
+
authorProfileImageRadius: "circle" | "rounded" | "square";
|
|
289
|
+
authorProfileImagePositionX: number;
|
|
290
|
+
authorProfileImagePositionY: number;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 글 1건의 실제 표시 여부 결정. 우선순위:
|
|
294
|
+
* 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).
|
|
295
|
+
* tocTitle: 명시 prop > settings.tocTitle > "목차".
|
|
296
|
+
*/
|
|
297
|
+
declare function resolvePostDisplay(post: {
|
|
298
|
+
metaJson?: Record<string, unknown> | null;
|
|
299
|
+
}, settings: RootTaleBlogSettings, explicit?: {
|
|
300
|
+
showTableOfContents?: boolean;
|
|
301
|
+
tableOfContentsTitle?: string;
|
|
302
|
+
}): ResolvedPostDisplay;
|
|
303
|
+
|
|
435
304
|
/** Schema.org LocalBusiness 하위 타입 (admin "운영 > 비즈니스 프로필" 의 업종). */
|
|
436
305
|
type BusinessType = "LocalBusiness" | "ProfessionalService" | "AccountingService" | "LegalService" | "MedicalClinic" | "Dentist" | "RealEstateAgent" | "Restaurant" | "BeautySalon";
|
|
437
306
|
/** Schema.org openingHours 요일 약어. */
|
|
@@ -526,39 +395,7 @@ declare function businessProfileFromWire(json: BusinessProfileWire): RootTaleBus
|
|
|
526
395
|
* `.catch(() => null)` 등으로 fallback.
|
|
527
396
|
*/
|
|
528
397
|
declare function fetchBusinessProfile(options: FetchBusinessProfileOptions): Promise<RootTaleBusinessProfile | null>;
|
|
529
|
-
|
|
530
|
-
showTableOfContents: boolean;
|
|
531
|
-
showAuthor: boolean;
|
|
532
|
-
showDate: boolean;
|
|
533
|
-
showAuthorCard: boolean;
|
|
534
|
-
tocTitle: string;
|
|
535
|
-
authorCardEyebrow: string;
|
|
536
|
-
authorCardDescription: string | null;
|
|
537
|
-
authorProfileName: string | null;
|
|
538
|
-
authorProfileBio: string | null;
|
|
539
|
-
authorProfileImageUrl: string | null;
|
|
540
|
-
authorProfileImageRadius: "circle" | "rounded" | "square";
|
|
541
|
-
authorProfileImagePositionX: number;
|
|
542
|
-
authorProfileImagePositionY: number;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* 글 1건의 실제 표시 여부 결정. 우선순위:
|
|
546
|
-
* 명시 prop > 글별 override(post.metaJson.display) > 사이트 기본값(settings).
|
|
547
|
-
* tocTitle: 명시 prop > settings.tocTitle > "목차".
|
|
548
|
-
*/
|
|
549
|
-
declare function resolvePostDisplay(post: {
|
|
550
|
-
metaJson?: Record<string, unknown> | null;
|
|
551
|
-
}, settings: RootTaleBlogSettings, explicit?: {
|
|
552
|
-
showTableOfContents?: boolean;
|
|
553
|
-
tableOfContentsTitle?: string;
|
|
554
|
-
}): ResolvedPostDisplay;
|
|
555
|
-
declare class CmsApiError extends Error {
|
|
556
|
-
readonly status: number;
|
|
557
|
-
readonly code: string;
|
|
558
|
-
constructor(message: string, status: number, code: string);
|
|
559
|
-
}
|
|
560
|
-
declare function fetchPosts(options: FetchPostsOptions): Promise<CmsPostContentPage>;
|
|
561
|
-
declare function fetchPost(options: FetchPostOptions): Promise<CmsPostContent | null>;
|
|
398
|
+
|
|
562
399
|
interface SearchPostsOptions {
|
|
563
400
|
apiKey: string;
|
|
564
401
|
baseUrl?: string;
|
|
@@ -629,4 +466,210 @@ declare function fetchMenu(options: FetchMenuOptions): Promise<RootTaleMenu | nu
|
|
|
629
466
|
/** 사이트의 네비게이션 메뉴 전체. 구 서버(라우트 미배포)는 404 — 빈 배열. */
|
|
630
467
|
declare function fetchMenus(options: FetchMenusOptions): Promise<RootTaleMenu[]>;
|
|
631
468
|
|
|
632
|
-
|
|
469
|
+
interface RssItemInput {
|
|
470
|
+
title: string;
|
|
471
|
+
/** 글 절대 URL. */
|
|
472
|
+
link: string;
|
|
473
|
+
/** 고유 식별자. 미지정 시 link 사용. */
|
|
474
|
+
guid?: string;
|
|
475
|
+
description?: string | null;
|
|
476
|
+
/** 발행 시각. */
|
|
477
|
+
pubDate?: string | Date | null;
|
|
478
|
+
}
|
|
479
|
+
interface RssFeedInput {
|
|
480
|
+
/** 사이트/피드 제목. */
|
|
481
|
+
title: string;
|
|
482
|
+
/** 사이트 홈 URL. */
|
|
483
|
+
link: string;
|
|
484
|
+
/** 이 피드 자체의 URL (atom:self). */
|
|
485
|
+
feedUrl: string;
|
|
486
|
+
description?: string;
|
|
487
|
+
/** 기본 "ko-KR". */
|
|
488
|
+
language?: string;
|
|
489
|
+
items: RssItemInput[];
|
|
490
|
+
}
|
|
491
|
+
/** RSS 2.0 XML 문자열 생성. */
|
|
492
|
+
declare function generateRssXml(input: RssFeedInput): string;
|
|
493
|
+
/** CmsPostContent[] → RSS item[]. postUrl 로 글 절대 URL 생성. */
|
|
494
|
+
declare function rssItemsFromPosts(posts: CmsPostContent[], opts: {
|
|
495
|
+
postUrl: (post: CmsPostContent) => string;
|
|
496
|
+
}): RssItemInput[];
|
|
497
|
+
type JsonLd = Record<string, unknown>;
|
|
498
|
+
interface ArticleSchemaInput {
|
|
499
|
+
headline: string;
|
|
500
|
+
url: string;
|
|
501
|
+
description?: string | null;
|
|
502
|
+
image?: string | null;
|
|
503
|
+
datePublished?: string | Date | null;
|
|
504
|
+
dateModified?: string | Date | null;
|
|
505
|
+
authorName?: string | null;
|
|
506
|
+
publisherName?: string;
|
|
507
|
+
publisherLogo?: string;
|
|
508
|
+
}
|
|
509
|
+
declare function articleSchema(input: ArticleSchemaInput): JsonLd;
|
|
510
|
+
declare function breadcrumbSchema(items: {
|
|
511
|
+
name: string;
|
|
512
|
+
url: string;
|
|
513
|
+
}[]): JsonLd;
|
|
514
|
+
declare function organizationSchema(input: {
|
|
515
|
+
name: string;
|
|
516
|
+
url: string;
|
|
517
|
+
logo?: string;
|
|
518
|
+
sameAs?: string[];
|
|
519
|
+
}): JsonLd;
|
|
520
|
+
/**
|
|
521
|
+
* `localBusinessSchema` 입력 — `fetchBusinessProfile` 의 반환값
|
|
522
|
+
* (RootTaleBusinessProfile) 을 그대로 받도록 느슨하게 정의 (null/undefined 모두
|
|
523
|
+
* 허용). 수동 구성도 가능.
|
|
524
|
+
*/
|
|
525
|
+
interface LocalBusinessProfileInput {
|
|
526
|
+
name: string;
|
|
527
|
+
legalName?: string | null;
|
|
528
|
+
/** Schema.org LocalBusiness 하위 타입. 미지정 시 "LocalBusiness". */
|
|
529
|
+
businessType?: string | null;
|
|
530
|
+
telephone?: string | null;
|
|
531
|
+
email?: string | null;
|
|
532
|
+
address?: {
|
|
533
|
+
streetAddress: string;
|
|
534
|
+
addressLocality: string;
|
|
535
|
+
addressRegion?: string | null;
|
|
536
|
+
postalCode?: string | null;
|
|
537
|
+
} | null;
|
|
538
|
+
geo?: {
|
|
539
|
+
latitude: number;
|
|
540
|
+
longitude: number;
|
|
541
|
+
} | null;
|
|
542
|
+
openingHours?: {
|
|
543
|
+
days: string[];
|
|
544
|
+
opens: string;
|
|
545
|
+
closes: string;
|
|
546
|
+
}[] | null;
|
|
547
|
+
priceRange?: string | null;
|
|
548
|
+
areaServed?: string[] | null;
|
|
549
|
+
/** 외부 프로필 URL — 값들이 sameAs 로 들어간다 (네이버플레이스·구글 등). */
|
|
550
|
+
profiles?: Record<string, string | null | undefined> | null;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* LocalBusiness JSON-LD — admin "운영 > 비즈니스 프로필" 의
|
|
554
|
+
* `fetchBusinessProfile` 결과로 layout 에 1회 렌더한다 (로컬 SEO).
|
|
555
|
+
*
|
|
556
|
+
* - `@type` 은 `[businessType, "LocalBusiness"]` 중복 제거 배열.
|
|
557
|
+
* - `profiles` 의 URL 들은 `sameAs` 로 — 검색엔진이 네이버플레이스/구글
|
|
558
|
+
* 비즈니스 프로필과 동일 사업장임을 연결한다.
|
|
559
|
+
*/
|
|
560
|
+
declare function localBusinessSchema(profile: LocalBusinessProfileInput | RootTaleBusinessProfile, opts: {
|
|
561
|
+
url: string;
|
|
562
|
+
image?: string | null;
|
|
563
|
+
}): JsonLd;
|
|
564
|
+
declare function websiteSchema(input: {
|
|
565
|
+
name: string;
|
|
566
|
+
url: string;
|
|
567
|
+
searchUrl?: string;
|
|
568
|
+
}): JsonLd;
|
|
569
|
+
declare function faqSchema(qa: {
|
|
570
|
+
question: string;
|
|
571
|
+
answer: string;
|
|
572
|
+
}[]): JsonLd;
|
|
573
|
+
/** JSON-LD 객체 → `<script type="application/ld+json">` HTML 문자열 (XSS-safe). */
|
|
574
|
+
declare function jsonLdScript(schema: JsonLd | JsonLd[]): string;
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* `@roottale/cms-client` — 리드(문의) 접수 server-side 헬퍼.
|
|
578
|
+
*
|
|
579
|
+
* 외부 고객사 사이트가 *블로그 조회(`fetchPosts`)와 동일한 API key 하나* 로 리드도
|
|
580
|
+
* 접수하기 위한 SSR-only 클라이언트. 브라우저가 아니라 사이트 서버(Server Action /
|
|
581
|
+
* Route Handler)에서 호출해야 한다 — API key 는 절대 브라우저로 노출 금지
|
|
582
|
+
* (ADR-0023 §5.1 #15). 키가 tenant 를 권위 식별하므로 `tenant_slug` 불필요.
|
|
583
|
+
*
|
|
584
|
+
* 사용 예 (Next.js server action):
|
|
585
|
+
* ```ts
|
|
586
|
+
* "use server";
|
|
587
|
+
* import { submitInquiry } from "@roottale/cms-client/server";
|
|
588
|
+
*
|
|
589
|
+
* const result = await submitInquiry({
|
|
590
|
+
* apiKey: process.env.ROOTTALE_API_KEY!, // fetchPosts 와 같은 키
|
|
591
|
+
* fields: {
|
|
592
|
+
* vertical: "tax",
|
|
593
|
+
* contactName: name,
|
|
594
|
+
* businessName: company,
|
|
595
|
+
* email,
|
|
596
|
+
* phone,
|
|
597
|
+
* message,
|
|
598
|
+
* privacyConsent: true,
|
|
599
|
+
* },
|
|
600
|
+
* });
|
|
601
|
+
* if (result.ok) showSuccess(); // 그 키의 tenant /crm 에 도착
|
|
602
|
+
* else showError(result.message);
|
|
603
|
+
* ```
|
|
604
|
+
*
|
|
605
|
+
* intake API 는 302 `?ok=1` / `?err=*` 로 응답 — 본 헬퍼가 파싱해 구조화 결과 반환.
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
type InquiryVertical = "consulting" | "medical" | "tax" | "legal";
|
|
609
|
+
/** intake API 의 `err` 코드 → 한국어 사용자 메시지. */
|
|
610
|
+
declare const INQUIRY_ERROR_MESSAGES: Record<string, string>;
|
|
611
|
+
interface SubmitInquiryFields {
|
|
612
|
+
vertical: InquiryVertical;
|
|
613
|
+
contactName: string;
|
|
614
|
+
businessName: string;
|
|
615
|
+
email: string;
|
|
616
|
+
phone: string;
|
|
617
|
+
/** 자유 문의 내용 (선택). */
|
|
618
|
+
message?: string;
|
|
619
|
+
/** 상담 분야 라벨 등 부가 메타 (선택). */
|
|
620
|
+
consultationField?: string;
|
|
621
|
+
/** 현재 사이트 URL (선택). */
|
|
622
|
+
currentSiteUrl?: string;
|
|
623
|
+
/** 개인정보 수집·이용 동의 (필수 true). */
|
|
624
|
+
privacyConsent: boolean;
|
|
625
|
+
/** 의료 vertical 의 국외이전 동의 (medical 일 때 필수). */
|
|
626
|
+
overseasTransferConsent?: boolean;
|
|
627
|
+
/**
|
|
628
|
+
* "patient"(고객사 방문자 문의, 기본) | "sales"(RootTale 자체 B2B 영업 리드).
|
|
629
|
+
* sales 는 medical 국외이전 동의 게이트 면제.
|
|
630
|
+
*/
|
|
631
|
+
leadKind?: "patient" | "sales";
|
|
632
|
+
/**
|
|
633
|
+
* 표준 필드 외에 폼이 받은 임의 항목(key→value)을 있는 그대로 전달. intake 가
|
|
634
|
+
* 암호화 보관하고 CRM 상세에서 노출한다. 예약 키(표준 필드)·`_` 접두 키는 무시.
|
|
635
|
+
* 주의: 개인정보가 담길 수 있으므로 폼의 동의 고지에 수집 항목을 반영할 것.
|
|
636
|
+
*/
|
|
637
|
+
extras?: Record<string, string>;
|
|
638
|
+
/**
|
|
639
|
+
* 유입 first-touch 어트리뷰션 (ADR-0048 §2, 선택). 브라우저에서
|
|
640
|
+
* `readAttribution()`(`@roottale/cms-client/attribution`)으로 읽어 Server
|
|
641
|
+
* Action 인자로 넘긴 값을 그대로 전달하면 된다. CRM 에 "이 문의가 어느
|
|
642
|
+
* 글/링크/QR 에서 왔는지"가 표시된다. null/undefined 면 미동반 제출.
|
|
643
|
+
*/
|
|
644
|
+
attribution?: InquiryAttribution | null;
|
|
645
|
+
}
|
|
646
|
+
interface SubmitInquiryOptions {
|
|
647
|
+
/** `rtlk_cust_*` API key — `fetchPosts` 와 동일한 키. Required. */
|
|
648
|
+
apiKey: string;
|
|
649
|
+
/** API base URL 오버라이드. 기본 `https://api.roottale.com`. */
|
|
650
|
+
baseUrl?: string;
|
|
651
|
+
fields: SubmitInquiryFields;
|
|
652
|
+
signal?: AbortSignal;
|
|
653
|
+
}
|
|
654
|
+
type SubmitInquiryResult = {
|
|
655
|
+
ok: true;
|
|
656
|
+
} | {
|
|
657
|
+
ok: false;
|
|
658
|
+
code: string | null;
|
|
659
|
+
message: string;
|
|
660
|
+
};
|
|
661
|
+
/** `err` 코드를 사용자 메시지로 (미지의 코드는 generic). */
|
|
662
|
+
declare function inquiryErrorMessage(code: string | null | undefined): string;
|
|
663
|
+
/**
|
|
664
|
+
* intake API 의 302 Location (`?ok=1` / `?err=*`) 을 구조화 결과로 파싱.
|
|
665
|
+
* 외부 사이트가 ERROR_MESSAGES·파싱을 각자 재구현하지 않도록 공유.
|
|
666
|
+
*/
|
|
667
|
+
declare function parseInquiryRedirect(location: string | null, base?: string): SubmitInquiryResult;
|
|
668
|
+
declare function formatKoreanPhoneNumber(input: string): string;
|
|
669
|
+
/**
|
|
670
|
+
* 리드를 RootTale intake API 로 제출 (server-to-server, Bearer 인증).
|
|
671
|
+
* 키의 tenant 로 라우팅되어 admin(mysite.roottale.com) /crm 에서 관리된다.
|
|
672
|
+
*/
|
|
673
|
+
declare function submitInquiry(options: SubmitInquiryOptions): Promise<SubmitInquiryResult>;
|
|
674
|
+
|
|
675
|
+
export { type AnalyticsTagConfig, type AnalyticsTagProvider, type ArticleSchemaInput, type BusinessDayCode, type BusinessOpeningHours, type BusinessType, CMS_CLIENT_VERSION, CmsApiError, type CmsPostContent, type CmsPostContentPage, type CmsPostType, type CmsRouteCollection, type CmsSearchHit, type CmsTaxonomyKind, type CmsTermRef, DEFAULT_BLOG_SETTINGS, type FetchAnalyticsOptions, type FetchBlogSettingsOptions, type FetchBusinessProfileOptions, type FetchCollectionsOptions, type FetchMenuOptions, type FetchMenusOptions, type FetchPostOptions, type FetchPostsOptions, type FetchThemeOptions, INQUIRY_ERROR_MESSAGES, InquiryAttribution, type InquiryVertical, type LocalBusinessProfileInput, type ResolvedPostDisplay, type RootTaleBlogSettings, type RootTaleBusinessProfile, type RootTaleMenu, type RootTaleMenuItem, type RootTaleTheme, type RssFeedInput, type RssItemInput, type SearchPostsOptions, type SiteAnalyticsTags, type SubmitInquiryFields, type SubmitInquiryOptions, type SubmitInquiryResult, articleSchema, breadcrumbSchema, businessProfileFromWire, faqSchema, fetchAnalyticsConfig, fetchBlogSettings, fetchBusinessProfile, fetchCollections, fetchCollectionsSafeDefault, fetchMenu, fetchMenus, fetchPost, fetchPosts, fetchTheme, formatKoreanPhoneNumber, generateRssXml, inquiryErrorMessage, jsonLdScript, localBusinessSchema, organizationSchema, parseInquiryRedirect, resolvePostDisplay, rssItemsFromPosts, searchPosts, submitInquiry, websiteSchema };
|