@ryusenkei/maezae 0.16.0 → 0.17.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.
@@ -1,14 +1,99 @@
1
1
  import { z } from "zod";
2
2
  // ../body/src/components.ts
3
3
  /** 予約の名前(ブロック) */
4
- const RESERVED_BLOCK_NAMES = /* @__PURE__ */ new Set(["block"]);
4
+ const RESERVED_BLOCK_NAMES = /* @__PURE__ */ new Set([
5
+ "block",
6
+ "table",
7
+ "file"
8
+ ]);
5
9
  /** 予約の名前(インライン) */
6
10
  const RESERVED_INLINE_NAMES = /* @__PURE__ */ new Set([
7
11
  "mark",
8
12
  "sup",
9
13
  "sub",
10
- "link"
14
+ "link",
15
+ "u",
16
+ "span"
11
17
  ]);
18
+ // ../body/src/embed.ts
19
+ /** 埋め込めるサービス */
20
+ const EMBED_PROVIDERS = [
21
+ "youtube",
22
+ "vimeo",
23
+ "x",
24
+ "instagram",
25
+ "spotify",
26
+ "apple-podcasts"
27
+ ];
28
+ const ID = /^[\w-]{1,64}$/;
29
+ /** 開始の秒(YouTube の t=1m30s・t=90・start=90) */
30
+ function startOf(params) {
31
+ const raw = params.get("t") ?? params.get("start");
32
+ if (!raw) return void 0;
33
+ const m = raw.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/);
34
+ if (!m) return void 0;
35
+ const sec = Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0);
36
+ return sec > 0 ? sec : void 0;
37
+ }
38
+ /** URL から埋め込み(埋め込めるサービスでなければ null) */
39
+ function embedOfUrl(raw) {
40
+ const url = URL.parse(raw);
41
+ if (!url || url.protocol !== "https:") return null;
42
+ const host = url.hostname.replace(/^(www|m)\./, "");
43
+ const path = url.pathname.split("/").filter(Boolean);
44
+ const pick = (provider, id, start) => id && ID.test(id) ? {
45
+ provider,
46
+ id,
47
+ ...start ? { start } : {}
48
+ } : null;
49
+ switch (host) {
50
+ case "youtube.com":
51
+ case "youtube-nocookie.com":
52
+ if (path[0] === "watch") return pick("youtube", url.searchParams.get("v") ?? void 0, startOf(url.searchParams));
53
+ if (path[0] === "embed" || path[0] === "shorts" || path[0] === "live") return pick("youtube", path[1], startOf(url.searchParams));
54
+ return null;
55
+ case "youtu.be": return pick("youtube", path[0], startOf(url.searchParams));
56
+ case "vimeo.com": return pick("vimeo", path.find((p) => /^\d+$/.test(p)));
57
+ case "player.vimeo.com": return path[0] === "video" ? pick("vimeo", path[1]) : null;
58
+ case "twitter.com":
59
+ case "x.com": return path[1] === "status" ? pick("x", path[2]) : null;
60
+ case "instagram.com": return path[0] === "p" || path[0] === "reel" ? pick("instagram", path[1]) : null;
61
+ case "open.spotify.com": {
62
+ const [kind, id] = (path[0] === "embed" ? path.slice(1) : path).filter((p) => !p.startsWith("intl-"));
63
+ return kind && [
64
+ "track",
65
+ "album",
66
+ "playlist",
67
+ "episode",
68
+ "show"
69
+ ].includes(kind) && id && ID.test(id) ? {
70
+ provider: "spotify",
71
+ id: `${kind}/${id}`
72
+ } : null;
73
+ }
74
+ case "podcasts.apple.com": {
75
+ const show = path.find((p) => /^id\d+$/.test(p));
76
+ const episode = url.searchParams.get("i");
77
+ return show ? pick("apple-podcasts", episode ? `${show}-${episode}` : show) : null;
78
+ }
79
+ default: return null;
80
+ }
81
+ }
82
+ /** 描画の iframe の URL(サービスと ID から組み立てる、X と Instagram は埋め込みのページ) */
83
+ function embedSrcOf(target) {
84
+ const id = encodeURIComponent(target.id).replaceAll("%2F", "/");
85
+ switch (target.provider) {
86
+ case "youtube": return `https://www.youtube-nocookie.com/embed/${id}${target.start ? `?start=${target.start}` : ""}`;
87
+ case "vimeo": return `https://player.vimeo.com/video/${id}${target.start ? `#t=${target.start}s` : ""}`;
88
+ case "x": return `https://platform.twitter.com/embed/Tweet.html?id=${id}`;
89
+ case "instagram": return `https://www.instagram.com/p/${id}/embed`;
90
+ case "spotify": return `https://open.spotify.com/embed/${id}`;
91
+ case "apple-podcasts": {
92
+ const [show, episode] = target.id.split("-");
93
+ return `https://embed.podcasts.apple.com/podcast/${encodeURIComponent(show ?? "")}${episode ? `?i=${encodeURIComponent(episode)}` : ""}`;
94
+ }
95
+ }
96
+ }
12
97
  // ../body/src/library.ts
