@cosense-toolbox/parser 0.1.0-beta.4 → 0.1.0-beta.6
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 +21 -1
- package/dist/compile.d.cts +1 -1
- package/dist/compile.d.mts +1 -1
- package/dist/extensions.d.cts +2 -2
- package/dist/extensions.d.mts +2 -2
- package/dist/html.cjs +14 -2
- package/dist/html.cjs.map +1 -1
- package/dist/html.d.cts +9 -3
- package/dist/html.d.cts.map +1 -1
- package/dist/html.d.mts +9 -3
- package/dist/html.d.mts.map +1 -1
- package/dist/html.mjs +14 -2
- package/dist/html.mjs.map +1 -1
- package/dist/{index-_o1STNhP.d.cts → index-BU-dd6fK.d.cts} +6 -4
- package/dist/index-BU-dd6fK.d.cts.map +1 -0
- package/dist/{index-CrIlOW1k.d.mts → index-DQ3C3Rsq.d.mts} +6 -4
- package/dist/index-DQ3C3Rsq.d.mts.map +1 -0
- package/dist/index.cjs +66 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +66 -32
- package/dist/index.mjs.map +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.mts +1 -1
- package/dist/{types-CdiHju1P.d.mts → types-BSP109Vm.d.cts} +2 -2
- package/dist/{types-CdiHju1P.d.mts.map → types-BSP109Vm.d.cts.map} +1 -1
- package/dist/{types-_-ZAyNkz.d.cts → types-BqkfzhyO.d.mts} +2 -2
- package/dist/{types-_-ZAyNkz.d.cts.map → types-BqkfzhyO.d.mts.map} +1 -1
- package/dist/{types-gWMrsXPX.d.cts → types-D2Q7NiCb.d.cts} +9 -2
- package/dist/types-D2Q7NiCb.d.cts.map +1 -0
- package/dist/{types-gWMrsXPX.d.mts → types-D2Q7NiCb.d.mts} +9 -2
- package/dist/types-D2Q7NiCb.d.mts.map +1 -0
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.mts +1 -1
- package/package.json +1 -1
- package/dist/index-CrIlOW1k.d.mts.map +0 -1
- package/dist/index-_o1STNhP.d.cts.map +0 -1
- package/dist/types-gWMrsXPX.d.cts.map +0 -1
- package/dist/types-gWMrsXPX.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -12,6 +12,23 @@ Cosense (旧 Scrapbox) の記法を、位置情報つきの AST に変換する
|
|
|
12
12
|
|
|
13
13
|
> **beta**:公開 API はまだ変わりうる。安定するまではバージョンを固定して使うほうが安全。
|
|
14
14
|
|
|
15
|
+
### 0.1.0-beta.6 の変更
|
|
16
|
+
|
|
17
|
+
- インラインコードが始まる括弧を、記法として読まないようにした。Cosense Web はコードを括弧より先に読むので、
|
|
18
|
+
``[* 太字の `code` です]`` は装飾にならず、括弧はそのままの文字、`` `code` `` はインラインコードになる。
|
|
19
|
+
数式 (`[$ …]`) も同じで、` [$ a`] `` は数式にならない。閉じないバッククォートは括弧を妨げない。
|
|
20
|
+
- コマンドの行を Cosense Web に合わせた。字下げの後が `$` か `%` と空白で、その後に何かある行だけがコマンドになる
|
|
21
|
+
(`monospace: true`)。コマンドの行は記法を読まず、`children` は書いたままの文字の `text` 1 つになる。
|
|
22
|
+
`$aa` のように空白が無い行と、引用の行 (`> $ x`) はコマンドにならない。
|
|
23
|
+
`toHtml` / `toHast` は、Cosense Web と同じくコマンドの行を記号・空白・コマンドの要素に分けて出す
|
|
24
|
+
(`<span class="prefix">$</span><span class="space"> </span><span class="command">ls</span>`)。
|
|
25
|
+
class 名は `classNames` の `commandPrefix` / `commandSpace` / `command` で変えられる。
|
|
26
|
+
|
|
27
|
+
### 0.1.0-beta.5 の変更
|
|
28
|
+
|
|
29
|
+
- タイトル行の記法を読まないようにした。Cosense Web と同じく、`[x]` も `#tag` も書いたままの文字になる。
|
|
30
|
+
`TitleBlock` の形は変わらず、`children` が書いたままの文字の `text` 1 つになる。
|
|
31
|
+
|
|
15
32
|
### 0.1.0-beta.3 の変更
|
|
16
33
|
|
|
17
34
|
- Cosense の文字装飾の記号 `!"#%&'()*+,-./{|}<>_~=` からなる並びを、**拡張なしで**すべて装飾として読むようにした。
|
|
@@ -41,7 +58,7 @@ beta.0 から上げるときは次の 2 点に注意。
|
|
|
41
58
|
## インストール
|
|
42
59
|
|
|
43
60
|
```sh
|
|
44
|
-
npm i @cosense-toolbox/parser
|
|
61
|
+
npm i @cosense-toolbox/parser
|
|
45
62
|
```
|
|
46
63
|
|
|
47
64
|
既定の見た目が要るなら [`@cosense-toolbox/style`](https://github.com/qaynam/cosense-toolbox/tree/main/packages/style) を別途入れる。
|
|
@@ -74,6 +91,9 @@ toHtml(page) // → '<div class="page"><h1 class="title">今日のメモ</h1>…
|
|
|
74
91
|
| `@cosense-toolbox/parser/extensions` | 記法を足す | `Extension` `InlineConstruct` `BracketRule` `tableCellNotation` |
|
|
75
92
|
| `@cosense-toolbox/parser/schema` | 外から来た値を検証する | `decodePage` |
|
|
76
93
|
|
|
94
|
+
`parse` はページ全体を読む。1 行目はタイトルで、Cosense Web と同じく記法を読まない。
|
|
95
|
+
記法を読みたい文字列がページでないなら、本文の 1 行は `parseLine`、文章の断片は `tokenizeInline` で読む。
|
|
96
|
+
|
|
77
97
|
各 API の詳細はドキュメントにある。
|
|
78
98
|
|
|
79
99
|
| ページ | 内容 |
|
package/dist/compile.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as NodeOfType, r as AnyNodeType, t as AnyNode } from "./types-
|
|
1
|
+
import { C as NodeOfType, r as AnyNodeType, t as AnyNode } from "./types-D2Q7NiCb.cjs";
|
|
2
2
|
//#region src/compile/create-compiler.d.ts
|
|
3
3
|
/** ハンドラの中から再帰的に変換するための入口。 */
|
|
4
4
|
interface CompileContext<Out> {
|
package/dist/compile.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as NodeOfType, r as AnyNodeType, t as AnyNode } from "./types-
|
|
1
|
+
import { C as NodeOfType, r as AnyNodeType, t as AnyNode } from "./types-D2Q7NiCb.mjs";
|
|
2
2
|
//#region src/compile/create-compiler.d.ts
|
|
3
3
|
/** ハンドラの中から再帰的に変換するための入口。 */
|
|
4
4
|
interface CompileContext<Out> {
|
package/dist/extensions.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-
|
|
2
|
-
import { a as InlineConstruct, i as Extension, n as BracketRuleContext, o as InlineContext, r as ConstructMatch, s as Origin, t as BracketRule } from "./types-
|
|
1
|
+
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-D2Q7NiCb.cjs";
|
|
2
|
+
import { a as InlineConstruct, i as Extension, n as BracketRuleContext, o as InlineContext, r as ConstructMatch, s as Origin, t as BracketRule } from "./types-BSP109Vm.cjs";
|
|
3
3
|
//#region src/extensions/table-cell-notation.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
package/dist/extensions.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-
|
|
2
|
-
import { a as InlineConstruct, i as Extension, n as BracketRuleContext, o as InlineContext, r as ConstructMatch, s as Origin, t as BracketRule } from "./types-
|
|
1
|
+
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-D2Q7NiCb.mjs";
|
|
2
|
+
import { a as InlineConstruct, i as Extension, n as BracketRuleContext, o as InlineContext, r as ConstructMatch, s as Origin, t as BracketRule } from "./types-BqkfzhyO.mjs";
|
|
3
3
|
//#region src/extensions/table-cell-notation.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
package/dist/html.cjs
CHANGED
|
@@ -37,6 +37,9 @@ const defaultClassNames = {
|
|
|
37
37
|
line: "line",
|
|
38
38
|
quote: "quote",
|
|
39
39
|
monospace: "monospace",
|
|
40
|
+
commandPrefix: "prefix",
|
|
41
|
+
commandSpace: "space",
|
|
42
|
+
command: "command",
|
|
40
43
|
codeBlock: "code-block",
|
|
41
44
|
codeStart: "code-start",
|
|
42
45
|
codeFilename: "code-block-start",
|
|
@@ -127,6 +130,16 @@ const contentsOf = (result) => Array.isArray(result) ? asElementContents(result)
|
|
|
127
130
|
const isBlank = (node) => node.type === "text" && node.value.trim() === "";
|
|
128
131
|
/** 空白だけのテキストを除いた、ただ 1 つの子。 */
|
|
129
132
|
const onlyChildOf = (children) => (0, effect.pipe)(effect.Option.some(children.filter((child) => !isBlank(child))), effect.Option.filter((meaningful) => meaningful.length === 1), effect.Option.flatMapNullable((meaningful) => meaningful[0]));
|
|
133
|
+
/**
|
|
134
|
+
* コマンドの行 (`$ ls`) を、Cosense Web と同じく記号・空白・コマンドの 3 つの要素に分ける。
|
|
135
|
+
* 記号とコマンドを別に選べるので、記号を薄くしたり、コマンドだけをコピーさせたりできる。
|
|
136
|
+
* パーサーはコマンドの行を書いたままの文字 1 つにするので、そうでなければ (拡張が置き換えたなど) 分けない。
|
|
137
|
+
*/
|
|
138
|
+
const commandParts = (node, cls) => (0, effect.pipe)(effect.Option.liftPredicate(node.children, (children) => children.length === 1), effect.Option.flatMapNullable(([child]) => child), effect.Option.filter((child) => child.type === "text"), effect.Option.map(({ value }) => [
|
|
139
|
+
element("span", withClass(cls.commandPrefix), [text(value.slice(0, 1))]),
|
|
140
|
+
element("span", withClass(cls.commandSpace), [text(value.slice(1, 2))]),
|
|
141
|
+
element("span", withClass(cls.command), [text(value.slice(2))])
|
|
142
|
+
]));
|
|
130
143
|
const elementNamed = (tagName) => (node) => effect.Option.liftPredicate(node, (content) => content.type === "element" && content.tagName === tagName);
|
|
131
144
|
/** hast の決まりでは className は配列だが、shiki は class を文字列で付ける。どちらも読む。 */
|
|
132
145
|
const classesOf = (value) => effect.Match.value(value).pipe(effect.Match.when(Array.isArray, (names) => names.map(String)), effect.Match.when(effect.Match.string, classList), effect.Match.orElse(() => []));
|
|
@@ -206,8 +219,7 @@ const defaultHastHandlers = {
|
|
|
206
219
|
title: (node, ctx) => [element("h1", withClass(ctx.options.classNames.title), ctx.children(node))],
|
|
207
220
|
line: (node, ctx) => {
|
|
208
221
|
const cls = ctx.options.classNames;
|
|
209
|
-
const
|
|
210
|
-
const styled = node.monospace ? [element("code", withClass(cls.monospace), body)] : body;
|
|
222
|
+
const styled = node.monospace ? [element("code", withClass(cls.monospace), effect.Option.getOrElse(commandParts(node, cls), () => ctx.children(node)))] : ctx.children(node);
|
|
211
223
|
const quoted = node.quote ? [element("blockquote", withClass(cls.quote), styled)] : styled;
|
|
212
224
|
const inner = quoted.length === 0 ? [element("br", {})] : quoted;
|
|
213
225
|
const mark = ctx.options.showPads && node.indent > 0 ? [indentMark(node.indent, cls)] : [];
|
package/dist/html.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.cjs","names":["pipe","Option","Match","asImageSrc","childrenOf","hastToHtml","Match","pipe","Option","pipe","Option","Match"],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"sourcesContent":["/**\n * to-hast.ts — AST を hast (HTML の AST) にする公式コンパイラ。\n *\n * HTML の文字列 (`toHtml`)、JSX (cosense-x)、rehype のプラグインはすべてこの出力から作る。\n * 描画の規則をここ 1 か所に置き、形式ごとに実装が分かれてずれないようにするため。\n *\n * ノード型ごとのハンドラの集まりで、`handlers` に渡した型だけが差し替わる。\n * 既定のハンドラは `defaultHastHandlers` として公開し、オプションは `ctx.options` から読む。\n * 利用者が既定の出力を包むとき、オプションを渡し直さなくて済むようにするため。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { Element, ElementContent, Properties, Root, Text } from \"hast\"\n\nimport { childrenOf } from \"../ast\"\nimport { asImageSrc } from \"../core/image-url\"\nimport type {\n AnyNode,\n AnyNodeType,\n CodeBlock,\n Decoration,\n Hashtag,\n IconNode,\n InternalLink,\n NodeOfType,\n ProjectLink,\n} from \"../types\"\n\n// ---------------------------------------------------------------------------\n// URL の検査\n// ---------------------------------------------------------------------------\n\n/** `href` に入れると script が動くスキーム。`data:text/html` があるので data: も拒む。 */\nconst UNSAFE_HREF_RE = /^(?:javascript|vbscript|data):/\n\n/** `src` に入れると script が動くスキーム。`data:` 画像は正当な使い道があるので許す。 */\nconst UNSAFE_SRC_RE = /^(?:javascript|vbscript):/\n\n/**\n * スキームだけを見るために空白と制御文字を落とす。\n * ブラウザは途中にタブや改行が挟まった `javascript:` もスキームとして解釈するため。\n */\nconst schemeOf = (url: string): string => url.replace(/[\\s\\p{Cc}]/gu, \"\").toLowerCase()\n\nconst safeUrl = (url: string, unsafe: RegExp): string | null =>\n unsafe.test(schemeOf(url)) ? null : url\n\n/** `href` に入れて安全な URL だけを返す。script が動くスキームなら null。 */\nexport const safeHref = (url: string): string | null => safeUrl(url, UNSAFE_HREF_RE)\n\n/** `src` に入れて安全な URL だけを返す。script が動くスキームなら null。 */\nexport const safeSrc = (url: string): string | null => safeUrl(url, UNSAFE_SRC_RE)\n\n/** 空文字は「指定なし」とみなす。class 名や URL を空にして属性ごと消せるようにするため。 */\nconst nonEmpty = (value: string | null | undefined): Option.Option<string> =>\n pipe(\n Option.fromNullable(value),\n Option.filter((text) => text !== \"\"),\n )\n\n// ---------------------------------------------------------------------------\n// オプション\n// ---------------------------------------------------------------------------\n\n/** プロジェクト内のページを指すノード。Cosense Web ではアイコンもユーザーのページへのリンクになる。 */\nexport type PageRefNode = InternalLink | ProjectLink | Hashtag | IconNode\n\n/**\n * 出力する要素に付ける class 名。指定したキーだけが既定を上書きする。\n * 空文字にすると class 属性自体を出さない。\n */\nexport interface HtmlClassNames {\n readonly page?: string\n readonly title?: string\n readonly line?: string\n /** 引用行の `<blockquote>` */\n readonly quote?: string\n /** 等幅行の `<code>` */\n readonly monospace?: string\n /** コードブロックに属する行 (ヘッダ行と本体行の両方) */\n readonly codeBlock?: string\n /** ヘッダ行の `<code>` */\n readonly codeStart?: string\n /** ヘッダ行に出るファイル名 */\n readonly codeFilename?: string\n /** 本体行の `<code>` */\n readonly codeBody?: string\n /** ハイライタを通した本体に追加で付く class */\n readonly codeHighlight?: string\n readonly table?: string\n /** `showPads` のときだけ出る、インデントぶんの余白と中点をまとめる要素 */\n readonly indentMark?: string\n /** インデント 1 段ぶんの余白 */\n readonly pad?: string\n /** インデントの右端に置く中点 */\n readonly dot?: string\n readonly internalLink?: string\n readonly externalLink?: string\n readonly projectLink?: string\n readonly hashtag?: string\n readonly inlineCode?: string\n readonly image?: string\n readonly icon?: string\n readonly formula?: string\n readonly decoration?: string\n}\n\n/** 既定の class 名。接頭辞を持たないため、利用側の CSS と名前がぶつかりうる。 */\nexport const defaultClassNames: HtmlClassNames = {\n page: \"page\",\n title: \"title\",\n line: \"line\",\n quote: \"quote\",\n monospace: \"monospace\",\n codeBlock: \"code-block\",\n codeStart: \"code-start\",\n codeFilename: \"code-block-start\",\n codeBody: \"code-body\",\n codeHighlight: \"highlight\",\n table: \"table\",\n indentMark: \"indent-mark\",\n pad: \"pad\",\n dot: \"dot\",\n internalLink: \"link\",\n externalLink: \"link link-external\",\n projectLink: \"link link-project\",\n hashtag: \"hashtag\",\n inlineCode: \"code\",\n image: \"image\",\n icon: \"icon\",\n formula: \"formula\",\n decoration: \"decoration\",\n}\n\n/**\n * HTML の文字列をそのまま入れるノード。`toHtml` はそのまま埋め込む (hast-util-to-html の raw ノード)。\n * hast の型には無いので、ここで持つ。hast-util-to-html の型拡張に頼ると、\n * `@types/hast` の版が分かれたときに型が合わなくなるため。\n */\nexport interface RawNode {\n readonly type: \"raw\"\n readonly value: string\n}\n\n/** ハンドラやハイライタが返せるノード。hast の要素の中身か、raw ノード。 */\nexport type HastContent = ElementContent | RawNode\n\n/**\n * コードブロックの中身を色付けする。\n *\n * `language` はファイル名から推測した名前 (`codeLanguageOf`)。\n * null を返すと色付けせず、1 行ずつのまま出す。知らない言語のときに使う。\n *\n * shiki の `codeToHast` のように `pre > code` を返したときは、code の中身を使い、\n * pre の class と style (テーマの背景色や文字色) をコードブロックの code に移す。\n * 行の要素 (`div.line`) の中に置くので、pre そのものは出さない。\n */\nexport type HastHighlighter = (code: string, language: string) => Root | HastContent[] | null\n\n/**\n * 既定のハンドラの振る舞いを変えるオプション。\n *\n * `pageUrl` / `iconImageUrl` / `highlight` は、**記法に書かれていないので AST から導けない**\n * 情報を外から渡すためのもの。残りは出力の見た目を調整する。\n */\nexport interface HastRenderOptions {\n /**\n * ページを指す記法 (`[title]` / `[/proj/page]` / `#tag` / `[user.icon]`) の遷移先。\n * アイコンについてはリンク先だけを決め、画像は `iconImageUrl` が決める。\n *\n * `title` は記法に書かれたタイトルそのままで、`[title]` なら `title`、\n * `[/proj/page]` なら `/proj/page`、`#tag` なら `tag`、`[user.icon]` なら `user`。\n *\n * @defaultValue `/{title}`。区切りを含むタイトルは区切りを残して各段を encode する\n */\n readonly pageUrl?: (title: string, node: PageRefNode) => string\n /**\n * アイコンの画像 URL。null なら `<img>` を出さず、ユーザー名のテキストになる。\n * リンク先のほうは `pageUrl` が決める。\n *\n * @defaultValue 常に null。Cosense Web の画像 URL (`/api/pages/{project}/{user}/icon`) は\n * プロジェクト名を要するが、記法には書かれていないため\n */\n readonly iconImageUrl?: (node: IconNode) => string | null\n /**\n * コードブロックの中身の色付け。渡すと、本体は 1 行 1 要素ではなく 1 つの要素にまとまる。\n * ハイライタの出力が複数行にまたがる要素を含みうるため。\n */\n readonly highlight?: HastHighlighter\n /** 出力する要素に付ける class 名。 */\n readonly classNames?: HtmlClassNames\n /**\n * インデントを Cosense Web と同じ要素として書き出す。深さ 1 段につき `pad` が 1 つ並び、\n * その右端に中点が付く。\n *\n * @defaultValue false。深さは行の `data-indent` 属性だけで表す\n */\n readonly showPads?: boolean\n}\n\n/** オプションの既定値を埋めたもの。ハンドラは `ctx.options` でこれを読む。 */\nexport interface ResolvedHastOptions {\n readonly pageUrl: (title: string, node: PageRefNode) => string\n readonly iconImageUrl: (node: IconNode) => string | null\n readonly highlight: HastHighlighter | null\n /** 既定の class 名に、指定したものを重ねたもの */\n readonly classNames: HtmlClassNames\n readonly showPads: boolean\n}\n\n/** ハンドラの中から再帰的に変換するための入口と、オプション。 */\nexport interface HastContext {\n /**\n * 今描いているノードの祖先。根 (`toHast` に渡したノード) から親までの順に並ぶ。\n * 「テーブルのセルの中の text だけ」のように、どこにあるかで出力を変えるときに使う。\n */\n readonly ancestors: readonly AnyNode[]\n /** ノード 1 つを変換する (そのノード型のハンドラを通る) */\n readonly node: (node: AnyNode) => ElementContent[]\n /** 子ノードをすべて変換し、平らな配列で返す */\n readonly children: (node: AnyNode) => ElementContent[]\n readonly options: ResolvedHastOptions\n}\n\nexport type HastHandler<K extends AnyNodeType> = (\n node: NodeOfType<K>,\n ctx: HastContext,\n) => HastContent | HastContent[]\n\n/**\n * ノード型 → ハンドラの対応。`AnyNodeMap` から型を導出しているので、\n * ノード型が増えたら (declaration merging を含む) ここも自動で新しいキーを受け入れる。\n */\nexport type HastHandlers = {\n readonly [K in AnyNodeType]?: HastHandler<K>\n}\n\n/**\n * 描画の拡張の 1 段。そのノード型の、ここまでの出力 (既定のハンドラか `handlers`、\n * 先に並べた拡張を通したもの) を受け取り、新しい出力を返す。\n * vite の `transform` と同じく、前の段の出力をもらって加工するだけの関数にしている。\n * 出力を一から作るのは `handlers` の役目なので、拡張では作り直さない。\n */\nexport type RenderTransform<K extends AnyNodeType> = (\n output: ElementContent[],\n node: NodeOfType<K>,\n ctx: HastContext,\n) => HastContent | HastContent[]\n\n/**\n * 描画の拡張。ノード型ごとに `RenderTransform` を持つ。\n * 行番号 (`codeLineNumbers`) のように、既定の出力に手を加えるものをまとめて配るための形。\n */\nexport type RenderExtension = {\n readonly [K in AnyNodeType]?: RenderTransform<K>\n}\n\nexport interface HastOptions extends HastRenderOptions {\n /**\n * ノード型ごとの出力の置き換え。指定した型だけが差し替わる。\n * 出力を一から作りたいときに使う。できた出力に手を加えるだけなら `extensions` を使う。\n */\n readonly handlers?: HastHandlers\n /**\n * 描画の拡張。`handlers` の後に、並べた順に出力を加工する。\n * 前の拡張の出力が次の拡張に渡るので、同じノード型に触る拡張どうしも重ねられる。\n */\n readonly extensions?: readonly RenderExtension[]\n}\n\n/**\n * コードブロックのファイル名から言語名を推測する。拡張子があればそれ、無ければファイル名全体。\n * Cosense では `code:python` のように言語名だけを書くこともできるため。\n * `highlight` に渡る言語名はこれで決めている。\n */\nexport const codeLanguageOf = (filename: string): string => {\n const dot = filename.lastIndexOf(\".\")\n return (dot > 0 ? filename.slice(dot + 1) : filename).toLowerCase()\n}\n\n/**\n * `pageUrl` の既定の実装。`/proj/page` のように区切りを含むタイトルは、\n * 区切りを残したまま各段を encode する。\n */\nexport const defaultPageUrl = (title: string): string =>\n title.startsWith(\"/\")\n ? title.split(\"/\").map(encodeURIComponent).join(\"/\")\n : `/${encodeURIComponent(title)}`\n\n/** 記法に書かれたページタイトル。ノード型ごとに置き場所が違うのをここで吸収する。 */\nconst pageTitleOf = (node: PageRefNode): string =>\n Match.value(node).pipe(\n Match.when({ type: \"hashtag\" }, (tag) => tag.value),\n Match.when({ type: \"icon\" }, (icon) => icon.user),\n Match.orElse((link) => link.target),\n )\n\n// ---------------------------------------------------------------------------\n// hast の組み立て\n// ---------------------------------------------------------------------------\n\nconst text = (value: string): Text => ({ type: \"text\", value })\n\nconst classList = (name: string | undefined): string[] =>\n name === undefined ? [] : name.split(/\\s+/).filter((part) => part !== \"\")\n\nconst element = (\n tagName: string,\n properties: Properties,\n children: ElementContent[] = [],\n): Element => ({ type: \"element\", tagName, properties, children })\n\n/** 値が undefined のプロパティは出さない (`exactOptionalPropertyTypes` と同じ考え)。 */\nconst compact = (properties: Properties): Properties =>\n Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== undefined))\n\n/** 空の class は属性ごと出さない。class 名を空文字にして消せるようにするため。 */\nconst withClass = (className: string | undefined, properties: Properties = {}): Properties => {\n const names = classList(className)\n return names.length === 0 ? compact(properties) : { className: names, ...compact(properties) }\n}\n\n/** 0 は既定値なので属性にしない。 */\nconst positive = (value: number): number | undefined => (value > 0 ? value : undefined)\n\n/** 複数の class 名の設定を 1 つにつなぐ。未設定のものは飛ばす。 */\nconst joinClasses = (...names: (string | undefined)[]): string =>\n names.filter((name) => name !== undefined).join(\" \")\n\n/** ページを指すノードの遷移先。安全でなければ null (href を出さない)。 */\nconst pageHrefOf = (node: PageRefNode, options: ResolvedHastOptions): string | undefined =>\n Option.getOrUndefined(nonEmpty(safeHref(options.pageUrl(pageTitleOf(node), node))))\n\nconst anchor = (className: string | undefined, href: string | undefined, label: string): Element =>\n element(\"a\", withClass(className, { href }), [text(label)])\n\n/**\n * 装飾を表す要素を、内側から外側の順に並べたもの。\n *\n * Cosense では 1 つの記法が複数の装飾を同時に持つ (`[-/ x]` は打消しかつ斜体) ので、\n * フラグの集合を入れ子の要素に開いて表現する。\n */\nconst DECORATION_TAGS: readonly (readonly [(node: Decoration) => boolean, string])[] = [\n [(node) => node.strike, \"s\"],\n [(node) => node.underline, \"u\"],\n [(node) => node.italic, \"em\"],\n [(node) => node.bold, \"strong\"],\n]\n\n/** 余白の数がインデントの深さを表し、中点はその右端に付く (Cosense Web と同じ形)。 */\nconst indentMark = (indent: number, cls: HtmlClassNames): Element =>\n element(\"span\", withClass(cls.indentMark), [\n ...Array.from({ length: indent }, () => element(\"span\", withClass(cls.pad), [text(\" \")])),\n element(\"span\", withClass(cls.dot)),\n ])\n\n/** 色付けした本体と、コードブロックの code に足す class と style。 */\ninterface HighlightedBody {\n readonly children: ElementContent[]\n readonly className: readonly string[]\n readonly style: string | undefined\n}\n\n/**\n * raw ノードを hast の要素の中身として扱う。型の変換はここ 1 か所に閉じる。\n *\n * hast の型は raw を知らない。raw の型拡張は mdast-util-to-hast が持っているが、\n * それに頼ると利用者の環境で型が効くかどうかが依存の入り方で変わるので、`RawNode` を自前で持っている。\n * 実行時には、文字列にするとき (hast-util-to-html の allowDangerousHtml) にそのまま埋め込まれる。\n */\nconst asElementContents = (nodes: readonly HastContent[]): ElementContent[] =>\n nodes as ElementContent[]\n\n/** 要素の中身に入るノードだけにする。Root の子には doctype も来うるため。 */\nconst contentsOf = (result: Root | HastContent[]): ElementContent[] =>\n Array.isArray(result)\n ? asElementContents(result)\n : result.children.filter((child): child is ElementContent => child.type !== \"doctype\")\n\nconst isBlank = (node: ElementContent): boolean => node.type === \"text\" && node.value.trim() === \"\"\n\n/** 空白だけのテキストを除いた、ただ 1 つの子。 */\nconst onlyChildOf = (children: readonly ElementContent[]): Option.Option<ElementContent> =>\n pipe(\n Option.some(children.filter((child) => !isBlank(child))),\n Option.filter((meaningful) => meaningful.length === 1),\n Option.flatMapNullable((meaningful) => meaningful[0]),\n )\n\nconst elementNamed =\n (tagName: string) =>\n (node: ElementContent): Option.Option<Element> =>\n Option.liftPredicate(\n node,\n (content): content is Element => content.type === \"element\" && content.tagName === tagName,\n )\n\n/** hast の決まりでは className は配列だが、shiki は class を文字列で付ける。どちらも読む。 */\nconst classesOf = (value: Properties[string]): string[] =>\n Match.value(value).pipe(\n Match.when(Array.isArray, (names) => names.map(String)),\n Match.when(Match.string, classList),\n Match.orElse(() => []),\n )\n\n/**\n * ハイライタの出力をコードブロックの code の中身にする。\n * `pre > code` (shiki や lowlight の形) なら pre を剥がし、pre に付いたテーマの class と style を引き継ぐ。\n */\nconst highlightedBody = (result: Root | HastContent[]): HighlightedBody => {\n const children = contentsOf(result)\n return pipe(\n onlyChildOf(children),\n Option.flatMap(elementNamed(\"pre\")),\n Option.flatMap((pre) =>\n pipe(\n onlyChildOf(pre.children),\n Option.flatMap(elementNamed(\"code\")),\n Option.map((code): HighlightedBody => ({\n children: code.children,\n className: classesOf(pre.properties.className ?? pre.properties.class),\n style: typeof pre.properties.style === \"string\" ? pre.properties.style : undefined,\n })),\n ),\n ),\n Option.getOrElse((): HighlightedBody => ({ children, className: [], style: undefined })),\n )\n}\n\n/**\n * ハイライタの出力が行ごとの要素 (shiki の `span.line`) に分かれていれば、それを行の数だけ返す。\n * 行の区切りの改行 (空白だけのテキスト) は、行を別の要素にするので要らない。\n */\nconst linesOf = (\n children: readonly ElementContent[],\n count: number,\n): Option.Option<readonly Element[]> => {\n const isLine = (child: ElementContent): child is Element =>\n child.type === \"element\" &&\n classesOf(child.properties.className ?? child.properties.class).includes(\"line\")\n return Option.liftPredicate(\n children.filter((child) => !isBlank(child)),\n (lines): lines is Element[] => lines.length === count && lines.every(isLine),\n )\n}\n\n/** style.css がコードブロックの背景と文字の色に使う変数。 */\nconst THEME_VARIABLES: Readonly<Record<string, string>> = {\n \"background-color\": \"--cosense-code-bg\",\n color: \"--cosense-code-text\",\n}\n\n/**\n * テーマの背景色と文字色 (shiki が pre に付ける style) を、style.css の変数に読み替える。\n * そのまま style に置くと、利用者の CSS から上書きするのに !important が要るため。\n * 変数なら、テーマの色は既定として出しつつ、普通の CSS で塗り替えられる。\n */\nconst themeStyleOf = (style: string | undefined): string | undefined =>\n pipe(\n Option.fromNullable(style),\n Option.map((declarations) =>\n declarations\n .split(\";\")\n .flatMap((declaration) => Option.toArray(themeDeclarationOf(declaration)))\n .join(\";\"),\n ),\n Option.getOrUndefined,\n )\n\n/** `name: value` の 1 つ。`:` の無い壊れた宣言は落とす。 */\nconst themeDeclarationOf = (declaration: string): Option.Option<string> =>\n pipe(\n Option.some(declaration.indexOf(\":\")),\n Option.filter((colon) => colon > 0),\n Option.map((colon) => {\n const name = declaration.slice(0, colon).trim()\n const variable = Option.getOrElse(Option.fromNullable(THEME_VARIABLES[name]), () => name)\n return `${variable}:${declaration.slice(colon + 1).trim()}`\n }),\n )\n\n/**\n * 1 行 = 1 要素に切る (Cosense Web と同じ)。行ごとにインデントを付けられるようにするため。\n * `highlight` があるときだけ、複数行にまたがる要素を壊さないよう本体をまとめる。\n */\nconst codeBlock = (node: CodeBlock, ctx: HastContext): ElementContent[] => {\n const cls = ctx.options.classNames\n const classes = joinClasses(cls.line, cls.codeBlock)\n const blockLine = (indent: number, child: Element): Element =>\n element(\"div\", withClass(classes, { dataIndent: positive(indent) }), [child])\n const filename = element(\"span\", withClass(cls.codeFilename), [text(node.filename)])\n const header = blockLine(node.indent, element(\"code\", withClass(cls.codeStart), [filename]))\n\n // 本体はヘッダより 1 段深い。それより深い字下げは値のほうに残っている。\n const bodyLine = (\n className: string | undefined,\n properties: Properties,\n children: ElementContent[],\n ) => blockLine(node.indent + 1, element(\"code\", withClass(className, properties), children))\n\n const highlighted = pipe(\n Option.fromNullable(ctx.options.highlight),\n // 例外を投げたら、そのブロックは色付けしない。shiki は読み込んでいない言語で投げるので、\n // 1 つのブロックのためにページ全体の描画を落とさないようにする。\n Option.flatMap((highlight) =>\n Option.liftThrowable(highlight)(\n node.lines.map((codeLine) => codeLine.value).join(\"\\n\"),\n codeLanguageOf(node.filename),\n ),\n ),\n Option.flatMapNullable((result) => result),\n Option.map(highlightedBody),\n )\n\n return Option.match(highlighted, {\n onNone: () => [\n header,\n ...node.lines.map((codeLine) => bodyLine(cls.codeBody, {}, ctx.node(codeLine))),\n ],\n onSome: ({ children, className, style }) => {\n const names = joinClasses(cls.codeBody, cls.codeHighlight, ...className)\n const properties = { style: themeStyleOf(style) }\n return Option.match(linesOf(children, node.lines.length), {\n // 行ごとに分かれた出力 (shiki) は、色付けしないときと同じく 1 行ずつの要素に入れ直す。\n // 行ごとのインデントや行番号を、外側の行の要素に付けられるようにするため。\n onSome: (lines) => [header, ...lines.map((line) => bodyLine(names, properties, [line]))],\n // 行をまたぐ出力 (highlight.js など) は、壊さないようひと塊のまま出す。\n onNone: () => [header, bodyLine(names, properties, children)],\n })\n },\n })\n}\n\n// ---------------------------------------------------------------------------\n// 既定のハンドラ\n// ---------------------------------------------------------------------------\n\n/**\n * 既定のハンドラ一式。`toHast` はこれに `handlers` を重ねてから走らせる。\n *\n * cosense-x のように描画を組み立て直すライブラリが、既定の部品 (行の包み方など) を使い回すためのもの。\n * 既定の出力に手を加えるだけなら、これを呼ばずに `extensions` を使う。\n * オプションは `ctx.options` から読むので、呼ぶ側が渡し直さなくてよい。\n */\nexport const defaultHastHandlers = {\n page: (node, ctx) => [element(\"div\", withClass(ctx.options.classNames.page), ctx.children(node))],\n\n title: (node, ctx) => [\n element(\"h1\", withClass(ctx.options.classNames.title), ctx.children(node)),\n ],\n\n line: (node, ctx) => {\n const cls = ctx.options.classNames\n const body = ctx.children(node)\n const styled = node.monospace ? [element(\"code\", withClass(cls.monospace), body)] : body\n const quoted = node.quote ? [element(\"blockquote\", withClass(cls.quote), styled)] : styled\n // 空行も 1 行分の高さを保つ。Cosense では空行が段落の区切りとして意味を持つ。\n const inner = quoted.length === 0 ? [element(\"br\", {})] : quoted\n const mark = ctx.options.showPads && node.indent > 0 ? [indentMark(node.indent, cls)] : []\n return [\n element(\"div\", withClass(cls.line, { dataIndent: positive(node.indent) }), [\n ...mark,\n ...inner,\n ]),\n ]\n },\n\n codeBlock,\n codeLine: (node) => [text(node.value)],\n\n table: (node, ctx) => {\n const caption = node.name === \"\" ? [] : [element(\"caption\", {}, [text(node.name)])]\n return [\n element(\"table\", withClass(ctx.options.classNames.table), [\n ...caption,\n element(\"tbody\", {}, ctx.children(node)),\n ]),\n ]\n },\n tableRow: (node, ctx) => [element(\"tr\", {}, ctx.children(node))],\n // Cosense のテーブルにヘッダ行の概念は無いので、1 行目も含めてすべて td。\n tableCell: (node, ctx) => [element(\"td\", {}, ctx.children(node))],\n\n text: (node) => [text(node.value)],\n\n internalLink: (node, ctx) => [\n anchor(ctx.options.classNames.internalLink, pageHrefOf(node, ctx.options), node.label),\n ],\n externalLink: (node, ctx) => [\n anchor(\n ctx.options.classNames.externalLink,\n Option.getOrUndefined(nonEmpty(safeHref(node.target))),\n node.label,\n ),\n ],\n projectLink: (node, ctx) => [\n anchor(ctx.options.classNames.projectLink, pageHrefOf(node, ctx.options), node.label),\n ],\n hashtag: (node, ctx) => [\n anchor(ctx.options.classNames.hashtag, pageHrefOf(node, ctx.options), `#${node.value}`),\n ],\n\n inlineCode: (node, ctx) => [\n element(\"code\", withClass(ctx.options.classNames.inlineCode), [text(node.value)]),\n ],\n\n image: (node, ctx) => {\n // Gyazo のページ URL のように、書かれたままでは <img> に入らない URL をここで直す。\n // AST はソースの文字列を保つ約束なので、表示用への変換は描画側の責任になる。\n const src = Option.getOrUndefined(nonEmpty(safeSrc(asImageSrc(node.src) ?? node.src)))\n const img = element(\n \"img\",\n withClass(ctx.options.classNames.image, {\n src,\n alt: \"\",\n dataLarge: node.large ? \"true\" : undefined,\n }),\n )\n // 遷移先があるときだけ <a> で包む。スキームが安全でなければ包まない。\n const href = pipe(\n Option.fromNullable(node.link),\n Option.flatMap((link) => nonEmpty(safeHref(link))),\n )\n return [\n Option.match(href, {\n onNone: () => img,\n onSome: (url) => element(\"a\", { href: url }, [img]),\n }),\n ]\n },\n\n // Cosense Web と同じく、そのユーザーのページへのリンクで画像を包む。\n icon: (node, ctx) => {\n const cls = ctx.options.classNames\n const src = pipe(\n Option.fromNullable(ctx.options.iconImageUrl(node)),\n Option.flatMap((url) => nonEmpty(safeSrc(url))),\n )\n const href = pageHrefOf(node, ctx.options)\n // 連打の数だけ出す。要素を共有しないよう、1 つずつ作る。\n return Array.from({ length: node.count }, () => {\n const body: ElementContent = Option.match(src, {\n onNone: () => text(node.user),\n onSome: (url) =>\n element(\"img\", withClass(cls.icon, { src: url, alt: node.user, title: node.user })),\n })\n return element(\"a\", withClass(joinClasses(cls.internalLink, cls.icon), { href }), [body])\n })\n },\n\n // 数式の組版は KaTeX 等の仕事なので、記法を外した中身をそのまま置く。\n formula: (node, ctx) => [\n element(\"span\", withClass(ctx.options.classNames.formula), [text(node.value)]),\n ],\n\n decoration: (node, ctx) => {\n const inner = DECORATION_TAGS.filter(([isOn]) => isOn(node)).reduce<ElementContent[]>(\n (wrapped, [, tag]) => [element(tag, {}, wrapped)],\n ctx.children(node),\n )\n // Cosense Web と同じく記号ごとの class も出す。意味を持たない記号を userCSS で\n // 拾えるようにするためのもので、`classNames.decoration` とは別に常に付く。\n const names = joinClasses(\n ctx.options.classNames.decoration,\n ...node.markers.map((marker) => `deco-${marker}`),\n )\n return [element(\"span\", withClass(names, { dataSizeLevel: positive(node.sizeLevel) }), inner)]\n },\n} satisfies HastHandlers\n\n/** オプションの既定値を埋める。 */\nconst resolveOptions = (options: HastRenderOptions): ResolvedHastOptions => ({\n pageUrl: options.pageUrl ?? defaultPageUrl,\n iconImageUrl: options.iconImageUrl ?? (() => null),\n highlight: options.highlight ?? null,\n classNames: { ...defaultClassNames, ...options.classNames },\n showPads: options.showPads === true,\n})\n\n/** ハンドラや拡張の戻り値 (1 つか配列) を、要素の中身の配列にする。 */\nconst contentsOfResult = (result: HastContent | HastContent[]): ElementContent[] =>\n asElementContents(Array.isArray(result) ? result : [result])\n\n/**\n * ノード型で表 (ハンドラや拡張) を引く。\n * 表の関数の型はキーごとに違うが、ノードの `type` でキーを引いている以上一致するので、型はここ 1 か所で合わせる。\n */\nconst entryOf = <F>(table: object, type: AnyNodeType): Option.Option<F> =>\n Option.fromNullable((table as Readonly<Record<string, F | undefined>>)[type])\n\n/**\n * ページ (または任意のノード) を hast にする。\n *\n * 1 つのノードは「既定のハンドラ (`handlers` があれば置き換え)」で作り、`extensions` を並べた順に通す。\n * テキストは hast のテキストノードに入れるだけなので、エスケープは文字列にする側 (`toHtml`) が行う。\n * `javascript:` のようなスキームの URL は属性ごと落とす。\n * ハンドラの無いノード型 (拡張が足した独自ノード) は、中身を落とさずに子だけを出す。\n */\nexport const toHast = (node: AnyNode, options: HastOptions = {}): Root => {\n const handlers: HastHandlers = { ...defaultHastHandlers, ...options.handlers }\n const extensions = options.extensions ?? []\n\n const resolved = resolveOptions(options)\n\n const render = (target: AnyNode, ctx: HastContext): ElementContent[] =>\n pipe(\n entryOf<HastHandler<AnyNodeType>>(handlers, target.type),\n Option.match({\n onNone: () => ctx.children(target),\n onSome: (handler) => contentsOfResult(handler(target, ctx)),\n }),\n )\n\n const extend = (target: AnyNode, output: ElementContent[], ctx: HastContext): ElementContent[] =>\n extensions.reduce(\n (current, extension) =>\n pipe(\n entryOf<RenderTransform<AnyNodeType>>(extension, target.type),\n Option.match({\n onNone: () => current,\n onSome: (transform) => contentsOfResult(transform(current, target, ctx)),\n }),\n ),\n output,\n )\n\n /**\n * `target` を描くときの文脈。`ctx.node` / `ctx.children` で描くノードは、\n * 渡したノードに依らず `target` の下にあるものとして描く。\n * ハンドラが中身を差し替えた写し (`{ ...node, children }`) を渡しても、祖先が重ならないようにするため。\n */\n const contextOf = (target: AnyNode, ancestors: readonly AnyNode[]): HastContext => {\n const inside = [...ancestors, target]\n return {\n ancestors,\n node: (child) => compile(child, inside),\n children: (parent) => childrenOf(parent).flatMap((child) => compile(child, inside)),\n options: resolved,\n }\n }\n\n const compile = (target: AnyNode, ancestors: readonly AnyNode[]): ElementContent[] => {\n const ctx = contextOf(target, ancestors)\n return extend(target, render(target, ctx), ctx)\n }\n\n return { type: \"root\", children: compile(node, []) }\n}\n","/**\n * to-html.ts — AST を HTML の文字列にする。\n *\n * `toHast` で hast にしてから文字列にするだけの近道。描画の規則は `toHast` にしか持たない。\n * JSX など HTML 以外の出力とずれないようにするため。\n */\nimport type { Root } from \"hast\"\nimport { toHtml as hastToHtml } from \"hast-util-to-html\"\n\nimport type { AnyNode } from \"../types\"\nimport { type HastContent, type HastHighlighter, type HastOptions, toHast } from \"./to-hast\"\n\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n}\n\n/**\n * `& < > \" '` を実体参照に置き換える。テキストと属性値のどちらにも使える。\n * `highlight` に HTML の文字列を返すハイライタを渡すとき、色付けしない部分のエスケープに使う。\n */\nexport const escapeHtml = (value: string): string =>\n value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char] ?? char)\n\n/**\n * コードブロックの中身を色付けする。`toHast` の `highlight` に加えて、HTML の文字列も返せる。\n * **文字列はそのまま埋め込まれる**ため、エスケープは実装側の責任になる。\n * highlight.js や Prism のように HTML の文字列を返すハイライタを、そのまま渡せるようにするため。\n */\nexport type Highlighter = (code: string, language: string) => string | Root | HastContent[] | null\n\nexport interface HtmlOptions extends Omit<HastOptions, \"highlight\"> {\n /** コードブロックの中身の色付け。 */\n readonly highlight?: Highlighter\n /**\n * 出力の先頭に `<style>` 要素として差し込む CSS。\n *\n * @defaultValue undefined。`<style>` を出さない\n */\n readonly style?: string\n}\n\n/** 文字列を返すハイライタを、hast の raw ノードを返す形にする。 */\nconst asHastHighlighter =\n (highlight: Highlighter): HastHighlighter =>\n (code, language) => {\n const result = highlight(code, language)\n return typeof result === \"string\" ? [{ type: \"raw\", value: result }] : result\n }\n\n/**\n * ページ (または任意のノード) を HTML 文字列にする。`hast-util-to-html(toHast(node, options))` と同じ。\n *\n * テキストと属性値はエスケープし、`javascript:` のようなスキームの URL は属性ごと落とす。\n * `highlight` が返した文字列と、`handlers` が返した raw ノードはそのまま埋め込むので、\n * そこでのエスケープは書いた人の責任になる。\n */\nexport const toHtml = (node: AnyNode, options: HtmlOptions = {}): string => {\n const { highlight, style, ...rest } = options\n const hast = toHast(node, {\n ...rest,\n ...(highlight === undefined ? {} : { highlight: asHastHighlighter(highlight) }),\n })\n const html = hastToHtml(hast, {\n // highlight の文字列や handlers の raw ノードを、書いた人の意図どおり HTML として入れる。\n allowDangerousHtml: true,\n characterReferences: { useNamedReferences: true },\n })\n return style === undefined ? html : `<style>${style}</style>${html}`\n}\n","/**\n * code-line-numbers.ts — コードブロックに行番号を付ける描画の拡張。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { ElementContent } from \"hast\"\n\nimport type { RenderExtension } from \"./to-hast\"\n\n/** 行の要素に行番号と桁数を付ける。要素でないもの (テキストなど) には付ける場所が無いのでそのまま。 */\nconst withLineNumber =\n (digits: number) =>\n (line: ElementContent, number: number): ElementContent =>\n Match.value(line).pipe(\n Match.when({ type: \"element\" }, (element) => ({\n ...element,\n properties: { ...element.properties, dataLine: number, dataLineDigits: digits },\n })),\n Match.orElse(() => line),\n )\n\n/**\n * コードブロックの出力 (先頭がヘッダ行、続いて本体行) に行番号を付ける。\n * 本体行が行の数だけ並んでいるときだけ付ける。ひと塊にまとめた出力や、\n * `handlers` が別の形に置き換えた出力では、行と番号が対応しないため。\n */\nconst numberLines = (output: ElementContent[], count: number): ElementContent[] =>\n pipe(\n Option.some(output),\n Option.filter((lines) => lines.length === count + 1),\n Option.map(([header, ...body]) => {\n // 番号の欄の幅を CSS が決められるよう、ブロックで一番大きい番号の桁数を全行に付ける。\n const number = withLineNumber(String(count).length)\n return [\n ...Option.toArray(Option.fromNullable(header)),\n ...body.map((line, index) => number(line, index + 1)),\n ]\n }),\n Option.getOrElse(() => output),\n )\n\n/**\n * コードブロックの本体行に、1 から数えた行番号 (`data-line`) と桁数 (`data-line-digits`) を付ける拡張。\n *\n * ```ts\n * toHtml(page, { extensions: [codeLineNumbers()] })\n * ```\n *\n * 番号を見せるのは CSS の役目 (`@cosense-toolbox/style` は `data-line` を見て行の左に番号を出す)。\n * 色付けしてひと塊にまとめたブロックは、行と番号が対応しないので付けない。\n */\nexport const codeLineNumbers = (): RenderExtension => ({\n codeBlock: (output, node) => numberLines(output, node.lines.length),\n})\n","/**\n * table-cell-line-breaks.ts — テーブルのセルの中の文字列を改行にする描画の拡張。\n *\n * Cosense のセルには改行を書けないので、`\\n` のような文字の並びを代わりに書いておき、\n * 描画のときに `<br>` にする。記法ではなく見た目の約束なので、パーサーではなく描画の拡張にしている。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { Element, ElementContent, Text } from \"hast\"\n\nimport type { AnyNode } from \"../types\"\nimport type { RenderExtension } from \"./to-hast\"\n\nconst lineBreak = (): Element => ({ type: \"element\", tagName: \"br\", properties: {}, children: [] })\n\n/** 空でない文字列だけを text にする。区切った部分が空なら何も出さない。 */\nconst textOf = (value: string): Option.Option<Text> =>\n pipe(\n Option.some(value),\n Option.filter((part) => part !== \"\"),\n Option.map((part): Text => ({ type: \"text\", value: part })),\n )\n\n/** 文字列を `marker` で区切り、間に `<br>` を挟む。 */\nconst breakText = (value: string, marker: string): ElementContent[] =>\n value\n .split(marker)\n .flatMap((part, index): ElementContent[] => [\n ...(index === 0 ? [] : [lineBreak()]),\n ...Option.toArray(textOf(part)),\n ])\n\n/** 出力のうち text だけを区切る。`handlers.text` が要素を返したときは、そのまま残す。 */\nconst breakContent =\n (marker: string) =>\n (content: ElementContent): ElementContent[] =>\n Match.value(content).pipe(\n Match.when({ type: \"text\" }, (text) => breakText(text.value, marker)),\n Match.orElse((other) => [other]),\n )\n\nconst isTableCell = (node: AnyNode): boolean => node.type === \"tableCell\"\n\n/**\n * テーブルのセルの中の `marker` を `<br>` にする描画の拡張。\n *\n * 当てるのは、セルの中にある AST の text ノード (記法の外の地の文) だけ。\n * コード・数式・リンクの表示は text ノードではないので、`classNames` や `handlers` で\n * 出力を変えても、その中には当たらない。装飾の中身は text ノードなので当たる。\n * `marker` は文字列そのままで探し (正規表現としては読まない)、区切った文字は通常どおりエスケープされる。\n * `marker` が空文字なら何もしない。\n *\n * @example\n * toHtml(page, { extensions: [tableCellLineBreaks('\\\\n')] })\n */\nexport const tableCellLineBreaks = (marker: string): RenderExtension => ({\n text: (output, _node, ctx) =>\n pipe(\n Option.some(marker),\n Option.filter((value) => value !== \"\"),\n Option.filter(() => ctx.ancestors.some(isTableCell)),\n Option.match({\n onNone: () => output,\n onSome: (value) => output.flatMap(breakContent(value)),\n }),\n ),\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"html.cjs","names":["pipe","Option","Match","asImageSrc","childrenOf","hastToHtml","Match","pipe","Option","pipe","Option","Match"],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"sourcesContent":["/**\n * to-hast.ts — AST を hast (HTML の AST) にする公式コンパイラ。\n *\n * HTML の文字列 (`toHtml`)、JSX (cosense-x)、rehype のプラグインはすべてこの出力から作る。\n * 描画の規則をここ 1 か所に置き、形式ごとに実装が分かれてずれないようにするため。\n *\n * ノード型ごとのハンドラの集まりで、`handlers` に渡した型だけが差し替わる。\n * 既定のハンドラは `defaultHastHandlers` として公開し、オプションは `ctx.options` から読む。\n * 利用者が既定の出力を包むとき、オプションを渡し直さなくて済むようにするため。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { Element, ElementContent, Properties, Root, Text } from \"hast\"\n\nimport { childrenOf } from \"../ast\"\nimport { asImageSrc } from \"../core/image-url\"\nimport type {\n AnyNode,\n AnyNodeType,\n CodeBlock,\n Decoration,\n Hashtag,\n IconNode,\n InternalLink,\n LineBlock,\n NodeOfType,\n ProjectLink,\n TextNode,\n} from \"../types\"\n\n// ---------------------------------------------------------------------------\n// URL の検査\n// ---------------------------------------------------------------------------\n\n/** `href` に入れると script が動くスキーム。`data:text/html` があるので data: も拒む。 */\nconst UNSAFE_HREF_RE = /^(?:javascript|vbscript|data):/\n\n/** `src` に入れると script が動くスキーム。`data:` 画像は正当な使い道があるので許す。 */\nconst UNSAFE_SRC_RE = /^(?:javascript|vbscript):/\n\n/**\n * スキームだけを見るために空白と制御文字を落とす。\n * ブラウザは途中にタブや改行が挟まった `javascript:` もスキームとして解釈するため。\n */\nconst schemeOf = (url: string): string => url.replace(/[\\s\\p{Cc}]/gu, \"\").toLowerCase()\n\nconst safeUrl = (url: string, unsafe: RegExp): string | null =>\n unsafe.test(schemeOf(url)) ? null : url\n\n/** `href` に入れて安全な URL だけを返す。script が動くスキームなら null。 */\nexport const safeHref = (url: string): string | null => safeUrl(url, UNSAFE_HREF_RE)\n\n/** `src` に入れて安全な URL だけを返す。script が動くスキームなら null。 */\nexport const safeSrc = (url: string): string | null => safeUrl(url, UNSAFE_SRC_RE)\n\n/** 空文字は「指定なし」とみなす。class 名や URL を空にして属性ごと消せるようにするため。 */\nconst nonEmpty = (value: string | null | undefined): Option.Option<string> =>\n pipe(\n Option.fromNullable(value),\n Option.filter((text) => text !== \"\"),\n )\n\n// ---------------------------------------------------------------------------\n// オプション\n// ---------------------------------------------------------------------------\n\n/** プロジェクト内のページを指すノード。Cosense Web ではアイコンもユーザーのページへのリンクになる。 */\nexport type PageRefNode = InternalLink | ProjectLink | Hashtag | IconNode\n\n/**\n * 出力する要素に付ける class 名。指定したキーだけが既定を上書きする。\n * 空文字にすると class 属性自体を出さない。\n */\nexport interface HtmlClassNames {\n readonly page?: string\n readonly title?: string\n readonly line?: string\n /** 引用行の `<blockquote>` */\n readonly quote?: string\n /** コマンドの行 (`$ ls`) の `<code>` */\n readonly monospace?: string\n /** コマンドの行の先頭の `$` / `%` */\n readonly commandPrefix?: string\n /** コマンドの行の、記号の後の空白 */\n readonly commandSpace?: string\n /** コマンドの行の、空白の後のコマンド */\n readonly command?: string\n /** コードブロックに属する行 (ヘッダ行と本体行の両方) */\n readonly codeBlock?: string\n /** ヘッダ行の `<code>` */\n readonly codeStart?: string\n /** ヘッダ行に出るファイル名 */\n readonly codeFilename?: string\n /** 本体行の `<code>` */\n readonly codeBody?: string\n /** ハイライタを通した本体に追加で付く class */\n readonly codeHighlight?: string\n readonly table?: string\n /** `showPads` のときだけ出る、インデントぶんの余白と中点をまとめる要素 */\n readonly indentMark?: string\n /** インデント 1 段ぶんの余白 */\n readonly pad?: string\n /** インデントの右端に置く中点 */\n readonly dot?: string\n readonly internalLink?: string\n readonly externalLink?: string\n readonly projectLink?: string\n readonly hashtag?: string\n readonly inlineCode?: string\n readonly image?: string\n readonly icon?: string\n readonly formula?: string\n readonly decoration?: string\n}\n\n/** 既定の class 名。接頭辞を持たないため、利用側の CSS と名前がぶつかりうる。 */\nexport const defaultClassNames: HtmlClassNames = {\n page: \"page\",\n title: \"title\",\n line: \"line\",\n quote: \"quote\",\n monospace: \"monospace\",\n commandPrefix: \"prefix\",\n commandSpace: \"space\",\n command: \"command\",\n codeBlock: \"code-block\",\n codeStart: \"code-start\",\n codeFilename: \"code-block-start\",\n codeBody: \"code-body\",\n codeHighlight: \"highlight\",\n table: \"table\",\n indentMark: \"indent-mark\",\n pad: \"pad\",\n dot: \"dot\",\n internalLink: \"link\",\n externalLink: \"link link-external\",\n projectLink: \"link link-project\",\n hashtag: \"hashtag\",\n inlineCode: \"code\",\n image: \"image\",\n icon: \"icon\",\n formula: \"formula\",\n decoration: \"decoration\",\n}\n\n/**\n * HTML の文字列をそのまま入れるノード。`toHtml` はそのまま埋め込む (hast-util-to-html の raw ノード)。\n * hast の型には無いので、ここで持つ。hast-util-to-html の型拡張に頼ると、\n * `@types/hast` の版が分かれたときに型が合わなくなるため。\n */\nexport interface RawNode {\n readonly type: \"raw\"\n readonly value: string\n}\n\n/** ハンドラやハイライタが返せるノード。hast の要素の中身か、raw ノード。 */\nexport type HastContent = ElementContent | RawNode\n\n/**\n * コードブロックの中身を色付けする。\n *\n * `language` はファイル名から推測した名前 (`codeLanguageOf`)。\n * null を返すと色付けせず、1 行ずつのまま出す。知らない言語のときに使う。\n *\n * shiki の `codeToHast` のように `pre > code` を返したときは、code の中身を使い、\n * pre の class と style (テーマの背景色や文字色) をコードブロックの code に移す。\n * 行の要素 (`div.line`) の中に置くので、pre そのものは出さない。\n */\nexport type HastHighlighter = (code: string, language: string) => Root | HastContent[] | null\n\n/**\n * 既定のハンドラの振る舞いを変えるオプション。\n *\n * `pageUrl` / `iconImageUrl` / `highlight` は、**記法に書かれていないので AST から導けない**\n * 情報を外から渡すためのもの。残りは出力の見た目を調整する。\n */\nexport interface HastRenderOptions {\n /**\n * ページを指す記法 (`[title]` / `[/proj/page]` / `#tag` / `[user.icon]`) の遷移先。\n * アイコンについてはリンク先だけを決め、画像は `iconImageUrl` が決める。\n *\n * `title` は記法に書かれたタイトルそのままで、`[title]` なら `title`、\n * `[/proj/page]` なら `/proj/page`、`#tag` なら `tag`、`[user.icon]` なら `user`。\n *\n * @defaultValue `/{title}`。区切りを含むタイトルは区切りを残して各段を encode する\n */\n readonly pageUrl?: (title: string, node: PageRefNode) => string\n /**\n * アイコンの画像 URL。null なら `<img>` を出さず、ユーザー名のテキストになる。\n * リンク先のほうは `pageUrl` が決める。\n *\n * @defaultValue 常に null。Cosense Web の画像 URL (`/api/pages/{project}/{user}/icon`) は\n * プロジェクト名を要するが、記法には書かれていないため\n */\n readonly iconImageUrl?: (node: IconNode) => string | null\n /**\n * コードブロックの中身の色付け。渡すと、本体は 1 行 1 要素ではなく 1 つの要素にまとまる。\n * ハイライタの出力が複数行にまたがる要素を含みうるため。\n */\n readonly highlight?: HastHighlighter\n /** 出力する要素に付ける class 名。 */\n readonly classNames?: HtmlClassNames\n /**\n * インデントを Cosense Web と同じ要素として書き出す。深さ 1 段につき `pad` が 1 つ並び、\n * その右端に中点が付く。\n *\n * @defaultValue false。深さは行の `data-indent` 属性だけで表す\n */\n readonly showPads?: boolean\n}\n\n/** オプションの既定値を埋めたもの。ハンドラは `ctx.options` でこれを読む。 */\nexport interface ResolvedHastOptions {\n readonly pageUrl: (title: string, node: PageRefNode) => string\n readonly iconImageUrl: (node: IconNode) => string | null\n readonly highlight: HastHighlighter | null\n /** 既定の class 名に、指定したものを重ねたもの */\n readonly classNames: HtmlClassNames\n readonly showPads: boolean\n}\n\n/** ハンドラの中から再帰的に変換するための入口と、オプション。 */\nexport interface HastContext {\n /**\n * 今描いているノードの祖先。根 (`toHast` に渡したノード) から親までの順に並ぶ。\n * 「テーブルのセルの中の text だけ」のように、どこにあるかで出力を変えるときに使う。\n */\n readonly ancestors: readonly AnyNode[]\n /** ノード 1 つを変換する (そのノード型のハンドラを通る) */\n readonly node: (node: AnyNode) => ElementContent[]\n /** 子ノードをすべて変換し、平らな配列で返す */\n readonly children: (node: AnyNode) => ElementContent[]\n readonly options: ResolvedHastOptions\n}\n\nexport type HastHandler<K extends AnyNodeType> = (\n node: NodeOfType<K>,\n ctx: HastContext,\n) => HastContent | HastContent[]\n\n/**\n * ノード型 → ハンドラの対応。`AnyNodeMap` から型を導出しているので、\n * ノード型が増えたら (declaration merging を含む) ここも自動で新しいキーを受け入れる。\n */\nexport type HastHandlers = {\n readonly [K in AnyNodeType]?: HastHandler<K>\n}\n\n/**\n * 描画の拡張の 1 段。そのノード型の、ここまでの出力 (既定のハンドラか `handlers`、\n * 先に並べた拡張を通したもの) を受け取り、新しい出力を返す。\n * vite の `transform` と同じく、前の段の出力をもらって加工するだけの関数にしている。\n * 出力を一から作るのは `handlers` の役目なので、拡張では作り直さない。\n */\nexport type RenderTransform<K extends AnyNodeType> = (\n output: ElementContent[],\n node: NodeOfType<K>,\n ctx: HastContext,\n) => HastContent | HastContent[]\n\n/**\n * 描画の拡張。ノード型ごとに `RenderTransform` を持つ。\n * 行番号 (`codeLineNumbers`) のように、既定の出力に手を加えるものをまとめて配るための形。\n */\nexport type RenderExtension = {\n readonly [K in AnyNodeType]?: RenderTransform<K>\n}\n\nexport interface HastOptions extends HastRenderOptions {\n /**\n * ノード型ごとの出力の置き換え。指定した型だけが差し替わる。\n * 出力を一から作りたいときに使う。できた出力に手を加えるだけなら `extensions` を使う。\n */\n readonly handlers?: HastHandlers\n /**\n * 描画の拡張。`handlers` の後に、並べた順に出力を加工する。\n * 前の拡張の出力が次の拡張に渡るので、同じノード型に触る拡張どうしも重ねられる。\n */\n readonly extensions?: readonly RenderExtension[]\n}\n\n/**\n * コードブロックのファイル名から言語名を推測する。拡張子があればそれ、無ければファイル名全体。\n * Cosense では `code:python` のように言語名だけを書くこともできるため。\n * `highlight` に渡る言語名はこれで決めている。\n */\nexport const codeLanguageOf = (filename: string): string => {\n const dot = filename.lastIndexOf(\".\")\n return (dot > 0 ? filename.slice(dot + 1) : filename).toLowerCase()\n}\n\n/**\n * `pageUrl` の既定の実装。`/proj/page` のように区切りを含むタイトルは、\n * 区切りを残したまま各段を encode する。\n */\nexport const defaultPageUrl = (title: string): string =>\n title.startsWith(\"/\")\n ? title.split(\"/\").map(encodeURIComponent).join(\"/\")\n : `/${encodeURIComponent(title)}`\n\n/** 記法に書かれたページタイトル。ノード型ごとに置き場所が違うのをここで吸収する。 */\nconst pageTitleOf = (node: PageRefNode): string =>\n Match.value(node).pipe(\n Match.when({ type: \"hashtag\" }, (tag) => tag.value),\n Match.when({ type: \"icon\" }, (icon) => icon.user),\n Match.orElse((link) => link.target),\n )\n\n// ---------------------------------------------------------------------------\n// hast の組み立て\n// ---------------------------------------------------------------------------\n\nconst text = (value: string): Text => ({ type: \"text\", value })\n\nconst classList = (name: string | undefined): string[] =>\n name === undefined ? [] : name.split(/\\s+/).filter((part) => part !== \"\")\n\nconst element = (\n tagName: string,\n properties: Properties,\n children: ElementContent[] = [],\n): Element => ({ type: \"element\", tagName, properties, children })\n\n/** 値が undefined のプロパティは出さない (`exactOptionalPropertyTypes` と同じ考え)。 */\nconst compact = (properties: Properties): Properties =>\n Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== undefined))\n\n/** 空の class は属性ごと出さない。class 名を空文字にして消せるようにするため。 */\nconst withClass = (className: string | undefined, properties: Properties = {}): Properties => {\n const names = classList(className)\n return names.length === 0 ? compact(properties) : { className: names, ...compact(properties) }\n}\n\n/** 0 は既定値なので属性にしない。 */\nconst positive = (value: number): number | undefined => (value > 0 ? value : undefined)\n\n/** 複数の class 名の設定を 1 つにつなぐ。未設定のものは飛ばす。 */\nconst joinClasses = (...names: (string | undefined)[]): string =>\n names.filter((name) => name !== undefined).join(\" \")\n\n/** ページを指すノードの遷移先。安全でなければ null (href を出さない)。 */\nconst pageHrefOf = (node: PageRefNode, options: ResolvedHastOptions): string | undefined =>\n Option.getOrUndefined(nonEmpty(safeHref(options.pageUrl(pageTitleOf(node), node))))\n\nconst anchor = (className: string | undefined, href: string | undefined, label: string): Element =>\n element(\"a\", withClass(className, { href }), [text(label)])\n\n/**\n * 装飾を表す要素を、内側から外側の順に並べたもの。\n *\n * Cosense では 1 つの記法が複数の装飾を同時に持つ (`[-/ x]` は打消しかつ斜体) ので、\n * フラグの集合を入れ子の要素に開いて表現する。\n */\nconst DECORATION_TAGS: readonly (readonly [(node: Decoration) => boolean, string])[] = [\n [(node) => node.strike, \"s\"],\n [(node) => node.underline, \"u\"],\n [(node) => node.italic, \"em\"],\n [(node) => node.bold, \"strong\"],\n]\n\n/** 余白の数がインデントの深さを表し、中点はその右端に付く (Cosense Web と同じ形)。 */\nconst indentMark = (indent: number, cls: HtmlClassNames): Element =>\n element(\"span\", withClass(cls.indentMark), [\n ...Array.from({ length: indent }, () => element(\"span\", withClass(cls.pad), [text(\" \")])),\n element(\"span\", withClass(cls.dot)),\n ])\n\n/** 色付けした本体と、コードブロックの code に足す class と style。 */\ninterface HighlightedBody {\n readonly children: ElementContent[]\n readonly className: readonly string[]\n readonly style: string | undefined\n}\n\n/**\n * raw ノードを hast の要素の中身として扱う。型の変換はここ 1 か所に閉じる。\n *\n * hast の型は raw を知らない。raw の型拡張は mdast-util-to-hast が持っているが、\n * それに頼ると利用者の環境で型が効くかどうかが依存の入り方で変わるので、`RawNode` を自前で持っている。\n * 実行時には、文字列にするとき (hast-util-to-html の allowDangerousHtml) にそのまま埋め込まれる。\n */\nconst asElementContents = (nodes: readonly HastContent[]): ElementContent[] =>\n nodes as ElementContent[]\n\n/** 要素の中身に入るノードだけにする。Root の子には doctype も来うるため。 */\nconst contentsOf = (result: Root | HastContent[]): ElementContent[] =>\n Array.isArray(result)\n ? asElementContents(result)\n : result.children.filter((child): child is ElementContent => child.type !== \"doctype\")\n\nconst isBlank = (node: ElementContent): boolean => node.type === \"text\" && node.value.trim() === \"\"\n\n/** 空白だけのテキストを除いた、ただ 1 つの子。 */\nconst onlyChildOf = (children: readonly ElementContent[]): Option.Option<ElementContent> =>\n pipe(\n Option.some(children.filter((child) => !isBlank(child))),\n Option.filter((meaningful) => meaningful.length === 1),\n Option.flatMapNullable((meaningful) => meaningful[0]),\n )\n\n/**\n * コマンドの行 (`$ ls`) を、Cosense Web と同じく記号・空白・コマンドの 3 つの要素に分ける。\n * 記号とコマンドを別に選べるので、記号を薄くしたり、コマンドだけをコピーさせたりできる。\n * パーサーはコマンドの行を書いたままの文字 1 つにするので、そうでなければ (拡張が置き換えたなど) 分けない。\n */\nconst commandParts = (node: LineBlock, cls: HtmlClassNames): Option.Option<ElementContent[]> =>\n pipe(\n Option.liftPredicate(node.children, (children) => children.length === 1),\n Option.flatMapNullable(([child]) => child),\n Option.filter((child): child is TextNode => child.type === \"text\"),\n Option.map(({ value }) => [\n element(\"span\", withClass(cls.commandPrefix), [text(value.slice(0, 1))]),\n element(\"span\", withClass(cls.commandSpace), [text(value.slice(1, 2))]),\n element(\"span\", withClass(cls.command), [text(value.slice(2))]),\n ]),\n )\n\nconst elementNamed =\n (tagName: string) =>\n (node: ElementContent): Option.Option<Element> =>\n Option.liftPredicate(\n node,\n (content): content is Element => content.type === \"element\" && content.tagName === tagName,\n )\n\n/** hast の決まりでは className は配列だが、shiki は class を文字列で付ける。どちらも読む。 */\nconst classesOf = (value: Properties[string]): string[] =>\n Match.value(value).pipe(\n Match.when(Array.isArray, (names) => names.map(String)),\n Match.when(Match.string, classList),\n Match.orElse(() => []),\n )\n\n/**\n * ハイライタの出力をコードブロックの code の中身にする。\n * `pre > code` (shiki や lowlight の形) なら pre を剥がし、pre に付いたテーマの class と style を引き継ぐ。\n */\nconst highlightedBody = (result: Root | HastContent[]): HighlightedBody => {\n const children = contentsOf(result)\n return pipe(\n onlyChildOf(children),\n Option.flatMap(elementNamed(\"pre\")),\n Option.flatMap((pre) =>\n pipe(\n onlyChildOf(pre.children),\n Option.flatMap(elementNamed(\"code\")),\n Option.map((code): HighlightedBody => ({\n children: code.children,\n className: classesOf(pre.properties.className ?? pre.properties.class),\n style: typeof pre.properties.style === \"string\" ? pre.properties.style : undefined,\n })),\n ),\n ),\n Option.getOrElse((): HighlightedBody => ({ children, className: [], style: undefined })),\n )\n}\n\n/**\n * ハイライタの出力が行ごとの要素 (shiki の `span.line`) に分かれていれば、それを行の数だけ返す。\n * 行の区切りの改行 (空白だけのテキスト) は、行を別の要素にするので要らない。\n */\nconst linesOf = (\n children: readonly ElementContent[],\n count: number,\n): Option.Option<readonly Element[]> => {\n const isLine = (child: ElementContent): child is Element =>\n child.type === \"element\" &&\n classesOf(child.properties.className ?? child.properties.class).includes(\"line\")\n return Option.liftPredicate(\n children.filter((child) => !isBlank(child)),\n (lines): lines is Element[] => lines.length === count && lines.every(isLine),\n )\n}\n\n/** style.css がコードブロックの背景と文字の色に使う変数。 */\nconst THEME_VARIABLES: Readonly<Record<string, string>> = {\n \"background-color\": \"--cosense-code-bg\",\n color: \"--cosense-code-text\",\n}\n\n/**\n * テーマの背景色と文字色 (shiki が pre に付ける style) を、style.css の変数に読み替える。\n * そのまま style に置くと、利用者の CSS から上書きするのに !important が要るため。\n * 変数なら、テーマの色は既定として出しつつ、普通の CSS で塗り替えられる。\n */\nconst themeStyleOf = (style: string | undefined): string | undefined =>\n pipe(\n Option.fromNullable(style),\n Option.map((declarations) =>\n declarations\n .split(\";\")\n .flatMap((declaration) => Option.toArray(themeDeclarationOf(declaration)))\n .join(\";\"),\n ),\n Option.getOrUndefined,\n )\n\n/** `name: value` の 1 つ。`:` の無い壊れた宣言は落とす。 */\nconst themeDeclarationOf = (declaration: string): Option.Option<string> =>\n pipe(\n Option.some(declaration.indexOf(\":\")),\n Option.filter((colon) => colon > 0),\n Option.map((colon) => {\n const name = declaration.slice(0, colon).trim()\n const variable = Option.getOrElse(Option.fromNullable(THEME_VARIABLES[name]), () => name)\n return `${variable}:${declaration.slice(colon + 1).trim()}`\n }),\n )\n\n/**\n * 1 行 = 1 要素に切る (Cosense Web と同じ)。行ごとにインデントを付けられるようにするため。\n * `highlight` があるときだけ、複数行にまたがる要素を壊さないよう本体をまとめる。\n */\nconst codeBlock = (node: CodeBlock, ctx: HastContext): ElementContent[] => {\n const cls = ctx.options.classNames\n const classes = joinClasses(cls.line, cls.codeBlock)\n const blockLine = (indent: number, child: Element): Element =>\n element(\"div\", withClass(classes, { dataIndent: positive(indent) }), [child])\n const filename = element(\"span\", withClass(cls.codeFilename), [text(node.filename)])\n const header = blockLine(node.indent, element(\"code\", withClass(cls.codeStart), [filename]))\n\n // 本体はヘッダより 1 段深い。それより深い字下げは値のほうに残っている。\n const bodyLine = (\n className: string | undefined,\n properties: Properties,\n children: ElementContent[],\n ) => blockLine(node.indent + 1, element(\"code\", withClass(className, properties), children))\n\n const highlighted = pipe(\n Option.fromNullable(ctx.options.highlight),\n // 例外を投げたら、そのブロックは色付けしない。shiki は読み込んでいない言語で投げるので、\n // 1 つのブロックのためにページ全体の描画を落とさないようにする。\n Option.flatMap((highlight) =>\n Option.liftThrowable(highlight)(\n node.lines.map((codeLine) => codeLine.value).join(\"\\n\"),\n codeLanguageOf(node.filename),\n ),\n ),\n Option.flatMapNullable((result) => result),\n Option.map(highlightedBody),\n )\n\n return Option.match(highlighted, {\n onNone: () => [\n header,\n ...node.lines.map((codeLine) => bodyLine(cls.codeBody, {}, ctx.node(codeLine))),\n ],\n onSome: ({ children, className, style }) => {\n const names = joinClasses(cls.codeBody, cls.codeHighlight, ...className)\n const properties = { style: themeStyleOf(style) }\n return Option.match(linesOf(children, node.lines.length), {\n // 行ごとに分かれた出力 (shiki) は、色付けしないときと同じく 1 行ずつの要素に入れ直す。\n // 行ごとのインデントや行番号を、外側の行の要素に付けられるようにするため。\n onSome: (lines) => [header, ...lines.map((line) => bodyLine(names, properties, [line]))],\n // 行をまたぐ出力 (highlight.js など) は、壊さないようひと塊のまま出す。\n onNone: () => [header, bodyLine(names, properties, children)],\n })\n },\n })\n}\n\n// ---------------------------------------------------------------------------\n// 既定のハンドラ\n// ---------------------------------------------------------------------------\n\n/**\n * 既定のハンドラ一式。`toHast` はこれに `handlers` を重ねてから走らせる。\n *\n * cosense-x のように描画を組み立て直すライブラリが、既定の部品 (行の包み方など) を使い回すためのもの。\n * 既定の出力に手を加えるだけなら、これを呼ばずに `extensions` を使う。\n * オプションは `ctx.options` から読むので、呼ぶ側が渡し直さなくてよい。\n */\nexport const defaultHastHandlers = {\n page: (node, ctx) => [element(\"div\", withClass(ctx.options.classNames.page), ctx.children(node))],\n\n title: (node, ctx) => [\n element(\"h1\", withClass(ctx.options.classNames.title), ctx.children(node)),\n ],\n\n line: (node, ctx) => {\n const cls = ctx.options.classNames\n const styled = node.monospace\n ? [\n element(\n \"code\",\n withClass(cls.monospace),\n Option.getOrElse(commandParts(node, cls), () => ctx.children(node)),\n ),\n ]\n : ctx.children(node)\n const quoted = node.quote ? [element(\"blockquote\", withClass(cls.quote), styled)] : styled\n // 空行も 1 行分の高さを保つ。Cosense では空行が段落の区切りとして意味を持つ。\n const inner = quoted.length === 0 ? [element(\"br\", {})] : quoted\n const mark = ctx.options.showPads && node.indent > 0 ? [indentMark(node.indent, cls)] : []\n return [\n element(\"div\", withClass(cls.line, { dataIndent: positive(node.indent) }), [\n ...mark,\n ...inner,\n ]),\n ]\n },\n\n codeBlock,\n codeLine: (node) => [text(node.value)],\n\n table: (node, ctx) => {\n const caption = node.name === \"\" ? [] : [element(\"caption\", {}, [text(node.name)])]\n return [\n element(\"table\", withClass(ctx.options.classNames.table), [\n ...caption,\n element(\"tbody\", {}, ctx.children(node)),\n ]),\n ]\n },\n tableRow: (node, ctx) => [element(\"tr\", {}, ctx.children(node))],\n // Cosense のテーブルにヘッダ行の概念は無いので、1 行目も含めてすべて td。\n tableCell: (node, ctx) => [element(\"td\", {}, ctx.children(node))],\n\n text: (node) => [text(node.value)],\n\n internalLink: (node, ctx) => [\n anchor(ctx.options.classNames.internalLink, pageHrefOf(node, ctx.options), node.label),\n ],\n externalLink: (node, ctx) => [\n anchor(\n ctx.options.classNames.externalLink,\n Option.getOrUndefined(nonEmpty(safeHref(node.target))),\n node.label,\n ),\n ],\n projectLink: (node, ctx) => [\n anchor(ctx.options.classNames.projectLink, pageHrefOf(node, ctx.options), node.label),\n ],\n hashtag: (node, ctx) => [\n anchor(ctx.options.classNames.hashtag, pageHrefOf(node, ctx.options), `#${node.value}`),\n ],\n\n inlineCode: (node, ctx) => [\n element(\"code\", withClass(ctx.options.classNames.inlineCode), [text(node.value)]),\n ],\n\n image: (node, ctx) => {\n // Gyazo のページ URL のように、書かれたままでは <img> に入らない URL をここで直す。\n // AST はソースの文字列を保つ約束なので、表示用への変換は描画側の責任になる。\n const src = Option.getOrUndefined(nonEmpty(safeSrc(asImageSrc(node.src) ?? node.src)))\n const img = element(\n \"img\",\n withClass(ctx.options.classNames.image, {\n src,\n alt: \"\",\n dataLarge: node.large ? \"true\" : undefined,\n }),\n )\n // 遷移先があるときだけ <a> で包む。スキームが安全でなければ包まない。\n const href = pipe(\n Option.fromNullable(node.link),\n Option.flatMap((link) => nonEmpty(safeHref(link))),\n )\n return [\n Option.match(href, {\n onNone: () => img,\n onSome: (url) => element(\"a\", { href: url }, [img]),\n }),\n ]\n },\n\n // Cosense Web と同じく、そのユーザーのページへのリンクで画像を包む。\n icon: (node, ctx) => {\n const cls = ctx.options.classNames\n const src = pipe(\n Option.fromNullable(ctx.options.iconImageUrl(node)),\n Option.flatMap((url) => nonEmpty(safeSrc(url))),\n )\n const href = pageHrefOf(node, ctx.options)\n // 連打の数だけ出す。要素を共有しないよう、1 つずつ作る。\n return Array.from({ length: node.count }, () => {\n const body: ElementContent = Option.match(src, {\n onNone: () => text(node.user),\n onSome: (url) =>\n element(\"img\", withClass(cls.icon, { src: url, alt: node.user, title: node.user })),\n })\n return element(\"a\", withClass(joinClasses(cls.internalLink, cls.icon), { href }), [body])\n })\n },\n\n // 数式の組版は KaTeX 等の仕事なので、記法を外した中身をそのまま置く。\n formula: (node, ctx) => [\n element(\"span\", withClass(ctx.options.classNames.formula), [text(node.value)]),\n ],\n\n decoration: (node, ctx) => {\n const inner = DECORATION_TAGS.filter(([isOn]) => isOn(node)).reduce<ElementContent[]>(\n (wrapped, [, tag]) => [element(tag, {}, wrapped)],\n ctx.children(node),\n )\n // Cosense Web と同じく記号ごとの class も出す。意味を持たない記号を userCSS で\n // 拾えるようにするためのもので、`classNames.decoration` とは別に常に付く。\n const names = joinClasses(\n ctx.options.classNames.decoration,\n ...node.markers.map((marker) => `deco-${marker}`),\n )\n return [element(\"span\", withClass(names, { dataSizeLevel: positive(node.sizeLevel) }), inner)]\n },\n} satisfies HastHandlers\n\n/** オプションの既定値を埋める。 */\nconst resolveOptions = (options: HastRenderOptions): ResolvedHastOptions => ({\n pageUrl: options.pageUrl ?? defaultPageUrl,\n iconImageUrl: options.iconImageUrl ?? (() => null),\n highlight: options.highlight ?? null,\n classNames: { ...defaultClassNames, ...options.classNames },\n showPads: options.showPads === true,\n})\n\n/** ハンドラや拡張の戻り値 (1 つか配列) を、要素の中身の配列にする。 */\nconst contentsOfResult = (result: HastContent | HastContent[]): ElementContent[] =>\n asElementContents(Array.isArray(result) ? result : [result])\n\n/**\n * ノード型で表 (ハンドラや拡張) を引く。\n * 表の関数の型はキーごとに違うが、ノードの `type` でキーを引いている以上一致するので、型はここ 1 か所で合わせる。\n */\nconst entryOf = <F>(table: object, type: AnyNodeType): Option.Option<F> =>\n Option.fromNullable((table as Readonly<Record<string, F | undefined>>)[type])\n\n/**\n * ページ (または任意のノード) を hast にする。\n *\n * 1 つのノードは「既定のハンドラ (`handlers` があれば置き換え)」で作り、`extensions` を並べた順に通す。\n * テキストは hast のテキストノードに入れるだけなので、エスケープは文字列にする側 (`toHtml`) が行う。\n * `javascript:` のようなスキームの URL は属性ごと落とす。\n * ハンドラの無いノード型 (拡張が足した独自ノード) は、中身を落とさずに子だけを出す。\n */\nexport const toHast = (node: AnyNode, options: HastOptions = {}): Root => {\n const handlers: HastHandlers = { ...defaultHastHandlers, ...options.handlers }\n const extensions = options.extensions ?? []\n\n const resolved = resolveOptions(options)\n\n const render = (target: AnyNode, ctx: HastContext): ElementContent[] =>\n pipe(\n entryOf<HastHandler<AnyNodeType>>(handlers, target.type),\n Option.match({\n onNone: () => ctx.children(target),\n onSome: (handler) => contentsOfResult(handler(target, ctx)),\n }),\n )\n\n const extend = (target: AnyNode, output: ElementContent[], ctx: HastContext): ElementContent[] =>\n extensions.reduce(\n (current, extension) =>\n pipe(\n entryOf<RenderTransform<AnyNodeType>>(extension, target.type),\n Option.match({\n onNone: () => current,\n onSome: (transform) => contentsOfResult(transform(current, target, ctx)),\n }),\n ),\n output,\n )\n\n /**\n * `target` を描くときの文脈。`ctx.node` / `ctx.children` で描くノードは、\n * 渡したノードに依らず `target` の下にあるものとして描く。\n * ハンドラが中身を差し替えた写し (`{ ...node, children }`) を渡しても、祖先が重ならないようにするため。\n */\n const contextOf = (target: AnyNode, ancestors: readonly AnyNode[]): HastContext => {\n const inside = [...ancestors, target]\n return {\n ancestors,\n node: (child) => compile(child, inside),\n children: (parent) => childrenOf(parent).flatMap((child) => compile(child, inside)),\n options: resolved,\n }\n }\n\n const compile = (target: AnyNode, ancestors: readonly AnyNode[]): ElementContent[] => {\n const ctx = contextOf(target, ancestors)\n return extend(target, render(target, ctx), ctx)\n }\n\n return { type: \"root\", children: compile(node, []) }\n}\n","/**\n * to-html.ts — AST を HTML の文字列にする。\n *\n * `toHast` で hast にしてから文字列にするだけの近道。描画の規則は `toHast` にしか持たない。\n * JSX など HTML 以外の出力とずれないようにするため。\n */\nimport type { Root } from \"hast\"\nimport { toHtml as hastToHtml } from \"hast-util-to-html\"\n\nimport type { AnyNode } from \"../types\"\nimport { type HastContent, type HastHighlighter, type HastOptions, toHast } from \"./to-hast\"\n\nconst HTML_ESCAPES: Readonly<Record<string, string>> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n}\n\n/**\n * `& < > \" '` を実体参照に置き換える。テキストと属性値のどちらにも使える。\n * `highlight` に HTML の文字列を返すハイライタを渡すとき、色付けしない部分のエスケープに使う。\n */\nexport const escapeHtml = (value: string): string =>\n value.replace(/[&<>\"']/g, (char) => HTML_ESCAPES[char] ?? char)\n\n/**\n * コードブロックの中身を色付けする。`toHast` の `highlight` に加えて、HTML の文字列も返せる。\n * **文字列はそのまま埋め込まれる**ため、エスケープは実装側の責任になる。\n * highlight.js や Prism のように HTML の文字列を返すハイライタを、そのまま渡せるようにするため。\n */\nexport type Highlighter = (code: string, language: string) => string | Root | HastContent[] | null\n\nexport interface HtmlOptions extends Omit<HastOptions, \"highlight\"> {\n /** コードブロックの中身の色付け。 */\n readonly highlight?: Highlighter\n /**\n * 出力の先頭に `<style>` 要素として差し込む CSS。\n *\n * @defaultValue undefined。`<style>` を出さない\n */\n readonly style?: string\n}\n\n/** 文字列を返すハイライタを、hast の raw ノードを返す形にする。 */\nconst asHastHighlighter =\n (highlight: Highlighter): HastHighlighter =>\n (code, language) => {\n const result = highlight(code, language)\n return typeof result === \"string\" ? [{ type: \"raw\", value: result }] : result\n }\n\n/**\n * ページ (または任意のノード) を HTML 文字列にする。`hast-util-to-html(toHast(node, options))` と同じ。\n *\n * テキストと属性値はエスケープし、`javascript:` のようなスキームの URL は属性ごと落とす。\n * `highlight` が返した文字列と、`handlers` が返した raw ノードはそのまま埋め込むので、\n * そこでのエスケープは書いた人の責任になる。\n */\nexport const toHtml = (node: AnyNode, options: HtmlOptions = {}): string => {\n const { highlight, style, ...rest } = options\n const hast = toHast(node, {\n ...rest,\n ...(highlight === undefined ? {} : { highlight: asHastHighlighter(highlight) }),\n })\n const html = hastToHtml(hast, {\n // highlight の文字列や handlers の raw ノードを、書いた人の意図どおり HTML として入れる。\n allowDangerousHtml: true,\n characterReferences: { useNamedReferences: true },\n })\n return style === undefined ? html : `<style>${style}</style>${html}`\n}\n","/**\n * code-line-numbers.ts — コードブロックに行番号を付ける描画の拡張。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { ElementContent } from \"hast\"\n\nimport type { RenderExtension } from \"./to-hast\"\n\n/** 行の要素に行番号と桁数を付ける。要素でないもの (テキストなど) には付ける場所が無いのでそのまま。 */\nconst withLineNumber =\n (digits: number) =>\n (line: ElementContent, number: number): ElementContent =>\n Match.value(line).pipe(\n Match.when({ type: \"element\" }, (element) => ({\n ...element,\n properties: { ...element.properties, dataLine: number, dataLineDigits: digits },\n })),\n Match.orElse(() => line),\n )\n\n/**\n * コードブロックの出力 (先頭がヘッダ行、続いて本体行) に行番号を付ける。\n * 本体行が行の数だけ並んでいるときだけ付ける。ひと塊にまとめた出力や、\n * `handlers` が別の形に置き換えた出力では、行と番号が対応しないため。\n */\nconst numberLines = (output: ElementContent[], count: number): ElementContent[] =>\n pipe(\n Option.some(output),\n Option.filter((lines) => lines.length === count + 1),\n Option.map(([header, ...body]) => {\n // 番号の欄の幅を CSS が決められるよう、ブロックで一番大きい番号の桁数を全行に付ける。\n const number = withLineNumber(String(count).length)\n return [\n ...Option.toArray(Option.fromNullable(header)),\n ...body.map((line, index) => number(line, index + 1)),\n ]\n }),\n Option.getOrElse(() => output),\n )\n\n/**\n * コードブロックの本体行に、1 から数えた行番号 (`data-line`) と桁数 (`data-line-digits`) を付ける拡張。\n *\n * ```ts\n * toHtml(page, { extensions: [codeLineNumbers()] })\n * ```\n *\n * 番号を見せるのは CSS の役目 (`@cosense-toolbox/style` は `data-line` を見て行の左に番号を出す)。\n * 色付けしてひと塊にまとめたブロックは、行と番号が対応しないので付けない。\n */\nexport const codeLineNumbers = (): RenderExtension => ({\n codeBlock: (output, node) => numberLines(output, node.lines.length),\n})\n","/**\n * table-cell-line-breaks.ts — テーブルのセルの中の文字列を改行にする描画の拡張。\n *\n * Cosense のセルには改行を書けないので、`\\n` のような文字の並びを代わりに書いておき、\n * 描画のときに `<br>` にする。記法ではなく見た目の約束なので、パーサーではなく描画の拡張にしている。\n */\nimport { Match, Option, pipe } from \"effect\"\nimport type { Element, ElementContent, Text } from \"hast\"\n\nimport type { AnyNode } from \"../types\"\nimport type { RenderExtension } from \"./to-hast\"\n\nconst lineBreak = (): Element => ({ type: \"element\", tagName: \"br\", properties: {}, children: [] })\n\n/** 空でない文字列だけを text にする。区切った部分が空なら何も出さない。 */\nconst textOf = (value: string): Option.Option<Text> =>\n pipe(\n Option.some(value),\n Option.filter((part) => part !== \"\"),\n Option.map((part): Text => ({ type: \"text\", value: part })),\n )\n\n/** 文字列を `marker` で区切り、間に `<br>` を挟む。 */\nconst breakText = (value: string, marker: string): ElementContent[] =>\n value\n .split(marker)\n .flatMap((part, index): ElementContent[] => [\n ...(index === 0 ? [] : [lineBreak()]),\n ...Option.toArray(textOf(part)),\n ])\n\n/** 出力のうち text だけを区切る。`handlers.text` が要素を返したときは、そのまま残す。 */\nconst breakContent =\n (marker: string) =>\n (content: ElementContent): ElementContent[] =>\n Match.value(content).pipe(\n Match.when({ type: \"text\" }, (text) => breakText(text.value, marker)),\n Match.orElse((other) => [other]),\n )\n\nconst isTableCell = (node: AnyNode): boolean => node.type === \"tableCell\"\n\n/**\n * テーブルのセルの中の `marker` を `<br>` にする描画の拡張。\n *\n * 当てるのは、セルの中にある AST の text ノード (記法の外の地の文) だけ。\n * コード・数式・リンクの表示は text ノードではないので、`classNames` や `handlers` で\n * 出力を変えても、その中には当たらない。装飾の中身は text ノードなので当たる。\n * `marker` は文字列そのままで探し (正規表現としては読まない)、区切った文字は通常どおりエスケープされる。\n * `marker` が空文字なら何もしない。\n *\n * @example\n * toHtml(page, { extensions: [tableCellLineBreaks('\\\\n')] })\n */\nexport const tableCellLineBreaks = (marker: string): RenderExtension => ({\n text: (output, _node, ctx) =>\n pipe(\n Option.some(marker),\n Option.filter((value) => value !== \"\"),\n Option.filter(() => ctx.ancestors.some(isTableCell)),\n Option.match({\n onNone: () => output,\n onSome: (value) => output.flatMap(breakContent(value)),\n }),\n ),\n})\n"],"mappings":"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"}
|
package/dist/html.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as TableCell, C as NodeOfType, D as ProjectLink, M as TextNode, N as TitleBlock, b as InternalLink, c as CodeLine, d as FormulaNode, f as Hashtag, h as InlineCode, j as TableRow, k as TableBlock, l as Decoration, m as ImageNode, p as IconNode, r as AnyNodeType, s as CodeBlock, t as AnyNode, u as ExternalLink, w as Page, x as LineBlock } from "./types-
|
|
2
|
-
import "./index-
|
|
1
|
+
import { A as TableCell, C as NodeOfType, D as ProjectLink, M as TextNode, N as TitleBlock, b as InternalLink, c as CodeLine, d as FormulaNode, f as Hashtag, h as InlineCode, j as TableRow, k as TableBlock, l as Decoration, m as ImageNode, p as IconNode, r as AnyNodeType, s as CodeBlock, t as AnyNode, u as ExternalLink, w as Page, x as LineBlock } from "./types-D2Q7NiCb.cjs";
|
|
2
|
+
import "./index-BU-dd6fK.cjs";
|
|
3
3
|
import { Element, ElementContent, Root, Text } from "hast";
|
|
4
4
|
//#region src/html/to-hast.d.ts
|
|
5
5
|
/** `href` に入れて安全な URL だけを返す。script が動くスキームなら null。 */
|
|
@@ -18,8 +18,14 @@ interface HtmlClassNames {
|
|
|
18
18
|
readonly line?: string;
|
|
19
19
|
/** 引用行の `<blockquote>` */
|
|
20
20
|
readonly quote?: string;
|
|
21
|
-
/**
|
|
21
|
+
/** コマンドの行 (`$ ls`) の `<code>` */
|
|
22
22
|
readonly monospace?: string;
|
|
23
|
+
/** コマンドの行の先頭の `$` / `%` */
|
|
24
|
+
readonly commandPrefix?: string;
|
|
25
|
+
/** コマンドの行の、記号の後の空白 */
|
|
26
|
+
readonly commandSpace?: string;
|
|
27
|
+
/** コマンドの行の、空白の後のコマンド */
|
|
28
|
+
readonly command?: string;
|
|
23
29
|
/** コードブロックに属する行 (ヘッダ行と本体行の両方) */
|
|
24
30
|
readonly codeBlock?: string;
|
|
25
31
|
/** ヘッダ行の `<code>` */
|
package/dist/html.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.cts","names":[],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"html.d.cts","names":[],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"mappings":";;;;;cAiDa,WAAY;;cAGZ,UAAW;;KAcZ,cAAc,eAAe,cAAc,UAAU;;;;;UAMhD;WACN;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;WACA;;WAEA;;WAEA;;WAEA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;cAIE,mBAAmB;;;;;;UAkCf;WACN;WACA;;;KAIC,cAAc,iBAAiB;;;;;;;;;;;KAY/B,mBAAmB,cAAc,qBAAqB,OAAO;;;;;;;UAQxD;;;;;;;;;;WAUN,WAAW,eAAe,MAAM;;;;;;;;WAQhC,gBAAgB,MAAM;;;;;WAKtB,YAAY;;WAEZ,aAAa;;;;;;;WAOb;;;UAIM;WACN,UAAU,eAAe,MAAM;WAC/B,eAAe,MAAM;WACrB,WAAW;;WAEX,YAAY;WACZ;;;UAIM;;;;;WAKN,oBAAoB;;WAEpB,OAAO,MAAM,YAAY;;WAEzB,WAAW,MAAM,YAAY;WAC7B,SAAS;;KAGR,YAAY,UAAU,gBAChC,MAAM,WAAW,IACjB,KAAK,gBACF,cAAc;;;;;KAMP,2BACA,KAAK,eAAe,YAAY;;;;;;;KAShC,gBAAgB,UAAU,gBACpC,QAAQ,kBACR,MAAM,WAAW,IACjB,KAAK,gBACF,cAAc;;;;;KAMP,8BACA,KAAK,eAAe,gBAAgB;UAG/B,oBAAoB;;;;;WAK1B,WAAW;;;;;WAKX,sBAAsB;;;;;;;cAQpB,iBAAkB;;;;;cASlB,iBAAkB;;;;;;;;cAqRlB;eAAA;;;EA3DY,YAAA,MAAA,WAAS,KAAO,gBAAc;mBAAA;;;;;;;;;;;;;;;;;;;;;;;cA4N1C,SAAU,MAAM,SAAS,UAAS,gBAAmB;;;;;;;cCpsBrD,aAAc;;;;;;KAQf,eAAe,cAAc,8BAA8B,OAAO;UAE7D,oBAAoB,KAAK;;WAE/B,YAAY;;;;;;WAMZ;;;;;;;;;cAkBE,SAAU,MAAM,SAAS,UAAS;;;;;;;;;;;;;cCVlC,uBAAsB;;;;;;;;;;;;;;;cCItB,sBAAuB,mBAAiB"}
|
package/dist/html.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as TableCell, C as NodeOfType, D as ProjectLink, M as TextNode, N as TitleBlock, b as InternalLink, c as CodeLine, d as FormulaNode, f as Hashtag, h as InlineCode, j as TableRow, k as TableBlock, l as Decoration, m as ImageNode, p as IconNode, r as AnyNodeType, s as CodeBlock, t as AnyNode, u as ExternalLink, w as Page, x as LineBlock } from "./types-
|
|
2
|
-
import "./index-
|
|
1
|
+
import { A as TableCell, C as NodeOfType, D as ProjectLink, M as TextNode, N as TitleBlock, b as InternalLink, c as CodeLine, d as FormulaNode, f as Hashtag, h as InlineCode, j as TableRow, k as TableBlock, l as Decoration, m as ImageNode, p as IconNode, r as AnyNodeType, s as CodeBlock, t as AnyNode, u as ExternalLink, w as Page, x as LineBlock } from "./types-D2Q7NiCb.mjs";
|
|
2
|
+
import "./index-DQ3C3Rsq.mjs";
|
|
3
3
|
import { Element, ElementContent, Root, Text } from "hast";
|
|
4
4
|
//#region src/html/to-hast.d.ts
|
|
5
5
|
/** `href` に入れて安全な URL だけを返す。script が動くスキームなら null。 */
|
|
@@ -18,8 +18,14 @@ interface HtmlClassNames {
|
|
|
18
18
|
readonly line?: string;
|
|
19
19
|
/** 引用行の `<blockquote>` */
|
|
20
20
|
readonly quote?: string;
|
|
21
|
-
/**
|
|
21
|
+
/** コマンドの行 (`$ ls`) の `<code>` */
|
|
22
22
|
readonly monospace?: string;
|
|
23
|
+
/** コマンドの行の先頭の `$` / `%` */
|
|
24
|
+
readonly commandPrefix?: string;
|
|
25
|
+
/** コマンドの行の、記号の後の空白 */
|
|
26
|
+
readonly commandSpace?: string;
|
|
27
|
+
/** コマンドの行の、空白の後のコマンド */
|
|
28
|
+
readonly command?: string;
|
|
23
29
|
/** コードブロックに属する行 (ヘッダ行と本体行の両方) */
|
|
24
30
|
readonly codeBlock?: string;
|
|
25
31
|
/** ヘッダ行の `<code>` */
|
package/dist/html.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.mts","names":[],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"html.d.mts","names":[],"sources":["../src/html/to-hast.ts","../src/html/to-html.ts","../src/html/code-line-numbers.ts","../src/html/table-cell-line-breaks.ts"],"mappings":";;;;;cAiDa,WAAY;;cAGZ,UAAW;;KAcZ,cAAc,eAAe,cAAc,UAAU;;;;;UAMhD;WACN;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;WACA;;WAEA;;WAEA;;WAEA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;;cAIE,mBAAmB;;;;;;UAkCf;WACN;WACA;;;KAIC,cAAc,iBAAiB;;;;;;;;;;;KAY/B,mBAAmB,cAAc,qBAAqB,OAAO;;;;;;;UAQxD;;;;;;;;;;WAUN,WAAW,eAAe,MAAM;;;;;;;;WAQhC,gBAAgB,MAAM;;;;;WAKtB,YAAY;;WAEZ,aAAa;;;;;;;WAOb;;;UAIM;WACN,UAAU,eAAe,MAAM;WAC/B,eAAe,MAAM;WACrB,WAAW;;WAEX,YAAY;WACZ;;;UAIM;;;;;WAKN,oBAAoB;;WAEpB,OAAO,MAAM,YAAY;;WAEzB,WAAW,MAAM,YAAY;WAC7B,SAAS;;KAGR,YAAY,UAAU,gBAChC,MAAM,WAAW,IACjB,KAAK,gBACF,cAAc;;;;;KAMP,2BACA,KAAK,eAAe,YAAY;;;;;;;KAShC,gBAAgB,UAAU,gBACpC,QAAQ,kBACR,MAAM,WAAW,IACjB,KAAK,gBACF,cAAc;;;;;KAMP,8BACA,KAAK,eAAe,gBAAgB;UAG/B,oBAAoB;;;;;WAK1B,WAAW;;;;;WAKX,sBAAsB;;;;;;;cAQpB,iBAAkB;;;;;cASlB,iBAAkB;;;;;;;;cAqRlB;eAAA;;;EA3DY,YAAA,MAAA,WAAS,KAAO,gBAAc;mBAAA;;;;;;;;;;;;;;;;;;;;;;;cA4N1C,SAAU,MAAM,SAAS,UAAS,gBAAmB;;;;;;;cCpsBrD,aAAc;;;;;;KAQf,eAAe,cAAc,8BAA8B,OAAO;UAE7D,oBAAoB,KAAK;;WAE/B,YAAY;;;;;;WAMZ;;;;;;;;;cAkBE,SAAU,MAAM,SAAS,UAAS;;;;;;;;;;;;;cCVlC,uBAAsB;;;;;;;;;;;;;;;cCItB,sBAAuB,mBAAiB"}
|
package/dist/html.mjs
CHANGED
|
@@ -36,6 +36,9 @@ const defaultClassNames = {
|
|
|
36
36
|
line: "line",
|
|
37
37
|
quote: "quote",
|
|
38
38
|
monospace: "monospace",
|
|
39
|
+
commandPrefix: "prefix",
|
|
40
|
+
commandSpace: "space",
|
|
41
|
+
command: "command",
|
|
39
42
|
codeBlock: "code-block",
|
|
40
43
|
codeStart: "code-start",
|
|
41
44
|
codeFilename: "code-block-start",
|
|
@@ -126,6 +129,16 @@ const contentsOf = (result) => Array.isArray(result) ? asElementContents(result)
|
|
|
126
129
|
const isBlank = (node) => node.type === "text" && node.value.trim() === "";
|
|
127
130
|
/** 空白だけのテキストを除いた、ただ 1 つの子。 */
|
|
128
131
|
const onlyChildOf = (children) => pipe(Option.some(children.filter((child) => !isBlank(child))), Option.filter((meaningful) => meaningful.length === 1), Option.flatMapNullable((meaningful) => meaningful[0]));
|
|
132
|
+
/**
|
|
133
|
+
* コマンドの行 (`$ ls`) を、Cosense Web と同じく記号・空白・コマンドの 3 つの要素に分ける。
|
|
134
|
+
* 記号とコマンドを別に選べるので、記号を薄くしたり、コマンドだけをコピーさせたりできる。
|
|
135
|
+
* パーサーはコマンドの行を書いたままの文字 1 つにするので、そうでなければ (拡張が置き換えたなど) 分けない。
|
|
136
|
+
*/
|
|
137
|
+
const commandParts = (node, cls) => pipe(Option.liftPredicate(node.children, (children) => children.length === 1), Option.flatMapNullable(([child]) => child), Option.filter((child) => child.type === "text"), Option.map(({ value }) => [
|
|
138
|
+
element("span", withClass(cls.commandPrefix), [text(value.slice(0, 1))]),
|
|
139
|
+
element("span", withClass(cls.commandSpace), [text(value.slice(1, 2))]),
|
|
140
|
+
element("span", withClass(cls.command), [text(value.slice(2))])
|
|
141
|
+
]));
|
|
129
142
|
const elementNamed = (tagName) => (node) => Option.liftPredicate(node, (content) => content.type === "element" && content.tagName === tagName);
|
|
130
143
|
/** hast の決まりでは className は配列だが、shiki は class を文字列で付ける。どちらも読む。 */
|
|
131
144
|
const classesOf = (value) => Match.value(value).pipe(Match.when(Array.isArray, (names) => names.map(String)), Match.when(Match.string, classList), Match.orElse(() => []));
|
|
@@ -205,8 +218,7 @@ const defaultHastHandlers = {
|
|
|
205
218
|
title: (node, ctx) => [element("h1", withClass(ctx.options.classNames.title), ctx.children(node))],
|
|
206
219
|
line: (node, ctx) => {
|
|
207
220
|
const cls = ctx.options.classNames;
|
|
208
|
-
const
|
|
209
|
-
const styled = node.monospace ? [element("code", withClass(cls.monospace), body)] : body;
|
|
221
|
+
const styled = node.monospace ? [element("code", withClass(cls.monospace), Option.getOrElse(commandParts(node, cls), () => ctx.children(node)))] : ctx.children(node);
|
|
210
222
|
const quoted = node.quote ? [element("blockquote", withClass(cls.quote), styled)] : styled;
|
|
211
223
|
const inner = quoted.length === 0 ? [element("br", {})] : quoted;
|
|
212
224
|
const mark = ctx.options.showPads && node.indent > 0 ? [indentMark(node.indent, cls)] : [];
|