@ryusenkei/maezae 0.1.0 → 0.3.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/README.md +14 -0
- package/dist/analytics.d.ts +1 -0
- package/dist/analytics.js +33 -0
- package/dist/body.d.ts +1 -24
- package/dist/body.js +40 -238
- package/dist/declared-9_jI3JXH.js +336 -0
- package/dist/delivery.d.ts +1 -211
- package/dist/integrations.d.ts +1 -0
- package/dist/integrations.js +16 -0
- package/dist/model.d.ts +1 -2
- package/dist/model.js +676 -1
- package/dist/preview.d.ts +1 -44
- package/dist/preview.js +1 -103
- package/dist/src-Baj-EC2G.js +205 -0
- package/dist/src-BrLwIWaT.js +7 -0
- package/dist/trace.d.ts +1 -0
- package/dist/trace.js +1 -0
- package/dist/types/access/src/index.d.ts +5 -0
- package/dist/types/access/src/jwt.d.ts +42 -0
- package/dist/types/access/src/oidc.d.ts +47 -0
- package/dist/types/access/src/signed.d.ts +47 -0
- package/dist/types/access/src/span.d.ts +23 -0
- package/dist/types/access/src/traceparent.d.ts +21 -0
- package/dist/types/analytics/src/day.d.ts +2 -0
- package/dist/types/analytics/src/index.d.ts +4 -0
- package/dist/types/analytics/src/record.d.ts +22 -0
- package/dist/types/analytics/src/schedule.d.ts +2 -0
- package/dist/types/analytics/src/source.d.ts +33 -0
- package/dist/types/analytics/src/store.d.ts +24 -0
- package/dist/types/body/src/components.d.ts +25 -0
- package/dist/types/body/src/declared.d.ts +34 -0
- package/dist/types/body/src/derive.d.ts +13 -0
- package/dist/types/body/src/description.d.ts +8 -0
- package/dist/types/body/src/href.d.ts +4 -0
- package/dist/types/body/src/index.d.ts +11 -0
- package/dist/types/body/src/library.d.ts +94 -0
- package/dist/types/body/src/markdown.d.ts +14 -0
- package/dist/types/body/src/normalize.d.ts +7 -0
- package/dist/types/body/src/profile.d.ts +48 -0
- package/dist/types/body/src/schema.d.ts +6 -0
- package/dist/types/body/src/standard.d.ts +20 -0
- package/dist/types/body/src/types.d.ts +91 -0
- package/dist/types/cms-contract/src/apiKeys.d.ts +13 -0
- package/dist/types/cms-contract/src/assets.d.ts +10 -0
- package/dist/types/cms-contract/src/builtins.d.ts +17 -0
- package/dist/types/cms-contract/src/embeds.d.ts +56 -0
- package/dist/types/cms-contract/src/errorLabels.d.ts +22 -0
- package/dist/types/cms-contract/src/errors.d.ts +97 -0
- package/dist/types/cms-contract/src/extensions.d.ts +75 -0
- package/dist/types/cms-contract/src/fieldRules.d.ts +37 -0
- package/dist/types/cms-contract/src/functions.d.ts +71 -0
- package/dist/types/cms-contract/src/index.d.ts +19 -0
- package/dist/types/cms-contract/src/labels.d.ts +6 -0
- package/dist/types/cms-contract/src/modules.d.ts +144 -0
- package/dist/types/cms-contract/src/paths.d.ts +6 -0
- package/dist/types/cms-contract/src/pending.d.ts +4 -0
- package/dist/types/cms-contract/src/permissions.d.ts +69 -0
- package/dist/types/cms-contract/src/roles.d.ts +35 -0
- package/dist/types/cms-contract/src/settings.d.ts +19 -0
- package/dist/types/cms-contract/src/transfer.d.ts +48 -0
- package/dist/types/cms-contract/src/views.d.ts +21 -0
- package/dist/types/cms-contract/src/webhooks.d.ts +45 -0
- package/dist/types/delivery-client/src/client.d.ts +62 -0
- package/dist/types/delivery-client/src/index.d.ts +5 -0
- package/dist/types/delivery-client/src/media.d.ts +12 -0
- package/dist/types/delivery-client/src/query.d.ts +68 -0
- package/dist/types/delivery-client/src/schema.d.ts +19 -0
- package/dist/types/delivery-client/src/values.d.ts +63 -0
- package/dist/types/model/src/asset.d.ts +89 -0
- package/dist/types/model/src/capabilities.d.ts +20 -0
- package/dist/types/model/src/changes.d.ts +23 -0
- package/dist/types/model/src/check.d.ts +10 -0
- package/dist/types/model/src/image.d.ts +38 -0
- package/dist/types/model/src/index.d.ts +14 -0
- package/dist/types/model/src/input.d.ts +26 -0
- package/dist/types/model/src/media.d.ts +5 -0
- package/dist/types/model/src/model.d.ts +190 -0
- package/dist/types/model/src/page.d.ts +47 -0
- package/dist/types/model/src/rules.d.ts +15 -0
- package/dist/types/model/src/schema.d.ts +3 -0
- package/dist/types/model/src/screens.d.ts +72 -0
- package/dist/types/model/src/time.d.ts +25 -0
- package/dist/types/model/src/values.d.ts +24 -0
- package/dist/types/sdk/src/analytics.d.ts +2 -0
- package/dist/types/sdk/src/body.d.ts +2 -0
- package/dist/types/sdk/src/defaults.d.ts +5 -0
- package/dist/types/sdk/src/delivery.d.ts +2 -0
- package/dist/types/sdk/src/integrations.d.ts +4 -0
- package/dist/types/sdk/src/model.d.ts +5 -0
- package/dist/types/sdk/src/preview.d.ts +2 -0
- package/dist/types/sdk/src/trace.d.ts +2 -0
- package/package.json +14 -2
- package/dist/index-57nirXxn.d.ts +0 -335
- package/dist/index-D11fUsoi.d.ts +0 -131
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ maezae の SDK(導入先のサイト向け)
|
|
|
10
10
|
| `@ryusenkei/maezae/body` | 本文のブロックの型と描画の補助 |
|
|
11
11
|
| `@ryusenkei/maezae/model` | コンテンツスキーマの型・ページのパス・画像の切り抜き |
|
|
12
12
|
| `@ryusenkei/maezae/preview` | プレビューと下書きの取得の署名(ヘッダー `x-preview-signature`) |
|
|
13
|
+
| `@ryusenkei/maezae/analytics` | サイトの閲覧の記録(CMS の閲覧数の集計と同じ形式) |
|
|
14
|
+
| `@ryusenkei/maezae/trace` | CMS のログと相関を取るトレースの区間 |
|
|
15
|
+
| `@ryusenkei/maezae/integrations` | 外の関数・Webhook が受け取る本文の型と署名の検証(ヘッダー `x-cms-signature`) |
|
|
13
16
|
|
|
14
17
|
## 取得(Cloudflare Workers の service binding)
|
|
15
18
|
|
|
@@ -35,3 +38,14 @@ import { SIGNED_HEADERS, verifyRequest } from '@ryusenkei/maezae/preview';
|
|
|
35
38
|
|
|
36
39
|
const isPreview = await verifyRequest(env.PREVIEW_TOKEN, request, { header: SIGNED_HEADERS.preview });
|
|
37
40
|
```
|
|
41
|
+
|
|
42
|
+
## 外の関数・Webhook の受け手
|
|
43
|
+
|
|
44
|
+
- 鍵: 媒体の管理画面の「設定 → 連携」の登録ごとの署名の鍵
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { type PublishCheckRequest, SIGNED_HEADERS, verifyRequest } from '@ryusenkei/maezae/integrations';
|
|
48
|
+
|
|
49
|
+
if (!(await verifyRequest(env.CMS_FUNCTION_SECRET, request, { header: SIGNED_HEADERS.outbound }))) return new Response(null, { status: 401 });
|
|
50
|
+
const body = (await request.json()) as PublishCheckRequest;
|
|
51
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types/sdk/src/analytics.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ../analytics/src/day.ts
|
|
2
|
+
/** 時間帯 timeZone での日付(YYYY-MM-DD、例: timeZone は Asia/Tokyo) */
|
|
3
|
+
const dayIn = (timeZone, at = /* @__PURE__ */ new Date()) => new Intl.DateTimeFormat("en-CA", {
|
|
4
|
+
timeZone,
|
|
5
|
+
year: "numeric",
|
|
6
|
+
month: "2-digit",
|
|
7
|
+
day: "2-digit"
|
|
8
|
+
}).format(at);
|
|
9
|
+
// ../analytics/src/record.ts
|
|
10
|
+
const BOT = /bot|crawl|spider|slurp|preview|monitor|headless/i;
|
|
11
|
+
/** ボットの UA か */
|
|
12
|
+
const isBot = (userAgent) => BOT.test(userAgent ?? "");
|
|
13
|
+
/** 閲覧を1件記録(対象: GET で応答が200の HTML・ボット以外、記録の失敗は無視) */
|
|
14
|
+
function recordView(dataset, request, response, { classify, timeZone, tenant }) {
|
|
15
|
+
if (!dataset || request.method !== "GET" || response.status !== 200) return;
|
|
16
|
+
if (!response.headers.get("content-type")?.includes("text/html") || isBot(request.headers.get("user-agent"))) return;
|
|
17
|
+
const path = new URL(request.url).pathname;
|
|
18
|
+
const { kind, id } = classify(path);
|
|
19
|
+
try {
|
|
20
|
+
dataset.writeDataPoint({
|
|
21
|
+
blobs: [
|
|
22
|
+
kind,
|
|
23
|
+
id,
|
|
24
|
+
path,
|
|
25
|
+
dayIn(timeZone),
|
|
26
|
+
"view",
|
|
27
|
+
tenant ?? ""
|
|
28
|
+
],
|
|
29
|
+
indexes: [tenant ?? kind]
|
|
30
|
+
});
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
export { recordView };
|
package/dist/body.d.ts
CHANGED
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/** meta description の長さの上限(検索結果で省略されにくい字数の目安) */
|
|
3
|
-
export declare const DESCRIPTION_MAX = 120;
|
|
4
|
-
/**
|
|
5
|
-
* 説明を meta description の長さに調整
|
|
6
|
-
* - 上限を超える場合: 上限までの最後の句点まで
|
|
7
|
-
* - 句点がない場合: 上限で切って省略の記号
|
|
8
|
-
*/
|
|
9
|
-
export declare function descriptionOf(excerpt: string, max?: number): string;
|
|
10
|
-
/** 読了時間の既定の分速(字/分) */
|
|
11
|
-
export declare const READING_SPEED = 500;
|
|
12
|
-
/** 断片の列の文字(装飾・リンクは文字、インラインのコンポーネントは中の文字) */
|
|
13
|
-
export declare const plainOf: (rich: Rich) => string;
|
|
14
|
-
/** 本文の注が参照する出典の ID(出現の順、重複あり) */
|
|
15
|
-
export declare function citesOf<M>(body: Body<M>): string[];
|
|
16
|
-
/** 本文の導入の文字(最初の見出しまでの段落をつなげたもの、導入がなければ空) */
|
|
17
|
-
export declare function excerptOf<M>(body: Body<M>): string;
|
|
18
|
-
/** 読了時間(分、本文の文字列の字数を分速 speed で割った概算、1分以上) */
|
|
19
|
-
export declare const readMinOf: <M>(body: Body<M>, speed?: number) => number;
|
|
20
|
-
/** 本文にあるサイト内へのリンク先(`/` で始まる href、重複なし) */
|
|
21
|
-
export declare const linksOf: <M>(body: Body<M>) => string[];
|
|
22
|
-
/** 本文のリンク先として許可する形か(保存の検査とサイトの出力で共通) */
|
|
23
|
-
export declare const isSafeHref: (href: string) => boolean;
|
|
24
|
-
export { type Block, type Body, type ComponentBlock, type HeadingLevel, type Inline, type InlineComponent, type Link, type List, type ListItem, type Mark, type Rich, type StandardBlock, type Text, blockSchemaOf };
|
|
1
|
+
export * from './types/sdk/src/body.js';
|
package/dist/body.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as coreComponentOf, n as declaredPropsZod, r as CITE } from "./declared-9_jI3JXH.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
// ../body/src/description.ts
|
|
3
4
|
/** meta description の長さの上限(検索結果で省略されにくい字数の目安) */
|
|
@@ -13,224 +14,6 @@ function descriptionOf(excerpt, max = 120) {
|
|
|
13
14
|
const end = head.lastIndexOf("。");
|
|
14
15
|
return end > 0 ? head.slice(0, end + 1) : `${[...head].slice(0, max - 1).join("")}…`;
|
|
15
16
|
}
|
|
16
|
-
// ../body/src/library.ts
|
|
17
|
-
/** 注のコンポーネントの名前(本文からの算出 citesOf が参照) */
|
|
18
|
-
const CITE = "cite";
|
|
19
|
-
const https = z.string().regex(/^https:\/\//, "URL は https://");
|
|
20
|
-
const caption = z.string().min(1, "注記が未入力");
|
|
21
|
-
const datum = z.object({
|
|
22
|
-
label: z.string(),
|
|
23
|
-
value: z.number()
|
|
24
|
-
});
|
|
25
|
-
const color = z.union([
|
|
26
|
-
z.literal(1),
|
|
27
|
-
z.literal(2),
|
|
28
|
-
z.literal(3),
|
|
29
|
-
z.literal(4),
|
|
30
|
-
z.literal(5),
|
|
31
|
-
z.literal(6),
|
|
32
|
-
z.literal(7),
|
|
33
|
-
z.literal(8)
|
|
34
|
-
]);
|
|
35
|
-
const chart = z.union([
|
|
36
|
-
z.object({
|
|
37
|
-
chart: z.literal("bar"),
|
|
38
|
-
data: z.array(datum),
|
|
39
|
-
unit: z.string(),
|
|
40
|
-
color: color.optional(),
|
|
41
|
-
caption
|
|
42
|
-
}),
|
|
43
|
-
z.object({
|
|
44
|
-
chart: z.literal("line"),
|
|
45
|
-
labels: z.array(z.string()),
|
|
46
|
-
series: z.array(z.object({
|
|
47
|
-
name: z.string(),
|
|
48
|
-
values: z.array(z.number())
|
|
49
|
-
})),
|
|
50
|
-
unit: z.string(),
|
|
51
|
-
caption
|
|
52
|
-
}),
|
|
53
|
-
z.object({
|
|
54
|
-
chart: z.literal("pie"),
|
|
55
|
-
data: z.array(datum),
|
|
56
|
-
unit: z.string(),
|
|
57
|
-
caption
|
|
58
|
-
}),
|
|
59
|
-
z.object({
|
|
60
|
-
chart: z.literal("heat"),
|
|
61
|
-
cols: z.array(z.string()),
|
|
62
|
-
rows: z.array(z.object({
|
|
63
|
-
label: z.string(),
|
|
64
|
-
values: z.array(z.number())
|
|
65
|
-
})),
|
|
66
|
-
unit: z.string(),
|
|
67
|
-
color: color.optional(),
|
|
68
|
-
caption
|
|
69
|
-
})
|
|
70
|
-
]);
|
|
71
|
-
const optional = (attrs, keys) => Object.fromEntries(keys.filter((k) => attrs[k] !== void 0).map((k) => [k, attrs[k]]));
|
|
72
|
-
const plain = (rich) => rich.map((i) => typeof i === "string" ? i : "text" in i ? i.text ?? "" : "").join("");
|
|
73
|
-
/** 核のコンポーネント(chart の Markdown は既定の JSON) */
|
|
74
|
-
const CORE_COMPONENTS = [
|
|
75
|
-
{
|
|
76
|
-
name: CITE,
|
|
77
|
-
inline: true,
|
|
78
|
-
description: "注(出典の ID を参照、出典は記事に結び付けが必要)",
|
|
79
|
-
example: ":cite{#出典の ID}",
|
|
80
|
-
props: () => z.object({ id: z.string().min(1, "注の出典が未入力") })
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: "pending",
|
|
84
|
-
inline: true,
|
|
85
|
-
description: "未定の値(方針のページで値が決まっていない箇所)",
|
|
86
|
-
example: ":pending[未定]",
|
|
87
|
-
props: () => z.object({})
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: "speech",
|
|
91
|
-
description: "発言(会話の一言を大きく引く、1段落)",
|
|
92
|
-
example: ":::speech\n大きく引く一言\n:::",
|
|
93
|
-
markdown: {
|
|
94
|
-
to: (p) => ({ content: [{
|
|
95
|
-
type: "paragraph",
|
|
96
|
-
content: [p.text]
|
|
97
|
-
}] }),
|
|
98
|
-
from: (_, content) => ({ text: content[0]?.type === "paragraph" ? plain(content[0].content) : "" })
|
|
99
|
-
},
|
|
100
|
-
props: () => z.object({ text: z.string().min(1, "発言が未入力") })
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "chart",
|
|
104
|
-
description: "グラフ(中は JSON、chart は bar・line・pie・heat、caption は必須)",
|
|
105
|
-
example: ":::chart\n```json\n{\"chart\": \"bar\", \"unit\": \"%\", \"data\": [{\"label\": \"A\", \"value\": 40}], \"caption\": \"説明\"}\n```\n:::",
|
|
106
|
-
props: () => chart
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: "diagram",
|
|
110
|
-
description: "図(中は Mermaid、alt は図の内容の説明、caption は必須)",
|
|
111
|
-
example: ":::diagram{alt=\"内容の説明\" caption=\"説明\"}\n```mermaid\nflowchart LR\n A --> B\n```\n:::",
|
|
112
|
-
markdown: {
|
|
113
|
-
to: (p) => ({
|
|
114
|
-
attributes: {
|
|
115
|
-
alt: p.alt,
|
|
116
|
-
caption: p.caption
|
|
117
|
-
},
|
|
118
|
-
content: [{
|
|
119
|
-
type: "code",
|
|
120
|
-
lang: "mermaid",
|
|
121
|
-
code: p.source
|
|
122
|
-
}]
|
|
123
|
-
}),
|
|
124
|
-
from: (attrs, content) => ({
|
|
125
|
-
source: content[0]?.type === "code" ? content[0].code : "",
|
|
126
|
-
alt: attrs.alt ?? "",
|
|
127
|
-
caption: attrs.caption ?? ""
|
|
128
|
-
})
|
|
129
|
-
},
|
|
130
|
-
props: () => z.object({
|
|
131
|
-
source: z.string().min(1, "図の記法が未入力"),
|
|
132
|
-
alt: z.string().min(1, "図の代替テキストが未入力"),
|
|
133
|
-
caption
|
|
134
|
-
})
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
name: "gallery",
|
|
138
|
-
description: "複数の画像を2列で配置(画像は2枚以上)",
|
|
139
|
-
example: ":::gallery\n\n\n\n:::",
|
|
140
|
-
markdown: {
|
|
141
|
-
to: (p) => ({ content: p.items.map((i) => ({
|
|
142
|
-
type: "image",
|
|
143
|
-
media: i.media,
|
|
144
|
-
...i.caption ? { caption: i.caption } : {}
|
|
145
|
-
})) }),
|
|
146
|
-
from: (_, content) => ({ items: content.flatMap((b) => b.type === "image" ? [{
|
|
147
|
-
media: b.media,
|
|
148
|
-
...b.caption ? { caption: b.caption } : {}
|
|
149
|
-
}] : []) })
|
|
150
|
-
},
|
|
151
|
-
props: (media) => z.object({ items: z.array(z.object({
|
|
152
|
-
media,
|
|
153
|
-
caption: z.string().optional()
|
|
154
|
-
})).min(2, "ギャラリーの画像は2枚以上") })
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: "link-card",
|
|
158
|
-
description: "外部リンクのカード(https のみ、site・desc・画像は任意)",
|
|
159
|
-
example: ":::link-card{href=\"https://…\" title=\"ページの題\" site=\"サイト名\" desc=\"説明\"}\n:::",
|
|
160
|
-
markdown: {
|
|
161
|
-
to: (p) => {
|
|
162
|
-
const { href, title, site, desc, image } = p;
|
|
163
|
-
return {
|
|
164
|
-
attributes: {
|
|
165
|
-
href,
|
|
166
|
-
title,
|
|
167
|
-
...site ? { site } : {},
|
|
168
|
-
...desc ? { desc } : {}
|
|
169
|
-
},
|
|
170
|
-
content: image ? [{
|
|
171
|
-
type: "image",
|
|
172
|
-
media: image
|
|
173
|
-
}] : []
|
|
174
|
-
};
|
|
175
|
-
},
|
|
176
|
-
from: (attrs, content) => ({
|
|
177
|
-
href: attrs.href,
|
|
178
|
-
title: attrs.title,
|
|
179
|
-
...optional(attrs, ["site", "desc"]),
|
|
180
|
-
...content[0]?.type === "image" ? { image: content[0].media } : {}
|
|
181
|
-
})
|
|
182
|
-
},
|
|
183
|
-
props: (media) => z.object({
|
|
184
|
-
href: https,
|
|
185
|
-
title: z.string().min(1, "リンクのタイトルが未入力"),
|
|
186
|
-
site: z.string().optional(),
|
|
187
|
-
desc: z.string().optional(),
|
|
188
|
-
image: media.optional()
|
|
189
|
-
})
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: "links",
|
|
193
|
-
description: "外部リンクの一覧(1件1行、リンクの title はサイト名、label は小見出しで既定は「参考」)",
|
|
194
|
-
example: ":::links{label=\"参考\"}\n- [ページの題](https://… \"サイト名\")\n:::",
|
|
195
|
-
markdown: {
|
|
196
|
-
to: (p) => {
|
|
197
|
-
const { label, items } = p;
|
|
198
|
-
const content = [{
|
|
199
|
-
type: "list",
|
|
200
|
-
items: items.map((i) => ({ content: [{
|
|
201
|
-
text: i.title,
|
|
202
|
-
link: {
|
|
203
|
-
href: i.href,
|
|
204
|
-
...i.site ? { title: i.site } : {}
|
|
205
|
-
}
|
|
206
|
-
}] }))
|
|
207
|
-
}];
|
|
208
|
-
return {
|
|
209
|
-
attributes: label ? { label } : void 0,
|
|
210
|
-
content
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
from: (attrs, content) => ({
|
|
214
|
-
...optional(attrs, ["label"]),
|
|
215
|
-
items: content[0]?.type === "list" ? content[0].items.flatMap((i) => i.content.flatMap((x) => typeof x !== "string" && "link" in x && x.link ? [{
|
|
216
|
-
href: x.link.href,
|
|
217
|
-
title: x.text,
|
|
218
|
-
...x.link.title ? { site: x.link.title } : {}
|
|
219
|
-
}] : [])) : []
|
|
220
|
-
})
|
|
221
|
-
},
|
|
222
|
-
props: () => z.object({
|
|
223
|
-
label: z.string().optional(),
|
|
224
|
-
items: z.array(z.object({
|
|
225
|
-
href: https,
|
|
226
|
-
title: z.string().min(1, "リンクのタイトルが未入力"),
|
|
227
|
-
site: z.string().optional()
|
|
228
|
-
}))
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
];
|
|
232
|
-
/** 名前から核のコンポーネント(なければ undefined) */
|
|
233
|
-
const coreComponentOf = (name) => CORE_COMPONENTS.find((c) => c.name === name);
|
|
234
17
|
// ../body/src/derive.ts
|
|
235
18
|
/** 読了時間の既定の分速(字/分) */
|
|
236
19
|
const READING_SPEED = 500;
|
|
@@ -412,42 +195,61 @@ const text = z.object({
|
|
|
412
195
|
marks: z.array(z.enum(MARKS)).optional(),
|
|
413
196
|
link: link.optional()
|
|
414
197
|
});
|
|
415
|
-
|
|
198
|
+
/** 部品の props の検査(名前で核のコンポーネント、なければ宣言した部品、どちらもないか種類が違えば null) */
|
|
199
|
+
function propsOf(name, inline, media, declared) {
|
|
200
|
+
const core = coreComponentOf(name);
|
|
201
|
+
if (core) return Boolean(core.inline) === inline ? core.props(media) : null;
|
|
202
|
+
const d = declared.find((c) => c.name === name);
|
|
203
|
+
return d && Boolean(d.inline) === inline ? declaredPropsZod(d) : null;
|
|
204
|
+
}
|
|
205
|
+
const inlineComponentOf = (declared) => z.object({
|
|
416
206
|
component: z.string().min(1),
|
|
417
207
|
props: z.record(z.string(), z.string()).optional(),
|
|
418
208
|
text: z.string().optional()
|
|
419
209
|
}).superRefine((c, ctx) => {
|
|
420
|
-
const
|
|
421
|
-
if (!
|
|
210
|
+
const props = propsOf(c.component, true, z.never(), declared);
|
|
211
|
+
if (!props) return void ctx.addIssue({
|
|
422
212
|
code: "custom",
|
|
423
213
|
message: `インラインのコンポーネント ${c.component} はない`
|
|
424
214
|
});
|
|
425
|
-
const r =
|
|
215
|
+
const r = props.safeParse(c.props ?? {});
|
|
426
216
|
if (!r.success) for (const issue of r.error.issues) ctx.addIssue({
|
|
427
217
|
code: "custom",
|
|
428
218
|
message: issue.message,
|
|
429
219
|
path: ["props", ...issue.path]
|
|
430
220
|
});
|
|
431
221
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
text
|
|
436
|
-
|
|
437
|
-
const
|
|
222
|
+
/** インラインの1要素(component を持つ要素は部品としてだけ検査、union で文字の形に落ちて部品の検査を素通りしないため) */
|
|
223
|
+
function inlineItemOf(declared) {
|
|
224
|
+
const component = inlineComponentOf(declared);
|
|
225
|
+
const plain = z.union([z.string(), text]);
|
|
226
|
+
return z.unknown().transform((value, ctx) => {
|
|
227
|
+
const r = (typeof value === "object" && value !== null && "component" in value ? component : plain).safeParse(value);
|
|
228
|
+
if (r.success) return r.data;
|
|
229
|
+
for (const issue of r.error.issues) ctx.addIssue({
|
|
230
|
+
code: "custom",
|
|
231
|
+
message: issue.message,
|
|
232
|
+
path: issue.path
|
|
233
|
+
});
|
|
234
|
+
return z.NEVER;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const richOf = (cites, declared) => z.array(inlineItemOf(declared)).transform((items) => cites ? items.flatMap((item) => typeof item === "string" ? liftCites(item) : [item]) : items);
|
|
438
238
|
const variant = z.string().optional();
|
|
439
|
-
/** 本文の1ブロックの検査(profile は本文の設定、media
|
|
440
|
-
function blockSchemaOf(profile, media) {
|
|
441
|
-
const rich = richOf(profile.components.includes(CITE));
|
|
442
|
-
const list = z.
|
|
239
|
+
/** 本文の1ブロックの検査(profile は本文の設定、media は画像の参照の検査、declared は導入先が宣言した部品) */
|
|
240
|
+
function blockSchemaOf(profile, media, declared = []) {
|
|
241
|
+
const rich = richOf(profile.components.includes(CITE), declared);
|
|
242
|
+
const list = z.object({
|
|
443
243
|
type: z.literal("list"),
|
|
444
244
|
ordered: z.boolean().optional(),
|
|
445
245
|
items: z.array(z.object({
|
|
446
246
|
content: rich,
|
|
447
|
-
children
|
|
247
|
+
get children() {
|
|
248
|
+
return list.optional();
|
|
249
|
+
}
|
|
448
250
|
})),
|
|
449
251
|
variant
|
|
450
|
-
})
|
|
252
|
+
});
|
|
451
253
|
const heading = z.object({
|
|
452
254
|
type: z.literal("heading"),
|
|
453
255
|
level: z.union([
|
|
@@ -462,7 +264,7 @@ function blockSchemaOf(profile, media) {
|
|
|
462
264
|
id: z.string().optional(),
|
|
463
265
|
variant
|
|
464
266
|
});
|
|
465
|
-
return z.
|
|
267
|
+
return z.discriminatedUnion("type", [
|
|
466
268
|
z.object({
|
|
467
269
|
type: z.literal("paragraph"),
|
|
468
270
|
content: rich,
|
|
@@ -513,15 +315,15 @@ function blockSchemaOf(profile, media) {
|
|
|
513
315
|
]).transform((block, ctx) => {
|
|
514
316
|
let out = block;
|
|
515
317
|
if (out.type === "component") {
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
318
|
+
const props = propsOf(out.name, false, media, declared);
|
|
319
|
+
if (!props) {
|
|
518
320
|
ctx.addIssue({
|
|
519
321
|
code: "custom",
|
|
520
322
|
message: `コンポーネント ${out.name} はない`
|
|
521
323
|
});
|
|
522
324
|
return z.NEVER;
|
|
523
325
|
}
|
|
524
|
-
const r =
|
|
326
|
+
const r = props.safeParse(out.props);
|
|
525
327
|
if (!r.success) {
|
|
526
328
|
for (const issue of r.error.issues) ctx.addIssue({
|
|
527
329
|
code: "custom",
|