13
98
  /** 注のコンポーネントの名前(本文からの算出 citesOf が参照) */
14
99
  const CITE = "cite";
@@ -71,9 +156,9 @@ const CORE_COMPONENTS = [
71
156
  {
72
157
  name: CITE,
73
158
  inline: true,
74
- description: "注(出典の ID を参照、出典は記事に結び付けが必要)",
159
+ description: "注(参考資料の ID を参照、参考資料はコンテンツに結び付けが必要)",
75
160
  example: ":cite{#出典の ID}",
76
- props: () => z.object({ id: z.string().min(1, "注の出典が未入力") })
161
+ props: () => z.object({ id: z.string().min(1, "注の参考資料が未入力") })
77
162
  },
78
163
  {
79
164
  name: "pending",
@@ -151,18 +236,20 @@ const CORE_COMPONENTS = [
151
236
  },
152
237
  {
153
238
  name: "link-card",
154
- description: "外部リンクのカード(https のみ、site・desc・画像は任意)",
239
+ description: "リンクのカード(外のページは https と題名、site・desc・画像は任意。中のコンテンツは ref=\"型の id/行の ID\")",
155
240
  example: ":::link-card{href=\"https://…\" title=\"ページの題\" site=\"サイト名\" desc=\"説明\"}\n:::",
156
241
  markdown: {
157
242
  to: (p) => {
158
- const { href, title, site, desc, image } = p;
243
+ const { href, ref, title, site, desc, image, fetchedAt } = p;
159
244
  return {
160
- attributes: {
245
+ attributes: Object.fromEntries(Object.entries({
161
246
  href,
247
+ ref,
162
248
  title,
163
- ...site ? { site } : {},
164
- ...desc ? { desc } : {}
165
- },
249
+ site,
250
+ desc,
251
+ "fetched-at": fetchedAt
252
+ }).filter((e) => typeof e[1] === "string" && e[1] !== "")),
166
253
  content: image ? [{
167
254
  type: "image",
168
255
  media: image
@@ -170,18 +257,70 @@ const CORE_COMPONENTS = [
170
257
  };
171
258
  },
172
259
  from: (attrs, content) => ({
173
- href: attrs.href,
174
- title: attrs.title,
175
- ...optional(attrs, ["site", "desc"]),
260
+ ...optional(attrs, [
261
+ "href",
262
+ "ref",
263
+ "title",
264
+ "site",
265
+ "desc"
266
+ ]),
267
+ ...attrs["fetched-at"] ? { fetchedAt: attrs["fetched-at"] } : {},
176
268
  ...content[0]?.type === "image" ? { image: content[0].media } : {}
177
269
  })
178
270
  },
179
- props: (media) => z.object({
271
+ props: (media) => z.union([z.object({
180
272
  href: https,
181
273
  title: z.string().min(1, "リンクのタイトルが未入力"),
182
274
  site: z.string().optional(),
183
275
  desc: z.string().optional(),
276
+ image: media.optional(),
277
+ fetchedAt: z.string().optional()
278
+ }), z.object({
279
+ ref: z.string().regex(/^[\w-]+\/[\w-]+$/, "ref は「型の id/行の ID」"),
280
+ title: z.string().optional(),
281
+ site: z.string().optional(),
282
+ desc: z.string().optional(),
184
283
  image: media.optional()
284
+ })])
285
+ },
286
+ {
287
+ name: "embed",
288
+ description: `外のサービスの埋め込み(provider は ${EMBED_PROVIDERS.join("・")}、id はサービスの中の ID、url は元の URL、start は開始の秒)`,
289
+ example: ":::embed{provider=youtube id=\"xxxxxxxxxxx\" url=\"https://www.youtube.com/watch?v=xxxxxxxxxxx\"}\n:::",
290
+ markdown: {
291
+ to: (p) => {
292
+ const { provider, id, url, width, height, start, title } = p;
293
+ return { attributes: Object.fromEntries(Object.entries({
294
+ provider,
295
+ id,
296
+ url,
297
+ width,
298
+ height,
299
+ start,
300
+ title
301
+ }).filter(([, v]) => v !== void 0).map(([k, v]) => [k, String(v)])) };
302
+ },
303
+ from: (attrs) => {
304
+ const num = (k) => attrs[k] !== void 0 && attrs[k] !== "" ? { [k]: Number(attrs[k]) } : {};
305
+ return {
306
+ provider: attrs.provider,
307
+ id: attrs.id,
308
+ url: attrs.url,
309
+ ...num("width"),
310
+ ...num("height"),
311
+ ...num("start"),
312
+ ...optional(attrs, ["title"])
313
+ };
314
+ }
315
+ },
316
+ props: () => z.object({
317
+ provider: z.enum(EMBED_PROVIDERS),
318
+ id: z.string().regex(/^[\w-]+(\/[\w-]+)?$/, "埋め込みの ID が不正"),
319
+ url: https,
320
+ width: z.number().int().positive().optional(),
321
+ height: z.number().int().positive().optional(),
322
+ start: z.number().int().nonnegative().optional(),
323
+ title: z.string().optional()
185
324
  })
186
325
  },
187
326
  {
@@ -333,4 +472,4 @@ function declaredPropsZod(d) {
333
472
  if (!schema) propsCache.set(d, schema = objectZod(d.props.properties, d.props.required ?? [], { error: () => `${d.label}の引数の形が不正` }));
334
473
  return schema;
335
474
  }
336
- export { coreComponentOf as a, CORE_COMPONENTS as i, declaredPropsZod as n, CITE as r, declaredComponentsSchema as t };
475
+ export { coreComponentOf as a, embedSrcOf as c, CORE_COMPONENTS as i, declaredPropsZod as n, EMBED_PROVIDERS as o, CITE as r, embedOfUrl as s, declaredComponentsSchema as t };
package/dist/model.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { c as coverStart, d as objectPosition, f as stillOf, i as acceptsRole, l as cropRect, m as isDateOnly, n as publicPathOf, o as typeOf, p as createClock, r as FIELD_ROLES, s as coverPosition, t as pagePathOf, u as croppedHotspot } from "./page-BnwHL-LC.js";
2
- import { i as CORE_COMPONENTS, t as declaredComponentsSchema } from "./declared-9_jI3JXH.js";
2
+ import { i as CORE_COMPONENTS, t as declaredComponentsSchema } from "./declared-Qo-mWnDd.js";
3
3
  import "./src-BrLwIWaT.js";
4
4
  import { z } from "zod";
5
5
  // ../model/src/asset.ts
@@ -1,4 +1,5 @@
1
1
  export { recordView, type Classify, type Dataset } from './record.js';
2
2
  export { syncCron } from './schedule.js';
3
+ export { isViewsCron, sharedViews, viewsFor, viewsSince, viewsSyncOf } from './shared.js';
3
4
  export { analyticsEngine, type DailyRow, fixedSource, type Source, projectDaily, type ProjectSource, renamedSource } from './source.js';
4
5
  export { readDaily, syncDaily } from './store.js';
@@ -0,0 +1,21 @@
1
+ import { type DailyRow, type ProjectSource, type Source } from './source.js';
2
+ /** 全媒体を問い合わせる範囲の最初の日付(UTC の日付、どの時間帯の媒体の直近3日も含む) */
3
+ export declare const viewsSince: (now: number) => string;
4
+ /** 全媒体の問い合わせを行う Cron か(minutes は集計の間隔の分) */
5
+ export declare const isViewsCron: (minutes: string | undefined, cron: string) => boolean;
6
+ /** 全媒体の閲覧数の問い合わせ(対象の Cron で設定があるときだけ、なしは null) */
7
+ export declare const viewsSyncOf: (source: ProjectSource | null, minutes: string | undefined, cron: string, scheduledTime: number) => Readonly<{
8
+ source: ProjectSource;
9
+ since: string;
10
+ }> | null;
11
+ /** 媒体ごとの読み込み元の作成 */
12
+ type ViewsOf = (projectId: string) => Source | null;
13
+ /**
14
+ * Queues なしの構成の媒体ごとの読み込み元(全媒体の問い合わせ結果の行)
15
+ * - 結果にない媒体: 行なし
16
+ * - 問い合わせの失敗: 各媒体の集計の処理が失敗として記録(ログ・実行の記録)
17
+ */
18
+ export declare function sharedViews(source: ProjectSource, since: string): Promise<ViewsOf>;
19
+ /** メッセージに載った行の読み込み元(なしは媒体ごとの問い合わせ live) */
20
+ export declare const viewsFor: (rows: readonly DailyRow[] | undefined, live: ViewsOf) => ViewsOf;
21
+ export {};
@@ -1,4 +1,4 @@
1
- import type { Body, Rich } from './types.js';
1
+ import { type Body, type Rich } from './types.js';
2
2
  /** 読了時間の既定の分速(字/分) */
3
3
  export declare const READING_SPEED = 500;
4
4
  /** 断片の列の文字(装飾・リンクは文字、インラインのコンポーネントは中の文字) */
@@ -0,0 +1,14 @@
1
+ /** 埋め込めるサービス */
2
+ export declare const EMBED_PROVIDERS: readonly ["youtube", "vimeo", "x", "instagram", "spotify", "apple-podcasts"];
3
+ /** 埋め込めるサービス */
4
+ export type EmbedProvider = (typeof EMBED_PROVIDERS)[number];
5
+ /** URL から決めた埋め込み(start は開始の秒) */
6
+ export type EmbedTarget = Readonly<{
7
+ provider: EmbedProvider;
8
+ id: string;
9
+ start?: number;
10
+ }>;
11
+ /** URL から埋め込み(埋め込めるサービスでなければ null) */
12
+ export declare function embedOfUrl(raw: string): EmbedTarget | null;
13
+ /** 描画の iframe の URL(サービスと ID から組み立てる、X と Instagram は埋め込みのページ) */
14
+ export declare function embedSrcOf(target: EmbedTarget): `https://${string}`;
@@ -0,0 +1,31 @@
1
+ import type { Body } from './types.js';
2
+ /** 変換の設定 */
3
+ export type HtmlOptions<M> = Readonly<{
4
+ /** 画像の参照(src は元の URL) */
5
+ image(src: string, info: Readonly<{
6
+ alt?: string;
7
+ width?: number;
8
+ height?: number;
9
+ }>): M;
10
+ /** ファイルの参照(href は元の URL、mime は data-mime-type) */
11
+ file?(href: string, info: Readonly<{
12
+ mime?: string;
13
+ name?: string;
14
+ }>): M;
15
+ /** 名前のついた装飾として残す class(なしは class をすべて捨てる) */
16
+ styleClass?(name: string): boolean;
17
+ /** 元の CSS の値に対応する名前(本文の設定の宣言、なしは名前なし) */
18
+ nameOf?(kind: 'color' | 'background' | 'size', value: string): string | undefined;
19
+ }>;
20
+ /** 変換で捨てたもの(what は要素・属性・style の項目・class、count は回数) */
21
+ export type Dropped = Readonly<{
22
+ what: string;
23
+ count: number;
24
+ }>;
25
+ /** 変換の結果 */
26
+ export type HtmlResult<M> = Readonly<{
27
+ body: Body<M>;
28
+ dropped: readonly Dropped[];
29
+ }>;
30
+ /** HTML を本文に(写せないものは dropped) */
31
+ export declare function bodyFromHtml<M>(html: string, options: HtmlOptions<M>): HtmlResult<M>;
@@ -4,8 +4,11 @@ export { DESCRIPTION_MAX, descriptionOf } from './description.js';
4
4
  export { citesOf, excerptOf, linksOf, plainOf, READING_SPEED, readMinOf } from './derive.js';
5
5
  export { CORE_COMPONENTS, coreComponentOf, type Chart, type ChartColor, type ChartDatum, type ComponentName, type ComponentProps, type CoreComponent } from './library.js';
6
6
  export { isSafeHref } from './href.js';
7
+ export { bodyFromHtml, type Dropped, type HtmlOptions, type HtmlResult } from './html.js';
8
+ export { EMBED_PROVIDERS, embedOfUrl, embedSrcOf, type EmbedProvider, type EmbedTarget } from './embed.js';
7
9
  export { fromMarkdown, toMarkdown, type MarkdownOptions } from './markdown.js';
8
- export { checkBody, describeMarkdown, type BodyProfile } from './profile.js';
10
+ export { checkBody, describeMarkdown, isSafeCssValue, type BodyProfile, type NamedStyle, type NamedValue } from './profile.js';
11
+ export { bodyToHtml, type RenderedImage, type RenderOptions } from './render.js';
9
12
  export { blockSchemaOf } from './schema.js';
10
13
  export { bodyFromMarkdown, bodyToMarkdown, markdownGuide, type MarkdownResult, type MediaSyntax } from './standard.js';
11
- export type { Block, Body, ComponentBlock, HeadingLevel, Inline, InlineComponent, Link, List, ListItem, Mark, Rich, StandardBlock, Text } from './types.js';
14
+ export { cellParagraphs, isTableCell, type Align, type Block, type Body, type Cell, type ComponentBlock, type HeadingLevel, type ImageAlign, type Inline, type InlineComponent, type Link, type List, type ListItem, type Mark, type Named, type Rich, type StandardBlock, type TableCell, type Text, type TextStyle } from './types.js';
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { ComponentDef } from './components.js';
3
+ import { type EmbedProvider } from './embed.js';
3
4
  /** グラフの項目と値 */
4
5
  export type ChartDatum = Readonly<{
5
6
  label: string;
@@ -63,12 +64,30 @@ export type ComponentProps<M> = Readonly<{
63
64
  caption?: string;
64
65
  }>[];
65
66
  }>;
67
+ /** 外のページは href と保存のときの OGP の写し(fetchedAt は取得の日時)、中のコンテンツは ref(型の id/行の ID、題名と画像は配信のときに参照先から) */
66
68
  'link-card': Readonly<{
67
69
  href: `https://${string}`;
68
70
  title: string;
69
71
  site?: string;
70
72
  desc?: string;
71
73
  image?: M;
74
+ fetchedAt?: string;
75
+ }> | Readonly<{
76
+ ref: string;
77
+ title?: string;
78
+ site?: string;
79
+ desc?: string;
80
+ image?: M;
81
+ }>;
82
+ /** 外のサービスの埋め込み(id はサービスの中の ID、url は元の URL、start は開始の秒) */
83
+ embed: Readonly<{
84
+ provider: EmbedProvider;
85
+ id: string;
86
+ url: `https://${string}`;
87
+ width?: number;
88
+ height?: number;
89
+ start?: number;
90
+ title?: string;
72
91
  }>;
73
92
  links: Readonly<{
74
93
  label?: string;
@@ -1,5 +1,5 @@
1
1
  import { type ComponentDef } from './components.js';
2
- import type { Body } from './types.js';
2
+ import { type Body } from './types.js';
3
3
  /** マッパーの設定(media は画像の参照と Markdown の文字列の変換) */
4
4
  export type MarkdownOptions<M> = Readonly<{
5
5
  media: Readonly<{
@@ -1,4 +1,4 @@
1
- import type { Body, Mark, Rich } from './types.js';
1
+ import { type Body, type Mark, type Rich } from './types.js';
2
2
  /** 装飾の順序(Markdown では外側から内側) */
3
3
  export declare const MARK_ORDER: readonly Mark[];
4
4
  /** インラインの断片の列を正規化 */
@@ -1,5 +1,5 @@
1
1
  import type { ComponentDef } from './components.js';
2
- import type { Body, HeadingLevel, Mark } from './types.js';
2
+ import { type Body, type HeadingLevel, type Mark } from './types.js';
3
3
  /** 本文の設定 */
4
4
  export type BodyProfile = Readonly<{
5
5
  headings: readonly HeadingLevel[];
@@ -10,6 +10,17 @@ export type BodyProfile = Readonly<{
10
10
  linkRels?: readonly string[];
11
11
  /** 装飾・リンク・インラインのコンポーネントを使用できない場所 */
12
12
  plain?: readonly ('heading' | 'tableHead')[];
13
+ /** リンクを別のタブで開く指定(既定は不可) */
14
+ linkTargets?: true;
15
+ /** 文字の見た目(色・背景・大きさは名前の宣言、項目がないものは使用不可、styles は名前のついた装飾の宣言) */
16
+ textStyle?: Readonly<{
17
+ color?: readonly NamedValue[];
18
+ background?: readonly NamedValue[];
19
+ size?: readonly NamedValue[];
20
+ styles?: readonly NamedStyle[];
21
+ }>;
22
+ /** 段落と見出しの揃え */
23
+ align?: true;
13
24
  list?: Readonly<{
14
25
  ordered: boolean;
15
26
  nested: boolean;
@@ -19,28 +30,49 @@ export type BodyProfile = Readonly<{
19
30
  multiple: boolean;
20
31
  cite: boolean;
21
32
  }>;
22
- /** コード(componentLangs はコンポーネントで書く言語とコンポーネントの名前、例: mermaid は図、その言語のフェンスだけのコードは不可) */
33
+ /** コード(filename はファイル名、componentLangs はコンポーネントで書く言語とコンポーネントの名前、例: mermaid は図、その言語のフェンスだけのコードは不可) */
23
34
  code?: Readonly<{
24
35
  caption: boolean;
36
+ filename?: boolean;
25
37
  componentLangs?: Readonly<Record<string, string>>;
26
38
  }>;
39
+ /** 画像(link は画像のリンク、align は横の位置) */
27
40
  image?: Readonly<{
28
41
  variants?: readonly string[];
42
+ link?: boolean;
43
+ align?: boolean;
29
44
  }>;
45
+ /** 表(cells は属性つきのセル: 見出しのセル・結合・揃え・複数の段落) */
30
46
  table?: Readonly<{
31
47
  head: 'required' | 'optional';
32
48
  caption: 'required' | 'optional' | 'none';
49
+ cells?: boolean;
33
50
  }>;
51
+ /** ファイル(実体の参照とリンクの文字) */
52
+ file?: true;
34
53
  divider?: true;
35
54
  html?: true;
36
55
  /** 使用できるコンポーネントの名前(ブロック・インライン) */
37
56
  components: readonly string[];
38
57
  }>;
58
+ /** 名前のついた値の宣言(label は管理画面の表示名、value はサイトが対応を持たないときの既定の CSS の値) */
59
+ export type NamedValue = Readonly<{
60
+ name: string;
61
+ label: string;
62
+ value?: string;
63
+ }>;
64
+ /** 名前のついた装飾の宣言(見せ方はサイトが名前で決める) */
65
+ export type NamedStyle = Readonly<{
66
+ name: string;
67
+ label: string;
68
+ }>;
39
69
  /** 本文の設定に合わない箇所(index は本文のブロックの位置、0から) */
40
70
  type BodyProblem = Readonly<{
41
71
  index: number;
42
72
  message: string;
43
73
  }>;
74
+ /** 元の CSS の値として描画してよいか(kind は色か大きさ) */
75
+ export declare const isSafeCssValue: (kind: "color" | "background" | "size", value: string) => boolean;
44
76
  /** 本文を設定で検査(合わない箇所をすべて返却) */
45
77
  export declare function checkBody<M>(body: Body<M>, profile: BodyProfile): BodyProblem[];
46
78
  /** 書き手と AI への Markdown の説明(使用できるものを記法の例つきで列挙し、使用できないものは末尾) */
@@ -0,0 +1,29 @@
1
+ import { type Body, type ComponentBlock } from './types.js';
2
+ /** 画像の描画の値 */
3
+ export type RenderedImage = Readonly<{
4
+ url: string;
5
+ width?: number;
6
+ height?: number;
7
+ alt?: string;
8
+ }>;
9
+ /** 描画の設定 */
10
+ export type RenderOptions<M> = Readonly<{
11
+ /** 画像の URL と寸法(null は描画しない) */
12
+ image(media: M): RenderedImage | null;
13
+ /** ファイルの URL(なし・null は描画しない) */
14
+ file?(media: M): Readonly<{
15
+ url: string;
16
+ }> | null;
17
+ /** 名前に対応する CSS の値(undefined は元の値) */
18
+ named?(kind: 'color' | 'background' | 'size', name: string): string | undefined;
19
+ /** 名前のついた装飾の class */
20
+ styleClass?(name: string): string;
21
+ /** コンポーネントの描画(null は既定の描画) */
22
+ component?(block: ComponentBlock, render: (body: Body<M>) => string): string | null;
23
+ /** インラインのコンポーネントの描画(null は中の文字) */
24
+ inlineComponent?(component: string, props: Readonly<Record<string, string>>, text: string): string | null;
25
+ /** HTML のブロックを生のまま描画 */
26
+ allowHtml?: boolean;
27
+ }>;
28
+ /** 本文を HTML に */
29
+ export declare function bodyToHtml<M>(body: Body<M>, options: RenderOptions<M>): string;
@@ -1,16 +1,30 @@
1
- /** インラインの装飾(strong・em・code・s・mark・sup・sub) */
2
- export type Mark = 'strong' | 'em' | 'code' | 'strike' | 'mark' | 'sup' | 'sub';
3
- /** リンク(HTML の a の href・title・rel) */
1
+ /** インラインの装飾(strong・em・code・s・mark・sup・sub・u) */
2
+ export type Mark = 'strong' | 'em' | 'code' | 'strike' | 'mark' | 'sup' | 'sub' | 'underline';
3
+ /** リンク(HTML の a の href・title・rel、target は別のタブ) */
4
4
  export type Link = Readonly<{
5
5
  href: string;
6
6
  title?: string;
7
7
  rel?: string;
8
+ target?: '_blank';
8
9
  }>;
9
- /** 装飾・リンクつきの文字列 */
10
+ /** 名前のついた値(name は本文の設定で宣言した名前、value は元の CSS の値、どちらか1つ以上) */
11
+ export type Named = Readonly<{
12
+ name?: string;
13
+ value?: string;
14
+ }>;
15
+ /** 文字の見た目(styles は本文の設定で宣言した名前のついた装飾、例: microCMS のカスタム class) */
16
+ export type TextStyle = Readonly<{
17
+ color?: Named;
18
+ background?: Named;
19
+ size?: Named;
20
+ styles?: readonly string[];
21
+ }>;
22
+ /** 装飾・リンク・見た目つきの文字列 */
10
23
  export type Text = Readonly<{
11
24
  text: string;
12
25
  marks?: readonly Mark[];
13
26
  link?: Link;
27
+ style?: TextStyle;
14
28
  }>;
15
29
  /** インラインのコンポーネント(例: 注、属性は文字列だけ、text は中の文字) */
16
30
  export type InlineComponent = Readonly<{
@@ -24,6 +38,20 @@ export type Inline = string | Text | InlineComponent;
24
38
  export type Rich = readonly Inline[];
25
39
  /** 見出しの段階 */
26
40
  export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
41
+ /** 揃え(段落・見出し・表のセル) */
42
+ export type Align = 'left' | 'center' | 'right' | 'justify';
43
+ /** 画像の横の位置 */
44
+ export type ImageAlign = 'left' | 'center' | 'right';
45
+ /** 属性つきの表のセル(content はセルの中の段落、header は見出しのセル、colspan・rowspan は結合) */
46
+ export type TableCell = Readonly<{
47
+ content: readonly Rich[];
48
+ header?: boolean;
49
+ colspan?: number;
50
+ rowspan?: number;
51
+ align?: Align;
52
+ }>;
53
+ /** 表のセル(断片の列だけのセルか、属性つきのセル) */
54
+ export type Cell = Rich | TableCell;
27
55
  /** リストの1項目(children は入れ子のリスト) */
28
56
  export type ListItem = Readonly<{
29
57
  content: Rich;
@@ -40,12 +68,14 @@ export type List = Readonly<{
40
68
  export type StandardBlock<M> = Readonly<{
41
69
  type: 'paragraph';
42
70
  content: Rich;
71
+ align?: Align;
43
72
  variant?: string;
44
73
  }> | Readonly<{
45
74
  type: 'heading';
46
75
  level: HeadingLevel;
47
76
  content: Rich;
48
77
  id?: string;
78
+ align?: Align;
49
79
  variant?: string;
50
80
  }> | List | Readonly<{
51
81
  type: 'quote';
@@ -60,19 +90,26 @@ export type StandardBlock<M> = Readonly<{
60
90
  code: string;
61
91
  lang?: string;
62
92
  caption?: string;
93
+ filename?: string;
63
94
  variant?: string;
64
95
  }> | Readonly<{
65
96
  type: 'image';
66
97
  media: M;
67
98
  alt?: string;
68
99
  caption?: string;
100
+ link?: Link;
101
+ align?: ImageAlign;
69
102
  variant?: string;
70
103
  }> | Readonly<{
71
104
  type: 'table';
72
105
  head?: readonly Rich[];
73
- rows: readonly (readonly Rich[])[];
106
+ rows: readonly (readonly Cell[])[];
74
107
  caption?: string;
75
108
  variant?: string;
109
+ }> | Readonly<{
110
+ type: 'file';
111
+ media: M;
112
+ label?: string;
76
113
  }> | Readonly<{
77
114
  type: 'divider';
78
115
  }> | Readonly<{
@@ -89,3 +126,7 @@ export type ComponentBlock = Readonly<{
89
126
  export type Block<M> = StandardBlock<M> | ComponentBlock;
90
127
  /** 本文 */
91
128
  export type Body<M> = readonly Block<M>[];
129
+ /** 属性つきのセルか */
130
+ export declare const isTableCell: (cell: Cell) => cell is TableCell;
131
+ /** セルの中の段落(断片の列だけのセルは1段落) */
132
+ export declare const cellParagraphs: (cell: Cell) => readonly Rich[];
@@ -1,7 +1,29 @@
1
+ /** 鍵の種類 */
2
+ export declare const API_KEY_KINDS: readonly ["server"];
3
+ /** 鍵の種類 */
4
+ export type ApiKeyKind = (typeof API_KEY_KINDS)[number];
5
+ /** 鍵の操作 */
6
+ export declare const API_KEY_OPERATIONS: readonly ["read", "read_drafts", "create", "update", "publish", "delete"];
7
+ /** 鍵の操作 */
8
+ export type ApiKeyOperation = (typeof API_KEY_OPERATIONS)[number];
9
+ /** 書き込みの操作(書き込みの API(#500)まで画面で選べない) */
10
+ export declare const API_KEY_WRITE_OPERATIONS: readonly ["create", "update", "publish", "delete"];
11
+ /** 権限のキーで、すべての型(後から足す型を含む) */
12
+ export declare const API_KEY_ALL_TYPES = "*";
13
+ /** 鍵の権限(キーは型の id か API_KEY_ALL_TYPES) */
14
+ export type ApiKeyPermissions = Readonly<Record<string, readonly ApiKeyOperation[]>>;
15
+ /** 権限を省略した鍵と既存の鍵の権限 */
16
+ export declare const DEFAULT_API_KEY_PERMISSIONS: {
17
+ readonly "*": readonly ["read"];
18
+ };
19
+ /** 鍵の権限が型の操作を許すか(read は read_drafts でも満たす) */
20
+ export declare function apiKeyAllows(permissions: ApiKeyPermissions, type: string, operation: ApiKeyOperation): boolean;
1
21
  /** 一覧に表示する API の鍵(tail は鍵の末尾、revokedAt は失効の日時で有効なら null) */
2
22
  export type ApiKey = Readonly<{
3
23
  id: string;
4
24
  label: string;
25
+ kind: ApiKeyKind;
26
+ permissions: ApiKeyPermissions;
5
27
  tail: string;
6
28
  createdAt: string;
7
29
  revokedAt: string | null;
@@ -11,3 +33,8 @@ export declare const API_KEY_RATE: {
11
33
  readonly limit: 600;
12
34
  readonly period: 60;
13
35
  };
36
+ /** 公開のエンドポイントで鍵や媒体の照合に失敗した要求の、接続元の IP ごとの上限(period 秒で limit 回、超えた IP は照合の前に429、platform#45) */
37
+ export declare const PUBLIC_FAILURE_RATE: {
38
+ readonly limit: 30;
39
+ readonly period: 60;
40
+ };
@@ -20,6 +20,7 @@ export declare const ERROR_STATUS: {
20
20
  readonly invalid_api_key: 401;
21
21
  readonly account_not_found: 403;
22
22
  readonly invalid_preview_token: 403;
23
+ readonly api_key_forbidden: 403;
23
24
  readonly mcp_not_allowed: 403;
24
25
  readonly admin_only: 403;
25
26
  readonly forbidden: 403;
@@ -62,7 +63,7 @@ export type ErrorTarget = 'request' | 'role' | 'media' | 'entry' | 'asset' | 'mc
62
63
  /** エラーの補足の値(コードごとに使用する項目だけ) */
63
64
  export type ErrorParams = {
64
65
  /**
65
- * 対象: not_found・own_only・forbidden・not_visible・no_linked_entry・editorial_only
66
+ * 対象: not_found・own_only・forbidden・not_visible・no_linked_entry・editorial_only・api_key_forbidden
66
67
  * - 文字列: モジュールの対象・コンテンツタイプの id
67
68
  */
68
69
  target?: ErrorTarget | (string & {});
@@ -71,9 +72,9 @@ export type ErrorParams = {
71
72
  /** 使用先: in_use */
72
73
  by?: 'uses' | 'places' | 'outputs' | 'entry' | 'writers' | 'members' | 'accounts';
73
74
  /** 状態の不一致の理由: invalid_state */
74
- reason?: 'not_draft' | 'no_draft' | 'not_in_review' | 'request_handled' | 'deleted' | 'not_deleted' | 'schema_confirm' | 'no_publishing' | 'no_review';
75
+ reason?: 'not_draft' | 'no_draft' | 'not_in_review' | 'request_handled' | 'deleted' | 'not_deleted' | 'schema_confirm' | 'no_publishing' | 'no_review' | 'not_published';
75
76
  /** 取得の対象: upstream_failed */
76
- resource?: 'page' | 'image' | 'webhook';
77
+ resource?: 'page' | 'image' | 'webhook' | 'microcms';
77
78
  /**
78
79
  * 名前
79
80
  * - no_linked_entry: 結び付くコンテンツタイプの表示名、account_not_found: メールアドレス