@ryhrm-gz/xincodo-lib
Version:
Utilities for working with Xincodo body documents.
280 lines (205 loc) • 8.96 kB
Markdown
# xincodo-lib
Xincodo body documents を TypeScript で構築、検証、移行、走査、編集、テキスト抽出するためのユーティリティです。
`Body` は `{ version: 1, content: BodyBlock[] }` というシンプルな構造で、本文ブロック、リッチテキスト、画像、テーブル、リンクなどを型付きで扱えます。
## Installation
```bash
pnpm add @ryhrm-gz/xincodo-lib
```
このパッケージは ESM として公開され、型定義は同梱されています。
If you use an AI agent, run `npx @tanstack/intent@latest install`.
## Quick Start
```ts
import {
bulletedList,
callout,
createBody,
estimateReadingTime,
heading,
listItem,
paragraph,
text,
toPlainText,
} from "@ryhrm-gz/xincodo-lib";
const body = createBody([
heading(1, "Getting started", { id: "getting-started" }),
paragraph(
"Create structured content with ",
text("typed helpers", { annotations: { bold: true } }),
".",
),
bulletedList([listItem("Build documents"), listItem("Validate before saving")]),
callout("Remember to add image alt text.", {
icon: { type: "emoji", emoji: "!" },
color: "yellow",
}),
]);
const plainText = toPlainText(body);
const readingTime = estimateReadingTime(body);
console.log(plainText);
console.log(readingTime.minutes);
```
## Document Model
`Body` は現在 `version: 1` のドキュメントです。本文は次の `BodyBlock` で表現します。
- `text`: 段落。`children` に子ブロックを持てます。
- `heading_1` から `heading_4`: 見出し。
- `bulleted_list`, `numbered_list`, `toggle_list`: リスト。各 item はリッチテキストと子ブロックを持てます。
- `callout`, `quote`: 補足や引用。`children` に子ブロックを持てます。
- `table`: 行とセルを持つテーブル。
- `divider`: 区切り線。
- `page_link`: Xincodo 内ページ ID または URL への参照。
- `image`, `gallery`: 単一画像または画像ギャラリー。
リッチテキストは `text()` と `lineBreak()` で作成できます。`text()` には `bold`, `italic`, `underline`, `strikethrough`, `color`, `backgroundColor`, `link` などの注釈を付けられます。
```ts
import { lineBreak, paragraph, text } from "@ryhrm-gz/xincodo-lib";
const block = paragraph(
text("Docs", {
annotations: { underline: true },
link: { href: "https://example.com/docs", title: "Documentation" },
}),
lineBreak(),
"Read more",
);
```
## Usage Examples
### Validate Unknown Input
外部入力や保存済み JSON は `parseBody()` で安全に検証できます。
```ts
import { parseBody } from "@ryhrm-gz/xincodo-lib";
const result = parseBody(JSON.parse(serializedBody));
if (!result.success) {
console.error(result.issues);
throw new Error("Invalid Xincodo body");
}
const body = result.output;
```
将来のバージョン移行を見越した読み込みには `safeMigrateBody()` または `migrateBody()` を使います。
```ts
import { safeMigrateBody } from "@ryhrm-gz/xincodo-lib";
const result = safeMigrateBody(rawBody);
if (result.success) {
console.log(result.output);
} else {
console.error(result.issue.message);
}
```
### Lint Before Saving
`lintBody()` は重複 ID、不正 URL、空のテーブル、見出しレベルのジャンプ、画像 alt 欠落などを検出します。
```ts
import { lintBody } from "@ryhrm-gz/xincodo-lib";
const report = lintBody(body, {
maxDepth: 3,
requireImageAlt: true,
validateUrls: true,
});
if (!report.valid) {
console.log(report.errors);
}
```
### Extract Render Metadata
目次、抜粋、読了時間、画像、リンクなど、レンダリング前に必要になりやすい情報を抽出できます。
```ts
import {
collectImageSources,
collectLinks,
createExcerpt,
estimateReadingTime,
extractHeadingAnchors,
} from "@ryhrm-gz/xincodo-lib";
const headings = extractHeadingAnchors(body);
const excerpt = createExcerpt(body, { maxLength: 120, preserveWords: true });
const readingTime = estimateReadingTime(body);
const images = collectImageSources(body);
const links = collectLinks(body);
```
### Traverse And Edit Immutably
走査や編集ヘルパーは元の `Body` を変更せず、新しい `Body` を返します。
```ts
import {
findBlockPathById,
insertBlockAtPath,
paragraph,
richText,
updateRichTextAtPath,
} from "@ryhrm-gz/xincodo-lib";
const introPath = findBlockPathById(body, "intro");
const withNote = introPath
? insertBlockAtPath(body, introPath, paragraph("Inserted after intro"), {
position: "after",
})
: body;
const updatedIntro = introPath
? updateRichTextAtPath(body, [...introPath, { key: "richText" }], () => richText("Updated intro"))
: body;
```
## Public API
すべての API はパッケージルートから import できます。
```ts
import { createBody, parseBody, type Body } from "@ryhrm-gz/xincodo-lib";
```
### Builders
- `createBody(content)`: `Body` を作成します。
- `text(value, options)`, `lineBreak()`, `richText(...content)`: リッチテキストを作成します。
- `paragraph()`, `heading()`, `callout()`, `quote()`: テキスト系ブロックを作成します。
- `listItem()`, `toggleListItem()`, `bulletedList()`, `numberedList()`, `toggleList()`: リストを作成します。
- `tableCell()`, `tableRow()`, `table()`: テーブルを作成します。
- `divider()`, `pageLink()`, `image()`, `galleryImage()`, `gallery()`: その他のブロックを作成します。
### Validation And Migration
- `parseBody(input)`: `bodySchema` で `unknown` を検証し、Valibot の safe parse result を返します。
- `bodySchema`, `bodyBlockSchema`, `bodyRichTextSchema` など: Valibot schema を直接利用できます。
- `safeMigrateBody(input)`: 移行結果を success/failure の union として返します。
- `migrateBody(input)`: 移行できない場合は `BodyMigrationError` を投げます。
### Lint
- `lintBody(input, options)`: 構造や編集上の問題を `BodyLintReport` として返します。
- `BodyLintIssue`, `BodyLintCode`, `BodyLintSeverity`: lint 結果の型です。
### Traversal And Editing
- `walkBody(input, visitor)`: block, richText, inline, listItem, tableCell, galleryImage を訪問します。
- `findBlock(input, predicate)`, `filterBlocks(input, predicate)`: 条件に合うブロックを検索します。
- `mapBody(input, mapper)`: ブロックを再帰的に変換します。
- `findBlockPathById(input, id)`, `getBlockAtPath(input, path)`: パスでブロックを参照します。
- `insertBlockAtRoot()`, `insertBlockAtPath()`, `updateBlockAtPath()`, `replaceBlockAtPath()`, `removeBlockAtPath()`, `moveBlock()`: ブロックを immutable に編集します。
- `updateRichTextAtPath()`: 指定パスのリッチテキストを immutable に編集します。
- 編集に失敗した場合は `BodyEditError` を投げます。
### Text And Rendering Helpers
- `toPlainText(input)`: `Body`, block 配列, block, rich text をプレーンテキストに変換します。
- `richTextToPlainText(richText)`: リッチテキストだけを文字列化します。
- `extractHeadings(input)`: 見出しの level, text, block, path を抽出します。
- `extractHeadingAnchors(input, options)`: 目次やアンカー用の一意な ID と href を作成します。
- `slugifyHeading(value)`: 見出し文字列を slug に変換します。
- `createExcerpt(input, options)`: 抜粋を作成します。
- `estimateReadingTime(input, options)`: 読了時間、単語数、文字数を推定します。
- `collectImageSources(input)`: image, gallery image, callout icon の画像参照を集めます。
- `collectLinks(input)`: text link と page link を集めます。
### Constants And Types
- `BODY_VERSION`: 現在の body version です。
- `BODY_BLOCK_TYPES`, `TEXT_COLORS`, `HEADING_LEVELS`: サポートされる値の一覧です。
- `Body`, `BodyBlock`, `BodyRichText`, `BodyBlockType`, `TextAnnotations`, `TextLink`, `ImageSource`, `PageReference` などの公開型を利用できます。
## Development
Install dependencies:
```bash
vp install
```
Run format, lint, type check, and tests:
```bash
vp check
vp test
```
Build the library:
```bash
vp pack
```
## Release
Releases are published from a clean local `main` branch.
Prerequisites:
- Authenticate the GitHub CLI with `gh auth login`.
- Authenticate npm with `npm login`.
Run a release:
```bash
vp run release
```
To choose the next version explicitly, pass a bumpp version argument:
```bash
vp run release -- patch
vp run release -- minor
vp run release -- major
```
The release script checks that local `main` matches `origin/main`, runs checks and tests, bumps `package.json`, creates and pushes the release commit and `vX.Y.Z` tag, publishes GitHub release notes, then publishes the package to npm.