@ryusenkei/maezae 0.16.0 → 0.18.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.
Files changed (35) hide show
  1. package/README.md +44 -0
  2. package/dist/body.js +312 -10
  3. package/dist/{declared-9_jI3JXH.js → declared-Qo-mWnDd.js} +154 -15
  4. package/dist/delivery.js +43 -2
  5. package/dist/model.js +4 -2
  6. package/dist/types/analytics/src/index.d.ts +1 -0
  7. package/dist/types/analytics/src/shared.d.ts +21 -0
  8. package/dist/types/body/src/derive.d.ts +1 -1
  9. package/dist/types/body/src/embed.d.ts +14 -0
  10. package/dist/types/body/src/html.d.ts +31 -0
  11. package/dist/types/body/src/index.d.ts +5 -2
  12. package/dist/types/body/src/library.d.ts +19 -0
  13. package/dist/types/body/src/markdown.d.ts +1 -1
  14. package/dist/types/body/src/normalize.d.ts +1 -1
  15. package/dist/types/body/src/profile.d.ts +34 -2
  16. package/dist/types/body/src/render.d.ts +29 -0
  17. package/dist/types/body/src/types.d.ts +46 -5
  18. package/dist/types/cms-contract/src/apiKeys.d.ts +27 -0
  19. package/dist/types/cms-contract/src/assets.d.ts +2 -0
  20. package/dist/types/cms-contract/src/errors.d.ts +5 -3
  21. package/dist/types/cms-contract/src/import.d.ts +33 -1
  22. package/dist/types/cms-contract/src/index.d.ts +6 -5
  23. package/dist/types/cms-contract/src/labels.d.ts +5 -0
  24. package/dist/types/cms-contract/src/roles.d.ts +3 -1
  25. package/dist/types/cms-contract/src/views.d.ts +2 -2
  26. package/dist/types/cms-contract/src/workflow.d.ts +49 -0
  27. package/dist/types/model/src/asset.d.ts +8 -2
  28. package/dist/types/model/src/index.d.ts +2 -2
  29. package/dist/types/model/src/mediaUrl.d.ts +20 -2
  30. package/dist/types/model/src/model.d.ts +12 -4
  31. package/dist/types/sdk/src/body.d.ts +2 -2
  32. package/dist/types/sdk/src/delivery/index.d.ts +1 -1
  33. package/dist/types/sdk/src/delivery/values.d.ts +6 -2
  34. package/dist/types/sdk/src/delivery.d.ts +1 -1
  35. package/package.json +1 -1
@@ -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/delivery.js CHANGED
@@ -250,12 +250,22 @@ const EXTENSIONS = {
250
250
  "image/webp": "webp",
251
251
  "image/gif": "gif",
252
252
  "video/mp4": "mp4",
253
- "video/webm": "webm"
253
+ "video/webm": "webm",
254
+ "application/pdf": "pdf",
255
+ "application/zip": "zip",
256
+ "text/plain": "txt",
257
+ "text/csv": "csv",
258
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
259
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
260
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx"
254
261
  };
262
+ /** 形式の実体の種類 */
263
+ const mediaKindOf = (mimeType) => mimeType.startsWith("image/") ? "image" : mimeType.startsWith("video/") ? "video" : "file";
255
264
  /** 拡張子の形式(配信しない拡張子は null) */
256
265
  const mediaMimeOf = (ext) => Object.entries(EXTENSIONS).find(([, e]) => e === ext)?.[0] ?? null;
257
266
  const IMAGE_PATH = /^\/images\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+)-(\d+)x(\d+)\.([a-z0-9]+)$/;
258
267
  const VIDEO_PATH = /^\/videos\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+)\.([a-z0-9]+)$/;
268
+ const FILE_PATH = /^\/files\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_-]+)\.([a-z0-9]+)$/;
259
269
  /** 配信 URL のパスを解析(形が違えば null) */
260
270
  function parseMediaPath(pathname) {
261
271
  const image = IMAGE_PATH.exec(pathname);
@@ -280,8 +290,39 @@ function parseMediaPath(pathname) {
280
290
  ext
281
291
  } : null;
282
292
  }
293
+ const file = FILE_PATH.exec(pathname);
294
+ if (file) {
295
+ const [, project, id, ext] = file;
296
+ const mime = mediaMimeOf(ext);
297
+ return mime && mediaKindOf(mime) === "file" ? {
298
+ kind: "file",
299
+ project,
300
+ id,
301
+ ext
302
+ } : null;
303
+ }
283
304
  return null;
284
305
  }
306
+ const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
307
+ /** 署名(HMAC-SHA256、対象はパスと期限、変換のクエリは含めない) */
308
+ async function signatureOf(secret, pathname, exp) {
309
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
310
+ name: "HMAC",
311
+ hash: "SHA-256"
312
+ }, false, ["sign"]);
313
+ return hex(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${pathname}:${exp}`)));
314
+ }
315
+ /**
316
+ * 非公開のメディアの署名つきの URL(exp と sig のクエリを付ける、ほかのクエリは残す)
317
+ * - secret: 媒体の非公開のメディアの署名の鍵、expiresInSec: 期限までの秒、now: 今の UNIX ミリ秒
318
+ */
319
+ async function signMediaUrl(url, secret, options = {}) {
320
+ const u = new URL(url);
321
+ const exp = Math.floor((options.now ?? Date.now()) / 1e3) + (options.expiresInSec ?? 3600);
322
+ u.searchParams.set("exp", String(exp));
323
+ u.searchParams.set("sig", await signatureOf(secret, u.pathname, exp));
324
+ return u.toString();
325
+ }
285
326
  /** 幅・高さの上限(画素) */
286
327
  const IMAGE_SIZE_MAX = 4096;
287
328
  const FITS = [
@@ -432,4 +473,4 @@ function createDeliveryClock(timeZone) {
432
473
  })
433
474
  };
434
475
  }
435
- export { DELIVERY_PATH, DeliveryError, cachedDelivery, createDeliveryClient, createDeliveryClock, deliveredImageUrl, fieldOf, imageParamsOf, limitOf, listParams, mediaOf, memoryDeliveryCache, optionLabels, pagePath, parseMediaPath, readChanges, subPath, withImageParams };
476
+ export { DELIVERY_PATH, DeliveryError, cachedDelivery, createDeliveryClient, createDeliveryClock, deliveredImageUrl, fieldOf, imageParamsOf, limitOf, listParams, mediaOf, memoryDeliveryCache, optionLabels, pagePath, parseMediaPath, readChanges, signMediaUrl, subPath, withImageParams };
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
@@ -344,11 +344,13 @@ const fieldUnion = z.discriminatedUnion("type", [
344
344
  type: z.literal("media"),
345
345
  accept: z.array(z.enum(["image", "video"])).optional(),
346
346
  multiple: z.boolean().optional(),
347
- frames: z.array(frame).optional()
347
+ frames: z.array(frame).optional(),
348
+ visibility: z.literal("private").optional()
348
349
  }),
349
350
  z.object({
350
351
  ...base,
351
352
  type: z.literal("body"),
353
+ visibility: z.literal("private").optional(),
352
354
  profile: z.object({
353
355
  headings: z.array(z.number()),
354
356
  marks: z.array(z.string()),
@@ -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;