@cosense-toolbox/parser 0.1.0-beta.2 → 0.1.0-beta.3
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 +10 -4
- package/dist/extensions.cjs +0 -15
- package/dist/extensions.cjs.map +1 -1
- package/dist/extensions.d.cts +1 -10
- package/dist/extensions.d.cts.map +1 -1
- package/dist/extensions.d.mts +1 -10
- package/dist/extensions.d.mts.map +1 -1
- package/dist/extensions.mjs +1 -15
- package/dist/extensions.mjs.map +1 -1
- package/dist/index.cjs +88 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +78 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
- package/dist/decoration-BB1cS4lH.mjs +0 -72
- package/dist/decoration-BB1cS4lH.mjs.map +0 -1
- package/dist/decoration-BYXIXxCA.cjs +0 -107
- package/dist/decoration-BYXIXxCA.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,15 @@ Cosense (旧 Scrapbox) の記法を、位置情報つきの AST に変換する
|
|
|
12
12
|
|
|
13
13
|
> **beta**:公開 API はまだ変わりうる。安定するまではバージョンを固定して使うほうが安全。
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### 0.1.0-beta.3 の変更
|
|
16
|
+
|
|
17
|
+
- Cosense の文字装飾の記号 `!"#%&'()*+,-./{|}<>_~=` からなる並びを、**拡張なしで**すべて装飾として読むようにした。
|
|
18
|
+
Cosense Web と同じ読み方で、`[! 注意]` は内部リンクではなく `markers: ['!']` の装飾になる。
|
|
19
|
+
見た目 (bold などのフラグ) が付くのは今までどおり `* / - _` だけ。ほかの記号の見た目は CSS で付ける。
|
|
20
|
+
- **`customDecorations` を削除した**。集合の中の記号は既定で読むので、この拡張で足せるのは
|
|
21
|
+
Cosense Web が装飾にしない記号だけになり、Web 版と違う AST を作るため。渡していた箇所は消すだけでよい。
|
|
22
|
+
|
|
23
|
+
### 0.1.0-beta.2 の変更
|
|
16
24
|
|
|
17
25
|
- 記法の拡張 (`InlineConstruct` / `BracketRule`) は、成立しなければ **`null` を返す**普通の関数になった。
|
|
18
26
|
これまでは effect の `Option` を返す必要があり、拡張を書くのに effect が要った。
|
|
@@ -30,8 +38,6 @@ beta.0 から上げるときは次の 2 点に注意。
|
|
|
30
38
|
出現順・重複なしで入る。`toHtml` はこれを `deco-*` のような class として出す。
|
|
31
39
|
装飾ノードを自分で組み立てている拡張は追随が要る。
|
|
32
40
|
|
|
33
|
-
記号を増やす [`customDecorations`](https://cosense-toolbox.qaynam.dev/parser/extend/) も足した。
|
|
34
|
-
|
|
35
41
|
## インストール
|
|
36
42
|
|
|
37
43
|
```sh
|
|
@@ -65,7 +71,7 @@ toHtml(page) // → '<div class="page"><h1 class="title">今日のメモ</h1>…
|
|
|
65
71
|
| `@cosense-toolbox/parser/utils` | ヘルパー。AST から取り出す | `visit` `find` `collect` `collectLinks` `firstImage` `rawTextOf` |
|
|
66
72
|
| `@cosense-toolbox/parser/html` | AST を HTML 系の出力 (hast と HTML の文字列) にする | `toHast` `toHtml` `codeLineNumbers` `tableCellLineBreaks` |
|
|
67
73
|
| `@cosense-toolbox/parser/compile` | AST を HTML 以外の形式にする | `toPlainText` `createCompiler` |
|
|
68
|
-
| `@cosense-toolbox/parser/extensions` | 記法を足す | `Extension` `InlineConstruct` `BracketRule` `
|
|
74
|
+
| `@cosense-toolbox/parser/extensions` | 記法を足す | `Extension` `InlineConstruct` `BracketRule` `tableCellNotation` |
|
|
69
75
|
| `@cosense-toolbox/parser/schema` | 外から来た値を検証する | `decodePage` |
|
|
70
76
|
|
|
71
77
|
各 API の詳細はドキュメントにある。
|
package/dist/extensions.cjs
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_decoration = require("./decoration-BYXIXxCA.cjs");
|
|
3
|
-
let effect = require("effect");
|
|
4
|
-
//#region src/extensions/custom-decoration.ts
|
|
5
|
-
/**
|
|
6
|
-
* 文字装飾記法として読む記号を増やす拡張。
|
|
7
|
-
*
|
|
8
|
-
* 既定では `* / - _` の 4 つだけを装飾として読み、それ以外は内部リンクになる。
|
|
9
|
-
* 渡した記号は既定の記号と混ぜられ、`[*' x]` は太字かつ `markers: ['*', "'"]` になる。
|
|
10
|
-
*/
|
|
11
|
-
const customDecorations = (markers) => {
|
|
12
|
-
const rule = require_decoration.buildDecorationRule(require_decoration.OFFICIAL_MARKERS + markers.join(""));
|
|
13
|
-
return { bracketRules: [(inner, ctx) => effect.Option.getOrNull(rule(inner, ctx))] };
|
|
14
|
-
};
|
|
15
|
-
//#endregion
|
|
16
2
|
//#region src/extensions/table-cell-notation.ts
|
|
17
3
|
/**
|
|
18
4
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
|
@@ -26,7 +12,6 @@ const customDecorations = (markers) => {
|
|
|
26
12
|
*/
|
|
27
13
|
const tableCellNotation = (types) => ({ keepInTableCell: types === void 0 ? () => true : (node) => types.includes(node.type) });
|
|
28
14
|
//#endregion
|
|
29
|
-
exports.customDecorations = customDecorations;
|
|
30
15
|
exports.tableCellNotation = tableCellNotation;
|
|
31
16
|
|
|
32
17
|
//# sourceMappingURL=extensions.cjs.map
|
package/dist/extensions.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.cjs","names":[
|
|
1
|
+
{"version":3,"file":"extensions.cjs","names":[],"sources":["../src/extensions/table-cell-notation.ts"],"sourcesContent":["import type { Extension } from \"../inline/types\"\nimport type { InlineNode } from \"../types\"\n\n/**\n * テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。\n *\n * 既定ではセルの中はリンクの記法だけを読み、それ以外は書いたままの文字になる (Cosense Web と同じ)。\n * `types` を渡すと、リンクに加えてその型のノードだけを読む。省くとすべての記法 (拡張の記法も含む) を読む。\n *\n * @example\n * parse(source, { extensions: [tableCellNotation()] }) // すべての記法\n * parse(source, { extensions: [tableCellNotation(['decoration'])] }) // リンクと装飾だけ\n */\nexport const tableCellNotation = (types?: readonly InlineNode[\"type\"][]): Extension => ({\n keepInTableCell: types === undefined ? () => true : (node) => types.includes(node.type),\n})\n"],"mappings":";;;;;;;;;;;;AAaA,MAAa,qBAAqB,WAAsD,EACtF,iBAAiB,UAAU,KAAA,UAAkB,QAAQ,SAAS,MAAM,SAAS,KAAK,IAAI,EACxF"}
|
package/dist/extensions.d.cts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-gWMrsXPX.cjs";
|
|
2
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-_-ZAyNkz.cjs";
|
|
3
|
-
//#region src/extensions/custom-decoration.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* 文字装飾記法として読む記号を増やす拡張。
|
|
6
|
-
*
|
|
7
|
-
* 既定では `* / - _` の 4 つだけを装飾として読み、それ以外は内部リンクになる。
|
|
8
|
-
* 渡した記号は既定の記号と混ぜられ、`[*' x]` は太字かつ `markers: ['*', "'"]` になる。
|
|
9
|
-
*/
|
|
10
|
-
declare const customDecorations: (markers: readonly string[]) => Extension;
|
|
11
|
-
//#endregion
|
|
12
3
|
//#region src/extensions/table-cell-notation.d.ts
|
|
13
4
|
/**
|
|
14
5
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
|
@@ -22,5 +13,5 @@ declare const customDecorations: (markers: readonly string[]) => Extension;
|
|
|
22
13
|
*/
|
|
23
14
|
declare const tableCellNotation: (types?: readonly InlineNode["type"][]) => Extension;
|
|
24
15
|
//#endregion
|
|
25
|
-
export { type BracketRule, type BracketRuleContext, type ConstructMatch, type Extension, type InlineConstruct, type InlineContext, type InlineNodeInit, type Origin, type WithoutPosition,
|
|
16
|
+
export { type BracketRule, type BracketRuleContext, type ConstructMatch, type Extension, type InlineConstruct, type InlineContext, type InlineNodeInit, type Origin, type WithoutPosition, tableCellNotation };
|
|
26
17
|
//# sourceMappingURL=extensions.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.d.cts","names":[],"sources":["../src/extensions/
|
|
1
|
+
{"version":3,"file":"extensions.d.cts","names":[],"sources":["../src/extensions/table-cell-notation.ts"],"mappings":";;;;;;;;;;;;;cAaa,oBAAqB,iBAAiB,yBAAuB"}
|
package/dist/extensions.d.mts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { F as WithoutPosition, _ as InlineNodeInit, g as InlineNode } from "./types-gWMrsXPX.mjs";
|
|
2
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-CdiHju1P.mjs";
|
|
3
|
-
//#region src/extensions/custom-decoration.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* 文字装飾記法として読む記号を増やす拡張。
|
|
6
|
-
*
|
|
7
|
-
* 既定では `* / - _` の 4 つだけを装飾として読み、それ以外は内部リンクになる。
|
|
8
|
-
* 渡した記号は既定の記号と混ぜられ、`[*' x]` は太字かつ `markers: ['*', "'"]` になる。
|
|
9
|
-
*/
|
|
10
|
-
declare const customDecorations: (markers: readonly string[]) => Extension;
|
|
11
|
-
//#endregion
|
|
12
3
|
//#region src/extensions/table-cell-notation.d.ts
|
|
13
4
|
/**
|
|
14
5
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
|
@@ -22,5 +13,5 @@ declare const customDecorations: (markers: readonly string[]) => Extension;
|
|
|
22
13
|
*/
|
|
23
14
|
declare const tableCellNotation: (types?: readonly InlineNode["type"][]) => Extension;
|
|
24
15
|
//#endregion
|
|
25
|
-
export { type BracketRule, type BracketRuleContext, type ConstructMatch, type Extension, type InlineConstruct, type InlineContext, type InlineNodeInit, type Origin, type WithoutPosition,
|
|
16
|
+
export { type BracketRule, type BracketRuleContext, type ConstructMatch, type Extension, type InlineConstruct, type InlineContext, type InlineNodeInit, type Origin, type WithoutPosition, tableCellNotation };
|
|
26
17
|
//# sourceMappingURL=extensions.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.d.mts","names":[],"sources":["../src/extensions/
|
|
1
|
+
{"version":3,"file":"extensions.d.mts","names":[],"sources":["../src/extensions/table-cell-notation.ts"],"mappings":";;;;;;;;;;;;;cAaa,oBAAqB,iBAAiB,yBAAuB"}
|
package/dist/extensions.mjs
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
import { n as buildDecorationRule, t as OFFICIAL_MARKERS } from "./decoration-BB1cS4lH.mjs";
|
|
2
|
-
import { Option } from "effect";
|
|
3
|
-
//#region src/extensions/custom-decoration.ts
|
|
4
|
-
/**
|
|
5
|
-
* 文字装飾記法として読む記号を増やす拡張。
|
|
6
|
-
*
|
|
7
|
-
* 既定では `* / - _` の 4 つだけを装飾として読み、それ以外は内部リンクになる。
|
|
8
|
-
* 渡した記号は既定の記号と混ぜられ、`[*' x]` は太字かつ `markers: ['*', "'"]` になる。
|
|
9
|
-
*/
|
|
10
|
-
const customDecorations = (markers) => {
|
|
11
|
-
const rule = buildDecorationRule(OFFICIAL_MARKERS + markers.join(""));
|
|
12
|
-
return { bracketRules: [(inner, ctx) => Option.getOrNull(rule(inner, ctx))] };
|
|
13
|
-
};
|
|
14
|
-
//#endregion
|
|
15
1
|
//#region src/extensions/table-cell-notation.ts
|
|
16
2
|
/**
|
|
17
3
|
* テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。
|
|
@@ -25,6 +11,6 @@ const customDecorations = (markers) => {
|
|
|
25
11
|
*/
|
|
26
12
|
const tableCellNotation = (types) => ({ keepInTableCell: types === void 0 ? () => true : (node) => types.includes(node.type) });
|
|
27
13
|
//#endregion
|
|
28
|
-
export {
|
|
14
|
+
export { tableCellNotation };
|
|
29
15
|
|
|
30
16
|
//# sourceMappingURL=extensions.mjs.map
|
package/dist/extensions.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.mjs","names":[],"sources":["../src/extensions/
|
|
1
|
+
{"version":3,"file":"extensions.mjs","names":[],"sources":["../src/extensions/table-cell-notation.ts"],"sourcesContent":["import type { Extension } from \"../inline/types\"\nimport type { InlineNode } from \"../types\"\n\n/**\n * テーブルのセルの中でも、行と同じく記法を読む拡張。Cosense Web には無い振る舞い。\n *\n * 既定ではセルの中はリンクの記法だけを読み、それ以外は書いたままの文字になる (Cosense Web と同じ)。\n * `types` を渡すと、リンクに加えてその型のノードだけを読む。省くとすべての記法 (拡張の記法も含む) を読む。\n *\n * @example\n * parse(source, { extensions: [tableCellNotation()] }) // すべての記法\n * parse(source, { extensions: [tableCellNotation(['decoration'])] }) // リンクと装飾だけ\n */\nexport const tableCellNotation = (types?: readonly InlineNode[\"type\"][]): Extension => ({\n keepInTableCell: types === undefined ? () => true : (node) => types.includes(node.type),\n})\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,qBAAqB,WAAsD,EACtF,iBAAiB,UAAU,KAAA,UAAkB,QAAQ,SAAS,MAAM,SAAS,KAAK,IAAI,EACxF"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_image_url = require("./image-url-YcTed8tg.cjs");
|
|
3
|
-
const require_decoration = require("./decoration-BYXIXxCA.cjs");
|
|
4
3
|
let effect = require("effect");
|
|
4
|
+
//#region src/core/position.ts
|
|
5
|
+
const originOfLine = (line, offset) => ({
|
|
6
|
+
line,
|
|
7
|
+
column: 0,
|
|
8
|
+
offset
|
|
9
|
+
});
|
|
10
|
+
/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */
|
|
11
|
+
const shiftOrigin = (origin, by) => ({
|
|
12
|
+
line: origin.line,
|
|
13
|
+
column: origin.column + by,
|
|
14
|
+
offset: origin.offset + by
|
|
15
|
+
});
|
|
16
|
+
const pointAt = (origin, index) => ({
|
|
17
|
+
line: origin.line,
|
|
18
|
+
column: origin.column + index,
|
|
19
|
+
offset: origin.offset + index
|
|
20
|
+
});
|
|
21
|
+
/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */
|
|
22
|
+
const spanAt = (origin, start, end) => ({
|
|
23
|
+
start: pointAt(origin, start),
|
|
24
|
+
end: pointAt(origin, end)
|
|
25
|
+
});
|
|
26
|
+
//#endregion
|
|
5
27
|
//#region src/inline/constructs/bare-url.ts
|
|
6
28
|
const URL_RE = /^https?:\/\/[^\s\]]+/i;
|
|
7
29
|
/**
|
|
@@ -54,6 +76,61 @@ const isTagBoundary = (s, index) => {
|
|
|
54
76
|
return prev === " " || prev === " " || prev === " ";
|
|
55
77
|
};
|
|
56
78
|
//#endregion
|
|
79
|
+
//#region src/inline/bracket-rules/decoration.ts
|
|
80
|
+
/**
|
|
81
|
+
* `[<記号の並び> 中身]`。記号は Cosense の文字装飾の記号 `` !"#%&'()*+,-./{|}<>_~= `` で、
|
|
82
|
+
* Cosense Web はこの集合からなる並びをすべて装飾として読む (help-jp「文字装飾記法」)。
|
|
83
|
+
* `=` は help-jp が将来の別用途のために使わないよう求めているが、今の Cosense Web は
|
|
84
|
+
* 装飾として読むので含める。
|
|
85
|
+
* どの記号が装飾になるかは構文なので、設定で変えられるようにしない。
|
|
86
|
+
* `$` と `[` はこの一覧に無い。`[$ x]` は数式、`[[x]]` は太字の別の記法で、どちらも別のルールが読む。
|
|
87
|
+
*
|
|
88
|
+
* 文字列から組み立てずに正規表現のリテラルで持つのは、トップレベルで関数を呼ばないため
|
|
89
|
+
* (`sideEffects: false`)。
|
|
90
|
+
*/
|
|
91
|
+
const DECORATION_PATTERN = /^([!"#%&'()*+,\-./{|}<>_~=]+)\s+([\s\S]+)$/;
|
|
92
|
+
/**
|
|
93
|
+
* 見た目が決まっている記号。bold などのフラグはこれだけから決める。
|
|
94
|
+
* ほかの記号は `markers` に残るだけで、見た目はプロジェクトの UserCSS が付ける。
|
|
95
|
+
*/
|
|
96
|
+
const STYLED_MARKERS = {
|
|
97
|
+
bold: "*",
|
|
98
|
+
italic: "/",
|
|
99
|
+
strike: "-",
|
|
100
|
+
underline: "_"
|
|
101
|
+
};
|
|
102
|
+
const MAX_SIZE_LEVEL = 4;
|
|
103
|
+
/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */
|
|
104
|
+
const uniqueChars = (marks) => [...new Set(marks)];
|
|
105
|
+
/**
|
|
106
|
+
* `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)、
|
|
107
|
+
* および見た目の付かない記号の装飾 (`[! 注意]`)。
|
|
108
|
+
*
|
|
109
|
+
* 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。
|
|
110
|
+
* そのため子の走査は allowDecoration=false で行う。
|
|
111
|
+
* 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
|
|
112
|
+
*/
|
|
113
|
+
const decorationRule = (inner, ctx) => {
|
|
114
|
+
if (!ctx.allowDecoration) return effect.Option.none();
|
|
115
|
+
const match = inner.match(DECORATION_PATTERN);
|
|
116
|
+
if (!match) return effect.Option.none();
|
|
117
|
+
const marks = match[1] ?? "";
|
|
118
|
+
const value = match[2] ?? "";
|
|
119
|
+
const stars = [...marks].filter((mark) => mark === STYLED_MARKERS.bold).length;
|
|
120
|
+
const valueOffset = inner.length - value.length;
|
|
121
|
+
return effect.Option.some({
|
|
122
|
+
type: "decoration",
|
|
123
|
+
value,
|
|
124
|
+
markers: uniqueChars(marks),
|
|
125
|
+
bold: stars > 0,
|
|
126
|
+
italic: marks.includes(STYLED_MARKERS.italic),
|
|
127
|
+
strike: marks.includes(STYLED_MARKERS.strike),
|
|
128
|
+
underline: marks.includes(STYLED_MARKERS.underline),
|
|
129
|
+
sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
|
|
130
|
+
children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
57
134
|
//#region src/inline/bracket-rules/formula.ts
|
|
58
135
|
/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */
|
|
59
136
|
const formulaRule = (inner) => inner.startsWith("$") ? effect.Option.some({
|
|
@@ -186,7 +263,7 @@ const urlRule = (inner) => {
|
|
|
186
263
|
//#endregion
|
|
187
264
|
//#region src/inline/bracket-rules/index.ts
|
|
188
265
|
/** 中身に角括弧を含んでいても成立しうるルール。 */
|
|
189
|
-
const bracketRules = [formulaRule,
|
|
266
|
+
const bracketRules = [formulaRule, decorationRule];
|
|
190
267
|
/**
|
|
191
268
|
* 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。
|
|
192
269
|
* これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。
|
|
@@ -219,7 +296,7 @@ const bracketConstruct = (source, index, ctx) => {
|
|
|
219
296
|
if (inner.trim() === "") return effect.Option.none();
|
|
220
297
|
const innerCtx = {
|
|
221
298
|
...ctx,
|
|
222
|
-
innerOrigin:
|
|
299
|
+
innerOrigin: shiftOrigin(ctx.origin, index + 1)
|
|
223
300
|
};
|
|
224
301
|
return (0, effect.pipe)(parseInner(inner, innerCtx), effect.Option.map((node) => ({
|
|
225
302
|
node,
|
|
@@ -295,7 +372,7 @@ const strongBracketConstruct = (source, index, ctx) => {
|
|
|
295
372
|
strike: false,
|
|
296
373
|
underline: false,
|
|
297
374
|
sizeLevel: 0,
|
|
298
|
-
children: ctx.tokenize(inner,
|
|
375
|
+
children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false)
|
|
299
376
|
},
|
|
300
377
|
length
|
|
301
378
|
});
|
|
@@ -362,7 +439,7 @@ const scan = (source, origin, allowDecoration, rules) => {
|
|
|
362
439
|
out.push({
|
|
363
440
|
type: "text",
|
|
364
441
|
value: source.slice(textStart, end),
|
|
365
|
-
position:
|
|
442
|
+
position: spanAt(origin, textStart, end)
|
|
366
443
|
});
|
|
367
444
|
};
|
|
368
445
|
while (index < source.length) {
|
|
@@ -373,7 +450,7 @@ const scan = (source, origin, allowDecoration, rules) => {
|
|
|
373
450
|
}
|
|
374
451
|
flushText(index);
|
|
375
452
|
const { node, length } = match.value;
|
|
376
|
-
out.push(withPosition(node,
|
|
453
|
+
out.push(withPosition(node, spanAt(origin, index, index + length)));
|
|
377
454
|
index += length;
|
|
378
455
|
textStart = index;
|
|
379
456
|
}
|
|
@@ -456,8 +533,8 @@ const indentOf = (raw) => leadingWhitespace(raw).length;
|
|
|
456
533
|
* この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる
|
|
457
534
|
* (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。
|
|
458
535
|
*/
|
|
459
|
-
const lineOrigin = (line) =>
|
|
460
|
-
const wholeLine = (line) =>
|
|
536
|
+
const lineOrigin = (line) => originOfLine(line.index, line.offset);
|
|
537
|
+
const wholeLine = (line) => spanAt(lineOrigin(line), 0, line.text.length);
|
|
461
538
|
/** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */
|
|
462
539
|
const dedent = (text, amount) => text.slice(Math.min(amount, indentOf(text)));
|
|
463
540
|
const titleBlock = (line, tokenize) => ({
|
|
@@ -492,8 +569,8 @@ const tableCells = (line, tokenize) => {
|
|
|
492
569
|
return {
|
|
493
570
|
type: "tableCell",
|
|
494
571
|
value,
|
|
495
|
-
children: tokenize(value,
|
|
496
|
-
position:
|
|
572
|
+
children: tokenize(value, shiftOrigin(origin, start)),
|
|
573
|
+
position: spanAt(origin, start, start + value.length)
|
|
497
574
|
};
|
|
498
575
|
});
|
|
499
576
|
};
|
|
@@ -628,7 +705,7 @@ const keepNotation = (nodes, source, origin, keep) => {
|
|
|
628
705
|
const literal = (start, end) => start >= end ? [] : [{
|
|
629
706
|
type: "text",
|
|
630
707
|
value: source.slice(start - origin.offset, end - origin.offset),
|
|
631
|
-
position:
|
|
708
|
+
position: spanAt(origin, start - origin.offset, end - origin.offset)
|
|
632
709
|
}];
|
|
633
710
|
const inside = (children) => mergeTexts(children.flatMap(demote));
|
|
634
711
|
/** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["Option","Option","Option","Option","hasImageExtension","Option","Option","Option","isImageUrl","Option","pipe","decorationRule","attempts","pipe","Option","shiftOrigin","Option","Option","Option","isImageUrl","shiftOrigin","Option","spanAt","Option","pipe","originOfLine","spanAt","shiftOrigin","Match","pipe","Option","spanAt","Match"],"sources":["../src/inline/constructs/bare-url.ts","../src/core/scan.ts","../src/inline/bracket-rules/formula.ts","../src/inline/bracket-rules/icon.ts","../src/inline/bracket-rules/image-extension.ts","../src/inline/bracket-rules/internal-link.ts","../src/inline/bracket-rules/project-link.ts","../src/inline/bracket-rules/url.ts","../src/inline/bracket-rules/index.ts","../src/inline/constructs/bracket.ts","../src/inline/constructs/hashtag.ts","../src/inline/constructs/inline-code.ts","../src/inline/constructs/strong-bracket.ts","../src/inline/constructs/index.ts","../src/inline/tokenize.ts","../src/block/classify.ts","../src/block/build.ts","../src/inline/table-cell.ts","../src/parse.ts"],"sourcesContent":["import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\nconst URL_RE = /^https?:\\/\\/[^\\s\\]]+/i\n\n/**\n * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない\n * (Cosense Web に合わせている。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。\n */\nexport const bareUrlConstruct: InternalConstruct = (source, index) => {\n const head = source[index]\n if (head !== \"h\" && head !== \"H\") return Option.none()\n\n const match = source.slice(index).match(URL_RE)\n if (!match) return Option.none()\n\n const url = match[0]\n return Option.some({\n node: { type: \"externalLink\", label: url, target: url },\n length: url.length,\n })\n}\n","/**\n * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。\n */\nimport { Option } from \"effect\"\n\n/** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */\nconst LEADING_WHITESPACE_RE = /^[ \\t\\u3000]*/\n\nexport const leadingWhitespace = (s: string): string => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? \"\"\n\n/**\n * `openIdx` の `[` に対応する `]` の位置。深さを数えるので\n * `[* a [b] c]` のような入れ子でも外側の `]` を返す。対応が閉じなければ None。\n */\nexport const findClosingBracket = (s: string, openIdx: number): Option.Option<number> => {\n let depth = 0\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"[\") depth++\n else if (ch === \"]\") {\n depth--\n if (depth === 0) return Option.some(i)\n }\n }\n return Option.none()\n}\n\n/** `#` がタグの開始位置か (行頭、または直前が空白)。単語の途中の `#` を除くため。 */\nexport const isTagBoundary = (s: string, index: number): boolean => {\n if (index === 0) return true\n const prev = s[index - 1]\n return prev === \" \" || prev === \"\\t\" || prev === \" \"\n}\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */\nexport const formulaRule: InternalBracketRule = (inner) =>\n inner.startsWith(\"$\")\n ? Option.some({ type: \"formula\", value: inner.slice(1).trim() })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst ICON_RE = /^(.+)\\.icon(?:\\*(\\d+))?$/\n\nconst MIN_COUNT = 1\nconst MAX_COUNT = 20\n\n/** `[user.icon]` と連打 `[user.icon*5]`。個数は 1..20 に収める (描画の暴走を防ぐため)。 */\nexport const iconRule: InternalBracketRule = (inner) => {\n const match = inner.match(ICON_RE)\n if (!match) return Option.none()\n\n const raw = match[2]\n const count = raw ? Math.min(Math.max(Number.parseInt(raw, 10), MIN_COUNT), MAX_COUNT) : MIN_COUNT\n\n return Option.some({ type: \"icon\", user: match[1] ?? \"\", count })\n}\n","import { Option } from \"effect\"\n\nimport { hasImageExtension } from \"../../core/image-url\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。\n *\n * 装飾の中では画像にせずリンク扱いにする (Cosense Web に合わせている)。`[* [a.png]]` の内側は\n * 画像ではなく `a.png` というタイトルの内部リンクになる。\n */\nexport const imageExtensionRule: InternalBracketRule = (inner, ctx) =>\n ctx.allowDecoration && hasImageExtension(inner)\n ? Option.some({ type: \"image\", src: inner })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[title]` — 他のどのルールにも当たらなかった角括弧は内部リンクになる。\n * 常に Some を返す catch-all なので、ルール配列の最後に置くこと。\n */\nexport const internalLinkRule: InternalBracketRule = (inner) =>\n Option.some({ type: \"internalLink\", label: inner, target: inner })\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[/project/title]` — 別プロジェクトのページへのリンク。\n *\n * タイトルは `/` を含みうるので最初の `/` だけで分割する。\n * `[/project]` のようにタイトルが無い書き方も記法としては成立するので、\n * その場合は title を空文字にする (利用側が「プロジェクトそのものへのリンク」と判断できる)。\n */\nexport const projectLinkRule: InternalBracketRule = (inner) => {\n if (!inner.startsWith(\"/\")) return Option.none()\n\n const rest = inner.slice(1)\n const slash = rest.indexOf(\"/\")\n const project = slash < 0 ? rest : rest.slice(0, slash)\n const title = slash < 0 ? \"\" : rest.slice(slash + 1)\n\n return Option.some({ type: \"projectLink\", label: inner, target: inner, project, title })\n}\n","import { Option, pipe } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst URLS_RE = /https?:\\/\\/[^\\s\\]]+/gi\n\n/** 画像 URL が複数あるとき最後のものを採るのはCosense Web の挙動。 */\nconst lastImage = (urls: readonly string[]): Option.Option<string> => {\n const images = urls.filter(isImageUrl)\n return Option.fromNullable(images[images.length - 1])\n}\n\n/** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */\nconst linkFor = (urls: readonly string[], src: string): Option.Option<string> =>\n Option.fromNullable(urls.find((url) => url !== src) ?? urls[0])\n\n/**\n * 中身に URL を含む角括弧。Cosense Web の挙動に合わせて次の順で決める:\n *\n * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする\n * (`[ラベル https://x/a.png]` は画像にならない)。\n * 2. URL だけが 2 つ以上:\n * - 画像 URL があれば「リンク付き画像」。画像が複数なら最後を表示し、それ以外の先頭をリンク先にする。\n * - 画像が無ければ 先頭 = リンク先 / 2 番目 = 表示テキスト。\n * 3. URL が 1 つだけ → 画像なら画像、違えば裸の外部リンク。\n */\nexport const urlRule: InternalBracketRule = (inner) => {\n const urls = inner.match(URLS_RE) ?? []\n if (urls.length === 0) return Option.none()\n\n const label = urls.reduce((rest, url) => rest.replace(url, \" \"), inner).trim()\n if (label !== \"\") {\n return Option.some({\n type: \"externalLink\",\n label,\n target: urls[0] ?? inner,\n })\n }\n\n if (urls.length >= 2) {\n return Option.some(\n pipe(\n lastImage(urls),\n Option.match({\n onSome: (src): InlineNodeInit =>\n pipe(\n linkFor(urls, src),\n Option.match({\n onNone: (): InlineNodeInit => ({ type: \"image\", src }),\n onSome: (link): InlineNodeInit => ({ type: \"image\", src, link }),\n }),\n ),\n onNone: (): InlineNodeInit => ({\n type: \"externalLink\",\n label: urls[1] ?? urls[0] ?? \"\",\n target: urls[0] ?? inner,\n }),\n }),\n ),\n )\n }\n\n const only = urls[0] ?? inner\n return Option.some(\n pipe(\n Option.liftPredicate(only, isImageUrl),\n Option.match({\n onSome: (src): InlineNodeInit => ({ type: \"image\", src }),\n onNone: (): InlineNodeInit => ({ type: \"externalLink\", label: only, target: only }),\n }),\n ),\n )\n}\n","/**\n * `[...]` の中身を解釈するルールの登録場所。**配列の順序が仕様**なので、\n * 既存の順序を動かさないこと (順序が変わると `[* [リンク]ですね]` のような\n * 入れ子ケースの解釈が変わる)。新しいルールは 1 ファイル 1 ルールで追加する。\n */\nimport type { InternalBracketRule } from \"../internal-types\"\nimport { decorationRule } from \"./decoration\"\nimport { formulaRule } from \"./formula\"\nimport { iconRule } from \"./icon\"\nimport { imageExtensionRule } from \"./image-extension\"\nimport { internalLinkRule } from \"./internal-link\"\nimport { projectLinkRule } from \"./project-link\"\nimport { urlRule } from \"./url\"\n\n/** 中身に角括弧を含んでいても成立しうるルール。 */\nexport const bracketRules: readonly InternalBracketRule[] = [formulaRule, decorationRule]\n\n/**\n * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。\n * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。\n * 末尾の internalLinkRule は常に成立する catch-all。\n */\nexport const simpleTargetRules: readonly InternalBracketRule[] = [\n iconRule,\n urlRule,\n imageExtensionRule,\n projectLinkRule,\n internalLinkRule,\n]\n\nexport { decorationRule, formulaRule, iconRule, imageExtensionRule, internalLinkRule }\nexport { projectLinkRule, urlRule }\n","import { Option, pipe } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport { findClosingBracket } from \"../../core/scan\"\nimport { bracketRules, simpleTargetRules } from \"../bracket-rules\"\nimport type { BracketScanContext, InternalBracketRule, InternalConstruct } from \"../internal-types\"\n\n/** ルールを順に試す。ジェネレータにしているのは最初に成立した時点で残りを評価しないため。 */\nfunction* attempts(rules: readonly InternalBracketRule[], inner: string, ctx: BracketScanContext) {\n for (const rule of rules) yield rule(inner, ctx)\n}\n\nconst parseInner = (inner: string, ctx: BracketScanContext) =>\n pipe(\n // 拡張のルールが最優先。既定の記法を上書きできるようにするため。\n Option.firstSomeOf(attempts(ctx.bracketRules, inner, ctx)),\n Option.orElse(() => Option.firstSomeOf(attempts(bracketRules, inner, ctx))),\n Option.orElse(() =>\n // 中に角括弧を含む中身は単純ターゲットではないので、記法として成立させない。\n inner.includes(\"[\") || inner.includes(\"]\")\n ? Option.none()\n : Option.firstSomeOf(attempts(simpleTargetRules, inner, ctx)),\n ),\n )\n\n/**\n * `[...]` 全般。閉じ括弧は深さを数えて探すので `[* a [b] c]` でも外側で閉じる。\n *\n * 中身が空 (`[]` / `[ ]`) のときと、どのルールにも当たらなかったときは None を返す。\n * 呼び出し側は先頭の `[` を素の文字として 1 文字進めるので、内側が改めて走査される。\n */\nexport const bracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\") return Option.none()\n\n return pipe(\n findClosingBracket(source, index),\n Option.flatMap((end) => {\n const inner = source.slice(index + 1, end)\n if (inner.trim() === \"\") return Option.none()\n\n const innerCtx: BracketScanContext = {\n ...ctx,\n innerOrigin: shiftOrigin(ctx.origin, index + 1),\n }\n return pipe(\n parseInner(inner, innerCtx),\n Option.map((node) => ({ node, length: end + 1 - index })),\n )\n }),\n )\n}\n","import { Option } from \"effect\"\n\nimport { isTagBoundary } from \"../../core/scan\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** タグ名に使えない文字。空白と角括弧と `#` 自身で終わる。 */\nconst TAG_NAME_RE = /^[^\\s[\\]#]+/\n\n/** `#tag`。行頭または空白の直後でだけ成立する (単語の途中の `#` を拾わないため)。 */\nexport const hashtagConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"#\" || !isTagBoundary(source, index)) return Option.none()\n\n const match = source.slice(index + 1).match(TAG_NAME_RE)\n if (!match) return Option.none()\n\n const value = match[0]\n return Option.some({ node: { type: \"hashtag\", value }, length: value.length + 1 })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** バッククォートで囲んだインラインコード。閉じるバッククォートが無ければ成立しない。 */\nexport const inlineCodeConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"`\") return Option.none()\n\n const end = source.indexOf(\"`\", index + 1)\n if (end < 0) return Option.none()\n\n return Option.some({\n node: { type: \"inlineCode\", value: source.slice(index + 1, end) },\n length: end + 1 - index,\n })\n}\n","import { Option } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/**\n * `[[...]]` — Cosense Web の strong。`]]` で閉じるときだけ成立する\n * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。\n *\n * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。\n */\nexport const strongBracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\" || source[index + 1] !== \"[\") return Option.none()\n\n const end = source.indexOf(\"]]\", index + 2)\n if (end < 0) return Option.none()\n\n const inner = source.slice(index + 2, end)\n const length = end + 2 - index\n\n if (isImageUrl(inner)) {\n const node: InlineNodeInit = { type: \"image\", src: inner, large: true }\n return Option.some({ node, length })\n }\n\n return Option.some({\n node: {\n type: \"decoration\",\n value: inner,\n // `[[x]]` は記号を書かないが、太字なので `[* x]` と同じ扱いにする\n markers: [\"*\"],\n bold: true,\n italic: false,\n strike: false,\n underline: false,\n sizeLevel: 0,\n children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false),\n },\n length,\n })\n}\n","/**\n * 行内の走査ルールの登録場所。**配列の順序が仕様**なので既存の順序を動かさないこと。\n * `[[` は `[` より先に試す必要がある (先に `[` が食べると strong にならない)。\n */\nimport type { InternalConstruct } from \"../internal-types\"\nimport { bareUrlConstruct } from \"./bare-url\"\nimport { bracketConstruct } from \"./bracket\"\nimport { hashtagConstruct } from \"./hashtag\"\nimport { inlineCodeConstruct } from \"./inline-code\"\nimport { strongBracketConstruct } from \"./strong-bracket\"\n\nexport const inlineConstructs: readonly InternalConstruct[] = [\n strongBracketConstruct,\n bracketConstruct,\n inlineCodeConstruct,\n hashtagConstruct,\n bareUrlConstruct,\n]\n\nexport { bareUrlConstruct, bracketConstruct, hashtagConstruct }\nexport { inlineCodeConstruct, strongBracketConstruct }\n","/**\n * tokenize.ts — 行内の走査ループ。\n *\n * 1 文字ずつ進みながら construct を順に試し、成立したらノードにする。どれも成立しなければ\n * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて\n * text ノードにする。位置の付与はこのループだけが行う。\n */\nimport { Option } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { InlineNode, InlineNodeInit, Position } from \"../types\"\nimport { inlineConstructs } from \"./constructs\"\nimport type { InternalBracketRule, InternalConstruct, ScanContext } from \"./internal-types\"\nimport type { BracketRule, ConstructMatch, Extension, InlineConstruct } from \"./types\"\n\nexport interface TokenizeInlineOptions {\n /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */\n readonly allowDecoration?: boolean\n /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */\n readonly origin?: {\n readonly line?: number\n readonly column?: number\n readonly offset?: number\n }\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\n/** 走査に渡せる形まで並べ終えたルール。`constructs` は先頭から順に試される。 */\nexport interface ResolvedExtensions {\n readonly constructs: readonly InternalConstruct[]\n readonly bracketRules: readonly InternalBracketRule[]\n}\n\n/** 拡張のルールは null で返すので、中のルールと同じく Option で返す形に包む。 */\nconst fromConstruct =\n (construct: InlineConstruct): InternalConstruct =>\n (source, index, ctx) =>\n Option.fromNullable(construct(source, index, ctx))\n\nconst fromBracketRule =\n (rule: BracketRule): InternalBracketRule =>\n (inner, ctx) =>\n Option.fromNullable(rule(inner, ctx))\n\n/** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */\nexport const resolveExtensions = (extensions?: readonly Extension[]): ResolvedExtensions => {\n if (extensions === undefined || extensions.length === 0) {\n return { constructs: inlineConstructs, bracketRules: [] }\n }\n const constructs = extensions.flatMap((extension) => extension.constructs ?? [])\n const bracketRules = extensions.flatMap((extension) => extension.bracketRules ?? [])\n return {\n constructs: [...constructs.map(fromConstruct), ...inlineConstructs],\n bracketRules: bracketRules.map(fromBracketRule),\n }\n}\n\n/**\n * ノード本体に位置を与える。判別共用体へのスプレッドは TS が型を保てないため\n * ここだけキャストする (position 以外のフィールドは触っていない)。\n */\nconst withPosition = (node: InlineNodeInit, position: Position): InlineNode =>\n ({ ...node, position }) as InlineNode\n\n/** ジェネレータにしているのは、最初に成立した時点で残りのルールを評価しないため。 */\nfunction* attempts(\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n) {\n for (const construct of constructs) yield construct(source, index, ctx)\n}\n\nconst firstMatch = (\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n): Option.Option<ConstructMatch> => Option.firstSomeOf(attempts(constructs, source, index, ctx))\n\nconst scan = (\n source: string,\n origin: Origin,\n allowDecoration: boolean,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => {\n const out: InlineNode[] = []\n const ctx: ScanContext = {\n allowDecoration,\n origin,\n bracketRules: rules.bracketRules,\n tokenize: (inner, innerOrigin, innerAllowDecoration) =>\n scan(inner, innerOrigin, innerAllowDecoration, rules),\n }\n\n let textStart = 0\n let index = 0\n\n const flushText = (end: number) => {\n if (end <= textStart) return\n out.push({\n type: \"text\",\n value: source.slice(textStart, end),\n position: spanAt(origin, textStart, end),\n })\n }\n\n while (index < source.length) {\n const match = firstMatch(rules.constructs, source, index, ctx)\n if (Option.isNone(match)) {\n index++\n continue\n }\n flushText(index)\n const { node, length } = match.value\n out.push(withPosition(node, spanAt(origin, index, index + length)))\n index += length\n textStart = index\n }\n flushText(index)\n\n return out\n}\n\nconst resolveOrigin = (origin: TokenizeInlineOptions[\"origin\"]): Origin => ({\n line: origin?.line ?? 0,\n column: origin?.column ?? 0,\n offset: origin?.offset ?? 0,\n})\n\n/**\n * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。\n *\n * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。\n */\nexport const tokenizeInline = (\n source: string,\n options?: TokenizeInlineOptions,\n): readonly InlineNode[] =>\n scan(\n source,\n resolveOrigin(options?.origin),\n options?.allowDecoration ?? true,\n resolveExtensions(options?.extensions),\n )\n\n/**\n * 解決済みのルールで走査する内部向け入口。\n * 行ごとに `tokenizeInline` を呼ぶと拡張の解決が毎回走るので、それを避けたいときに使う。\n */\nexport const tokenizeInlineWith = (\n source: string,\n origin: Origin,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => scan(source, origin, true, rules)\n","/**\n * classify.ts — 1 行が何の行かを判定する。インライン記法の中身には立ち入らない。\n *\n * 結果はタグ付きユニオンなので、行の種類を増やすと分岐している側が\n * exhaustive チェックで対応漏れを教えてくれる。\n */\nimport { Option, pipe } from \"effect\"\n\nimport { leadingWhitespace } from \"../core/scan\"\n\nconst CODE_HEADER_RE = /^code:(.+)$/\nconst TABLE_HEADER_RE = /^table:(.+)$/\n/** 引用記号と、その直後の空白 1 つ (あれば本文から除く)。 */\nconst QUOTE_RE = /^>\\s?/\nconst MONOSPACE_RE = /^[$%]/\n\nexport interface CodeHeaderLine {\n readonly _tag: \"codeHeader\"\n readonly indent: number\n readonly filename: string\n}\n\nexport interface TableHeaderLine {\n readonly _tag: \"tableHeader\"\n readonly indent: number\n readonly name: string\n}\n\nexport interface ContentLine {\n readonly _tag: \"content\"\n readonly indent: number\n readonly quote: boolean\n readonly monospace: boolean\n /** 行の先頭から数えて、インライン記法の解析を始める位置 (インデントと引用記号の分だけ進む) */\n readonly contentOffset: number\n}\n\nexport type LineRole = CodeHeaderLine | TableHeaderLine | ContentLine\n\nconst codeHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(CODE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"codeHeader\", indent, filename: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst tableHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(TABLE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"tableHeader\", indent, name: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst content = (rest: string, indent: number): LineRole => {\n const quoteMark = rest.match(QUOTE_RE)?.[0] ?? \"\"\n const body = rest.slice(quoteMark.length)\n return {\n _tag: \"content\",\n indent,\n quote: quoteMark !== \"\",\n monospace: MONOSPACE_RE.test(body),\n contentOffset: indent + quoteMark.length,\n }\n}\n\n/**\n * 行の役割を判定する。`code:` / `table:` は行の意味がページの文脈\n * (コードブロックの中かどうか) で変わるので、ここでは「ヘッダの形をしている」ことだけを見る。\n */\nexport const classifyLine = (raw: string): LineRole => {\n const indent = leadingWhitespace(raw).length\n const rest = raw.slice(indent)\n return pipe(\n Option.firstSomeOf([codeHeader(rest, indent), tableHeader(rest, indent)]),\n Option.getOrElse(() => content(rest, indent)),\n )\n}\n\n/** 行頭の空白の文字数。全角スペースも 1 文字として数える。 */\nexport const indentOf = (raw: string): number => leadingWhitespace(raw).length\n","/**\n * build.ts — 行の並びをブロックに畳む。\n *\n * `code:` / `table:` は「ヘッダ行 + それより深いインデントの行」で 1 つのブロックになる。\n * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる\n * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。\n */\nimport { Match } from \"effect\"\n\nimport { type Origin, originOfLine, shiftOrigin, spanAt } from \"../core/position\"\nimport type {\n CodeBlock,\n CodeLine,\n InlineNode,\n LineBlock,\n TableBlock,\n TableCell,\n TableRow,\n TitleBlock,\n TopLevelBlock,\n} from \"../types\"\nimport { classifyLine, type ContentLine, indentOf } from \"./classify\"\n\n/** 1 行分の入力。`offset` はソース全文における行頭の位置。 */\nexport interface SourceLine {\n readonly index: number\n readonly text: string\n readonly offset: number\n}\n\n/**\n * 行の一部をインライン記法として解析する関数。\n * 記法ルールの構成は呼び出し側が束ねて渡すので、この層は拡張の有無を知らずに済む。\n */\nexport type TokenizeLine = (source: string, origin: Origin) => readonly InlineNode[]\n\n/** 行の中身と、テーブルのセルの中身の読み方。セルは行と違う規則で読むので別に受け取る。 */\nexport interface Tokenizers {\n readonly line: TokenizeLine\n readonly tableCell: TokenizeLine\n}\n\nconst lineOrigin = (line: SourceLine): Origin => originOfLine(line.index, line.offset)\n\nconst wholeLine = (line: SourceLine) => spanAt(lineOrigin(line), 0, line.text.length)\n\n/** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */\nconst dedent = (text: string, amount: number): string =>\n text.slice(Math.min(amount, indentOf(text)))\n\nconst titleBlock = (line: SourceLine, tokenize: TokenizeLine): TitleBlock => ({\n type: \"title\",\n value: line.text,\n children: tokenize(line.text, lineOrigin(line)),\n position: wholeLine(line),\n})\n\nconst lineBlock = (line: SourceLine, role: ContentLine, tokenize: TokenizeLine): LineBlock => ({\n type: \"line\",\n indent: role.indent,\n quote: role.quote,\n monospace: role.monospace,\n children: tokenize(line.text.slice(role.contentOffset), {\n line: line.index,\n column: role.contentOffset,\n offset: line.offset + role.contentOffset,\n }),\n position: wholeLine(line),\n})\n\nconst codeLine = (line: SourceLine, headerIndent: number): CodeLine => ({\n type: \"codeLine\",\n // ヘッダより 1 段深いところがコードの左端。それより深いインデントは中身なので保つ。\n value: dedent(line.text, headerIndent + 1),\n position: wholeLine(line),\n})\n\nconst tableCells = (line: SourceLine, tokenize: TokenizeLine): readonly TableCell[] => {\n const origin = lineOrigin(line)\n const indent = indentOf(line.text)\n const values = line.text.slice(indent).split(\"\\t\")\n return values.map((value, index) => {\n // 前にあるセルと、その区切りのタブ 1 文字ずつのぶんだけ右から始まる。\n const start = indent + values.slice(0, index).reduce((sum, cell) => sum + cell.length + 1, 0)\n return {\n type: \"tableCell\",\n value,\n children: tokenize(value, shiftOrigin(origin, start)),\n position: spanAt(origin, start, start + value.length),\n }\n })\n}\n\nconst tableRow = (line: SourceLine, tokenize: TokenizeLine): TableRow => ({\n type: \"tableRow\",\n cells: tableCells(line, tokenize),\n position: wholeLine(line),\n})\n\n/** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */\nconst bodyEnd = (lines: readonly SourceLine[], start: number, headerIndent: number): number => {\n let end = start\n while (end < lines.length) {\n const line = lines[end]\n if (line === undefined || indentOf(line.text) <= headerIndent) break\n end++\n }\n return end\n}\n\nconst blockPosition = (header: SourceLine, body: readonly SourceLine[]) => ({\n start: wholeLine(header).start,\n end: wholeLine(body[body.length - 1] ?? header).end,\n})\n\nconst codeBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n filename: string,\n indent: number,\n): CodeBlock => ({\n type: \"codeBlock\",\n filename,\n indent,\n lines: body.map((line) => codeLine(line, indent)),\n position: blockPosition(header, body),\n})\n\nconst tableBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n name: string,\n indent: number,\n tokenize: TokenizeLine,\n): TableBlock => ({\n type: \"table\",\n name,\n indent,\n rows: body.map((line) => tableRow(line, tokenize)),\n position: blockPosition(header, body),\n})\n\n/**\n * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる\n * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。\n */\nexport const buildBlocks = (\n lines: readonly SourceLine[],\n tokenizers: Tokenizers,\n): readonly TopLevelBlock[] => {\n const tokenize = tokenizers.line\n const blocks: TopLevelBlock[] = []\n const head = lines[0]\n if (head === undefined) return blocks\n blocks.push(titleBlock(head, tokenize))\n\n let index = 1\n while (index < lines.length) {\n const line = lines[index]\n if (line === undefined) break\n\n index = Match.value(classifyLine(line.text)).pipe(\n Match.tag(\"codeHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(codeBlock(line, lines.slice(index + 1, end), role.filename, role.indent))\n return end\n }),\n Match.tag(\"tableHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(\n tableBlock(\n line,\n lines.slice(index + 1, end),\n role.name,\n role.indent,\n tokenizers.tableCell,\n ),\n )\n return end\n }),\n Match.tag(\"content\", (role) => {\n blocks.push(lineBlock(line, role, tokenize))\n return index + 1\n }),\n Match.exhaustive,\n )\n }\n\n return blocks\n}\n\n/**\n * 1 行だけを通常行として組み立てる。\n * ページの文脈が無いので `code:` / `table:` ヘッダもブロックにはせず、通常行として扱う。\n */\nexport const buildLineBlock = (line: SourceLine, tokenize: TokenizeLine): LineBlock => {\n const role = classifyLine(line.text)\n return lineBlock(\n line,\n role._tag === \"content\"\n ? role\n : {\n _tag: \"content\",\n indent: role.indent,\n quote: false,\n monospace: false,\n contentOffset: role.indent,\n },\n tokenize,\n )\n}\n","/**\n * table-cell.ts — テーブルのセルの中の記法。\n *\n * Cosense Web はセルの中ではリンクの記法 (`[title]` / `[https://…]` / `[/project/page]` /\n * 裸の URL / `#tag`) だけを読み、それ以外は書いたままの文字として出す。\n * ここでは行と同じ規則でいったん読み、残さないノードを書いたままの文字に戻す。\n * 最初からリンクの規則だけで読むと、`[* 太字]` が `* 太字` というページへのリンクになってしまうため。\n *\n * どのノードを残すかは拡張の `keepInTableCell` で足せる (`tableCellNotation`)。\n */\nimport { Match, Option, pipe } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { Decoration, InlineNode, TextNode } from \"../types\"\nimport type { Extension } from \"./types\"\n\n/** セルの中でも記法として残すかどうか。 */\nexport type KeepInTableCell = (node: InlineNode) => boolean\n\n/** Cosense Web がセルの中でも読む記法。 */\nconst LINK_TYPES: ReadonlySet<InlineNode[\"type\"]> = new Set([\n \"internalLink\",\n \"externalLink\",\n \"projectLink\",\n \"hashtag\",\n])\n\n/** リンクの記法は必ず残し、それに加えて拡張のどれかが残すと言ったノードを残す。 */\nexport const keepInTableCellOf = (extensions: readonly Extension[] = []): KeepInTableCell => {\n const keeps = extensions.flatMap((extension) =>\n extension.keepInTableCell === undefined ? [] : [extension.keepInTableCell],\n )\n return (node) => LINK_TYPES.has(node.type) || keeps.some((keep) => keep(node))\n}\n\nconst joinTexts = (previous: TextNode, next: TextNode): TextNode => ({\n type: \"text\",\n value: previous.value + next.value,\n position: { start: previous.position.start, end: next.position.end },\n})\n\nconst isText = (node: InlineNode): node is TextNode => node.type === \"text\"\n\n/** `start` から続く text ノードの並び。 */\nconst textRunFrom = (nodes: readonly InlineNode[], start: number): readonly TextNode[] => {\n const end = nodes.findIndex((node, index) => index > start && !isText(node))\n return nodes.slice(start, end === -1 ? nodes.length : end).filter(isText)\n}\n\n/**\n * 隣り合う text ノードを 1 つにまとめる。行を読んだときと同じ形にするため。\n * text の並びの先頭で並び全体をまとめ、続きの text は先頭に含まれているので出さない。\n */\nconst mergeTexts = (nodes: readonly InlineNode[]): readonly InlineNode[] =>\n nodes.flatMap((node, index): readonly InlineNode[] =>\n !isText(node)\n ? [node]\n : pipe(Option.fromNullable(nodes[index - 1]), Option.exists(isText))\n ? []\n : [textRunFrom(nodes, index).reduce(joinTexts)],\n )\n\n/**\n * 行と同じ規則で読んだセルのノード列から、`keep` が残さないノードを書いたままの文字に戻す。\n * `source` はセルの中身、`origin` はその先頭のソース上の位置。\n *\n * 装飾を残さないときは記号の部分だけを文字に戻し、中身は同じように辿る。\n * `[* [リンク]]` の中のリンクは残す。装飾を残すときも、その中身は同じ規則で辿る。\n */\nexport const keepNotation = (\n nodes: readonly InlineNode[],\n source: string,\n origin: Origin,\n keep: KeepInTableCell,\n): readonly InlineNode[] => {\n /** ソース上の `[start, end)` を、書いたままの文字のノードにする。 */\n const literal = (start: number, end: number): readonly TextNode[] =>\n start >= end\n ? []\n : [\n {\n type: \"text\",\n value: source.slice(start - origin.offset, end - origin.offset),\n position: spanAt(origin, start - origin.offset, end - origin.offset),\n },\n ]\n\n const inside = (children: readonly InlineNode[]): readonly InlineNode[] =>\n mergeTexts(children.flatMap(demote))\n\n /** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */\n const unwrap = (node: Decoration): readonly InlineNode[] =>\n pipe(\n Option.all([\n Option.fromNullable(node.children[0]),\n Option.fromNullable(node.children[node.children.length - 1]),\n ]),\n Option.match({\n onNone: () => literal(node.position.start.offset, node.position.end.offset),\n onSome: ([first, last]) => [\n ...literal(node.position.start.offset, first.position.start.offset),\n ...node.children.flatMap(demote),\n ...literal(last.position.end.offset, node.position.end.offset),\n ],\n }),\n )\n\n const demote = (node: InlineNode): readonly InlineNode[] =>\n Match.value(node).pipe(\n Match.when({ type: \"text\" }, (text) => [text]),\n Match.when({ type: \"decoration\" }, (decoration) =>\n keep(decoration)\n ? [{ ...decoration, children: inside(decoration.children) }]\n : unwrap(decoration),\n ),\n Match.when(keep, (kept) => [kept]),\n Match.orElse((other) => literal(other.position.start.offset, other.position.end.offset)),\n )\n\n return inside(nodes)\n}\n","/**\n * parse.ts — ページ全文の入口。\n */\nimport { buildBlocks, buildLineBlock, type SourceLine } from \"./block/build\"\nimport { keepInTableCellOf, keepNotation } from \"./inline/table-cell\"\nimport { resolveExtensions, tokenizeInlineWith } from \"./inline/tokenize\"\nimport type { Extension } from \"./inline/types\"\nimport type { LineBlock, Page } from \"./types\"\n\n/**\n * CR / CRLF を LF に揃える。\n *\n * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の\n * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。\n */\nexport const normalizeLineEndings = (source: string): string => source.replace(/\\r\\n?/g, \"\\n\")\n\nexport interface ParseOptions {\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\nconst toSourceLines = (source: string): readonly SourceLine[] => {\n const lines: SourceLine[] = []\n let offset = 0\n for (const [index, text] of source.split(\"\\n\").entries()) {\n lines.push({ index, text, offset })\n offset += text.length + 1 // 改行 1 文字ぶん進める\n }\n return lines\n}\n\n/**\n * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。\n * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。\n *\n * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。\n */\nexport const parse = (source: string, options?: ParseOptions): Page => {\n const normalized = normalizeLineEndings(source)\n const lines = toSourceLines(normalized)\n const rules = resolveExtensions(options?.extensions)\n const keepInTableCell = keepInTableCellOf(options?.extensions)\n const last = lines[lines.length - 1]\n\n return {\n type: \"page\",\n children: buildBlocks(lines, {\n line: (text, origin) => tokenizeInlineWith(text, origin, rules),\n tableCell: (text, origin) =>\n keepNotation(tokenizeInlineWith(text, origin, rules), text, origin, keepInTableCell),\n }),\n position: {\n start: { line: 0, column: 0, offset: 0 },\n end: {\n line: last?.index ?? 0,\n column: last?.text.length ?? 0,\n offset: normalized.length,\n },\n },\n }\n}\n\nexport interface ParseLineOptions extends ParseOptions {\n /** この行がページ内の何行目か (既定: 0) */\n readonly line?: number\n /** この行の行頭がソース全文のどこにあるか (既定: 0) */\n readonly offset?: number\n}\n\n/**\n * 1 行だけを通常行としてパースする。\n *\n * `code:` / `table:` はページの文脈があって初めてブロックになるので、\n * ここでは通常行として扱う。\n */\nexport const parseLine = (raw: string, options?: ParseLineOptions): LineBlock => {\n const rules = resolveExtensions(options?.extensions)\n return buildLineBlock(\n { index: options?.line ?? 0, text: raw, offset: options?.offset ?? 0 },\n (text, origin) => tokenizeInlineWith(text, origin, rules),\n )\n}\n\nexport interface Parser {\n readonly parse: (source: string) => Page\n readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, \"extensions\">) => LineBlock\n}\n\n/**\n * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、\n * 呼び出しごとに options を渡さずに済む。\n */\nexport const createParser = (options?: ParseOptions): Parser => ({\n parse: (source) => parse(source, options),\n parseLine: (raw, lineOptions) => parseLine(raw, { ...lineOptions, ...options }),\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Option","Option","Option","Option","Option","hasImageExtension","Option","Option","Option","isImageUrl","Option","pipe","attempts","pipe","Option","Option","Option","Option","isImageUrl","Option","Option","pipe","Match","pipe","Option","Match"],"sources":["../src/core/position.ts","../src/inline/constructs/bare-url.ts","../src/core/scan.ts","../src/inline/bracket-rules/decoration.ts","../src/inline/bracket-rules/formula.ts","../src/inline/bracket-rules/icon.ts","../src/inline/bracket-rules/image-extension.ts","../src/inline/bracket-rules/internal-link.ts","../src/inline/bracket-rules/project-link.ts","../src/inline/bracket-rules/url.ts","../src/inline/bracket-rules/index.ts","../src/inline/constructs/bracket.ts","../src/inline/constructs/hashtag.ts","../src/inline/constructs/inline-code.ts","../src/inline/constructs/strong-bracket.ts","../src/inline/constructs/index.ts","../src/inline/tokenize.ts","../src/block/classify.ts","../src/block/build.ts","../src/inline/table-cell.ts","../src/parse.ts"],"sourcesContent":["/**\n * position.ts — 位置情報の組み立て。\n *\n * インラインの走査は「1 行の中の部分文字列」を対象にするため、走査対象の先頭が\n * ソース上のどこかを表す Origin を持ち回り、そこからの相対インデックスで位置を作る。\n * 位置計算をここ 1 箇所に集約することで、装飾の中の再帰でオフセットがずれる事故を防ぐ。\n */\nimport type { Point, Position } from \"../types\"\n\n/** 走査対象の文字列のインデックス 0 が、ソース上のどこにあるか。 */\nexport interface Origin {\n readonly line: number\n readonly column: number\n readonly offset: number\n}\n\nexport const originOfLine = (line: number, offset: number): Origin => ({ line, column: 0, offset })\n\n/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */\nexport const shiftOrigin = (origin: Origin, by: number): Origin => ({\n line: origin.line,\n column: origin.column + by,\n offset: origin.offset + by,\n})\n\nexport const pointAt = (origin: Origin, index: number): Point => ({\n line: origin.line,\n column: origin.column + index,\n offset: origin.offset + index,\n})\n\n/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */\nexport const spanAt = (origin: Origin, start: number, end: number): Position => ({\n start: pointAt(origin, start),\n end: pointAt(origin, end),\n})\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\nconst URL_RE = /^https?:\\/\\/[^\\s\\]]+/i\n\n/**\n * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない\n * (Cosense Web に合わせている。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。\n */\nexport const bareUrlConstruct: InternalConstruct = (source, index) => {\n const head = source[index]\n if (head !== \"h\" && head !== \"H\") return Option.none()\n\n const match = source.slice(index).match(URL_RE)\n if (!match) return Option.none()\n\n const url = match[0]\n return Option.some({\n node: { type: \"externalLink\", label: url, target: url },\n length: url.length,\n })\n}\n","/**\n * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。\n */\nimport { Option } from \"effect\"\n\n/** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */\nconst LEADING_WHITESPACE_RE = /^[ \\t\\u3000]*/\n\nexport const leadingWhitespace = (s: string): string => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? \"\"\n\n/**\n * `openIdx` の `[` に対応する `]` の位置。深さを数えるので\n * `[* a [b] c]` のような入れ子でも外側の `]` を返す。対応が閉じなければ None。\n */\nexport const findClosingBracket = (s: string, openIdx: number): Option.Option<number> => {\n let depth = 0\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"[\") depth++\n else if (ch === \"]\") {\n depth--\n if (depth === 0) return Option.some(i)\n }\n }\n return Option.none()\n}\n\n/** `#` がタグの開始位置か (行頭、または直前が空白)。単語の途中の `#` を除くため。 */\nexport const isTagBoundary = (s: string, index: number): boolean => {\n if (index === 0) return true\n const prev = s[index - 1]\n return prev === \" \" || prev === \"\\t\" || prev === \" \"\n}\n","import { Option } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[<記号の並び> 中身]`。記号は Cosense の文字装飾の記号 `` !\"#%&'()*+,-./{|}<>_~= `` で、\n * Cosense Web はこの集合からなる並びをすべて装飾として読む (help-jp「文字装飾記法」)。\n * `=` は help-jp が将来の別用途のために使わないよう求めているが、今の Cosense Web は\n * 装飾として読むので含める。\n * どの記号が装飾になるかは構文なので、設定で変えられるようにしない。\n * `$` と `[` はこの一覧に無い。`[$ x]` は数式、`[[x]]` は太字の別の記法で、どちらも別のルールが読む。\n *\n * 文字列から組み立てずに正規表現のリテラルで持つのは、トップレベルで関数を呼ばないため\n * (`sideEffects: false`)。\n */\nconst DECORATION_PATTERN = /^([!\"#%&'()*+,\\-./{|}<>_~=]+)\\s+([\\s\\S]+)$/\n\n/**\n * 見た目が決まっている記号。bold などのフラグはこれだけから決める。\n * ほかの記号は `markers` に残るだけで、見た目はプロジェクトの UserCSS が付ける。\n */\nconst STYLED_MARKERS = { bold: \"*\", italic: \"/\", strike: \"-\", underline: \"_\" } as const\n\nconst MAX_SIZE_LEVEL = 4\n\n/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */\nconst uniqueChars = (marks: string): readonly string[] => [...new Set(marks)]\n\n/**\n * `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)、\n * および見た目の付かない記号の装飾 (`[! 注意]`)。\n *\n * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。\n * そのため子の走査は allowDecoration=false で行う。\n * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。\n */\nexport const decorationRule: InternalBracketRule = (inner, ctx) => {\n if (!ctx.allowDecoration) return Option.none()\n\n const match = inner.match(DECORATION_PATTERN)\n if (!match) return Option.none()\n\n const marks = match[1] ?? \"\"\n const value = match[2] ?? \"\"\n const stars = [...marks].filter((mark) => mark === STYLED_MARKERS.bold).length\n\n // 正規表現が末尾まで貪欲にマッチするので、中身は inner の末尾側の部分文字列になる。\n const valueOffset = inner.length - value.length\n\n return Option.some({\n type: \"decoration\",\n value,\n markers: uniqueChars(marks),\n bold: stars > 0,\n italic: marks.includes(STYLED_MARKERS.italic),\n strike: marks.includes(STYLED_MARKERS.strike),\n underline: marks.includes(STYLED_MARKERS.underline),\n sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),\n children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false),\n })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */\nexport const formulaRule: InternalBracketRule = (inner) =>\n inner.startsWith(\"$\")\n ? Option.some({ type: \"formula\", value: inner.slice(1).trim() })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst ICON_RE = /^(.+)\\.icon(?:\\*(\\d+))?$/\n\nconst MIN_COUNT = 1\nconst MAX_COUNT = 20\n\n/** `[user.icon]` と連打 `[user.icon*5]`。個数は 1..20 に収める (描画の暴走を防ぐため)。 */\nexport const iconRule: InternalBracketRule = (inner) => {\n const match = inner.match(ICON_RE)\n if (!match) return Option.none()\n\n const raw = match[2]\n const count = raw ? Math.min(Math.max(Number.parseInt(raw, 10), MIN_COUNT), MAX_COUNT) : MIN_COUNT\n\n return Option.some({ type: \"icon\", user: match[1] ?? \"\", count })\n}\n","import { Option } from \"effect\"\n\nimport { hasImageExtension } from \"../../core/image-url\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。\n *\n * 装飾の中では画像にせずリンク扱いにする (Cosense Web に合わせている)。`[* [a.png]]` の内側は\n * 画像ではなく `a.png` というタイトルの内部リンクになる。\n */\nexport const imageExtensionRule: InternalBracketRule = (inner, ctx) =>\n ctx.allowDecoration && hasImageExtension(inner)\n ? Option.some({ type: \"image\", src: inner })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[title]` — 他のどのルールにも当たらなかった角括弧は内部リンクになる。\n * 常に Some を返す catch-all なので、ルール配列の最後に置くこと。\n */\nexport const internalLinkRule: InternalBracketRule = (inner) =>\n Option.some({ type: \"internalLink\", label: inner, target: inner })\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[/project/title]` — 別プロジェクトのページへのリンク。\n *\n * タイトルは `/` を含みうるので最初の `/` だけで分割する。\n * `[/project]` のようにタイトルが無い書き方も記法としては成立するので、\n * その場合は title を空文字にする (利用側が「プロジェクトそのものへのリンク」と判断できる)。\n */\nexport const projectLinkRule: InternalBracketRule = (inner) => {\n if (!inner.startsWith(\"/\")) return Option.none()\n\n const rest = inner.slice(1)\n const slash = rest.indexOf(\"/\")\n const project = slash < 0 ? rest : rest.slice(0, slash)\n const title = slash < 0 ? \"\" : rest.slice(slash + 1)\n\n return Option.some({ type: \"projectLink\", label: inner, target: inner, project, title })\n}\n","import { Option, pipe } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst URLS_RE = /https?:\\/\\/[^\\s\\]]+/gi\n\n/** 画像 URL が複数あるとき最後のものを採るのはCosense Web の挙動。 */\nconst lastImage = (urls: readonly string[]): Option.Option<string> => {\n const images = urls.filter(isImageUrl)\n return Option.fromNullable(images[images.length - 1])\n}\n\n/** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */\nconst linkFor = (urls: readonly string[], src: string): Option.Option<string> =>\n Option.fromNullable(urls.find((url) => url !== src) ?? urls[0])\n\n/**\n * 中身に URL を含む角括弧。Cosense Web の挙動に合わせて次の順で決める:\n *\n * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする\n * (`[ラベル https://x/a.png]` は画像にならない)。\n * 2. URL だけが 2 つ以上:\n * - 画像 URL があれば「リンク付き画像」。画像が複数なら最後を表示し、それ以外の先頭をリンク先にする。\n * - 画像が無ければ 先頭 = リンク先 / 2 番目 = 表示テキスト。\n * 3. URL が 1 つだけ → 画像なら画像、違えば裸の外部リンク。\n */\nexport const urlRule: InternalBracketRule = (inner) => {\n const urls = inner.match(URLS_RE) ?? []\n if (urls.length === 0) return Option.none()\n\n const label = urls.reduce((rest, url) => rest.replace(url, \" \"), inner).trim()\n if (label !== \"\") {\n return Option.some({\n type: \"externalLink\",\n label,\n target: urls[0] ?? inner,\n })\n }\n\n if (urls.length >= 2) {\n return Option.some(\n pipe(\n lastImage(urls),\n Option.match({\n onSome: (src): InlineNodeInit =>\n pipe(\n linkFor(urls, src),\n Option.match({\n onNone: (): InlineNodeInit => ({ type: \"image\", src }),\n onSome: (link): InlineNodeInit => ({ type: \"image\", src, link }),\n }),\n ),\n onNone: (): InlineNodeInit => ({\n type: \"externalLink\",\n label: urls[1] ?? urls[0] ?? \"\",\n target: urls[0] ?? inner,\n }),\n }),\n ),\n )\n }\n\n const only = urls[0] ?? inner\n return Option.some(\n pipe(\n Option.liftPredicate(only, isImageUrl),\n Option.match({\n onSome: (src): InlineNodeInit => ({ type: \"image\", src }),\n onNone: (): InlineNodeInit => ({ type: \"externalLink\", label: only, target: only }),\n }),\n ),\n )\n}\n","/**\n * `[...]` の中身を解釈するルールの登録場所。**配列の順序が仕様**なので、\n * 既存の順序を動かさないこと (順序が変わると `[* [リンク]ですね]` のような\n * 入れ子ケースの解釈が変わる)。新しいルールは 1 ファイル 1 ルールで追加する。\n */\nimport type { InternalBracketRule } from \"../internal-types\"\nimport { decorationRule } from \"./decoration\"\nimport { formulaRule } from \"./formula\"\nimport { iconRule } from \"./icon\"\nimport { imageExtensionRule } from \"./image-extension\"\nimport { internalLinkRule } from \"./internal-link\"\nimport { projectLinkRule } from \"./project-link\"\nimport { urlRule } from \"./url\"\n\n/** 中身に角括弧を含んでいても成立しうるルール。 */\nexport const bracketRules: readonly InternalBracketRule[] = [formulaRule, decorationRule]\n\n/**\n * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。\n * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。\n * 末尾の internalLinkRule は常に成立する catch-all。\n */\nexport const simpleTargetRules: readonly InternalBracketRule[] = [\n iconRule,\n urlRule,\n imageExtensionRule,\n projectLinkRule,\n internalLinkRule,\n]\n\nexport { decorationRule, formulaRule, iconRule, imageExtensionRule, internalLinkRule }\nexport { projectLinkRule, urlRule }\n","import { Option, pipe } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport { findClosingBracket } from \"../../core/scan\"\nimport { bracketRules, simpleTargetRules } from \"../bracket-rules\"\nimport type { BracketScanContext, InternalBracketRule, InternalConstruct } from \"../internal-types\"\n\n/** ルールを順に試す。ジェネレータにしているのは最初に成立した時点で残りを評価しないため。 */\nfunction* attempts(rules: readonly InternalBracketRule[], inner: string, ctx: BracketScanContext) {\n for (const rule of rules) yield rule(inner, ctx)\n}\n\nconst parseInner = (inner: string, ctx: BracketScanContext) =>\n pipe(\n // 拡張のルールが最優先。既定の記法を上書きできるようにするため。\n Option.firstSomeOf(attempts(ctx.bracketRules, inner, ctx)),\n Option.orElse(() => Option.firstSomeOf(attempts(bracketRules, inner, ctx))),\n Option.orElse(() =>\n // 中に角括弧を含む中身は単純ターゲットではないので、記法として成立させない。\n inner.includes(\"[\") || inner.includes(\"]\")\n ? Option.none()\n : Option.firstSomeOf(attempts(simpleTargetRules, inner, ctx)),\n ),\n )\n\n/**\n * `[...]` 全般。閉じ括弧は深さを数えて探すので `[* a [b] c]` でも外側で閉じる。\n *\n * 中身が空 (`[]` / `[ ]`) のときと、どのルールにも当たらなかったときは None を返す。\n * 呼び出し側は先頭の `[` を素の文字として 1 文字進めるので、内側が改めて走査される。\n */\nexport const bracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\") return Option.none()\n\n return pipe(\n findClosingBracket(source, index),\n Option.flatMap((end) => {\n const inner = source.slice(index + 1, end)\n if (inner.trim() === \"\") return Option.none()\n\n const innerCtx: BracketScanContext = {\n ...ctx,\n innerOrigin: shiftOrigin(ctx.origin, index + 1),\n }\n return pipe(\n parseInner(inner, innerCtx),\n Option.map((node) => ({ node, length: end + 1 - index })),\n )\n }),\n )\n}\n","import { Option } from \"effect\"\n\nimport { isTagBoundary } from \"../../core/scan\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** タグ名に使えない文字。空白と角括弧と `#` 自身で終わる。 */\nconst TAG_NAME_RE = /^[^\\s[\\]#]+/\n\n/** `#tag`。行頭または空白の直後でだけ成立する (単語の途中の `#` を拾わないため)。 */\nexport const hashtagConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"#\" || !isTagBoundary(source, index)) return Option.none()\n\n const match = source.slice(index + 1).match(TAG_NAME_RE)\n if (!match) return Option.none()\n\n const value = match[0]\n return Option.some({ node: { type: \"hashtag\", value }, length: value.length + 1 })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** バッククォートで囲んだインラインコード。閉じるバッククォートが無ければ成立しない。 */\nexport const inlineCodeConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"`\") return Option.none()\n\n const end = source.indexOf(\"`\", index + 1)\n if (end < 0) return Option.none()\n\n return Option.some({\n node: { type: \"inlineCode\", value: source.slice(index + 1, end) },\n length: end + 1 - index,\n })\n}\n","import { Option } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/**\n * `[[...]]` — Cosense Web の strong。`]]` で閉じるときだけ成立する\n * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。\n *\n * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。\n */\nexport const strongBracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\" || source[index + 1] !== \"[\") return Option.none()\n\n const end = source.indexOf(\"]]\", index + 2)\n if (end < 0) return Option.none()\n\n const inner = source.slice(index + 2, end)\n const length = end + 2 - index\n\n if (isImageUrl(inner)) {\n const node: InlineNodeInit = { type: \"image\", src: inner, large: true }\n return Option.some({ node, length })\n }\n\n return Option.some({\n node: {\n type: \"decoration\",\n value: inner,\n // `[[x]]` は記号を書かないが、太字なので `[* x]` と同じ扱いにする\n markers: [\"*\"],\n bold: true,\n italic: false,\n strike: false,\n underline: false,\n sizeLevel: 0,\n children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false),\n },\n length,\n })\n}\n","/**\n * 行内の走査ルールの登録場所。**配列の順序が仕様**なので既存の順序を動かさないこと。\n * `[[` は `[` より先に試す必要がある (先に `[` が食べると strong にならない)。\n */\nimport type { InternalConstruct } from \"../internal-types\"\nimport { bareUrlConstruct } from \"./bare-url\"\nimport { bracketConstruct } from \"./bracket\"\nimport { hashtagConstruct } from \"./hashtag\"\nimport { inlineCodeConstruct } from \"./inline-code\"\nimport { strongBracketConstruct } from \"./strong-bracket\"\n\nexport const inlineConstructs: readonly InternalConstruct[] = [\n strongBracketConstruct,\n bracketConstruct,\n inlineCodeConstruct,\n hashtagConstruct,\n bareUrlConstruct,\n]\n\nexport { bareUrlConstruct, bracketConstruct, hashtagConstruct }\nexport { inlineCodeConstruct, strongBracketConstruct }\n","/**\n * tokenize.ts — 行内の走査ループ。\n *\n * 1 文字ずつ進みながら construct を順に試し、成立したらノードにする。どれも成立しなければ\n * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて\n * text ノードにする。位置の付与はこのループだけが行う。\n */\nimport { Option } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { InlineNode, InlineNodeInit, Position } from \"../types\"\nimport { inlineConstructs } from \"./constructs\"\nimport type { InternalBracketRule, InternalConstruct, ScanContext } from \"./internal-types\"\nimport type { BracketRule, ConstructMatch, Extension, InlineConstruct } from \"./types\"\n\nexport interface TokenizeInlineOptions {\n /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */\n readonly allowDecoration?: boolean\n /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */\n readonly origin?: {\n readonly line?: number\n readonly column?: number\n readonly offset?: number\n }\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\n/** 走査に渡せる形まで並べ終えたルール。`constructs` は先頭から順に試される。 */\nexport interface ResolvedExtensions {\n readonly constructs: readonly InternalConstruct[]\n readonly bracketRules: readonly InternalBracketRule[]\n}\n\n/** 拡張のルールは null で返すので、中のルールと同じく Option で返す形に包む。 */\nconst fromConstruct =\n (construct: InlineConstruct): InternalConstruct =>\n (source, index, ctx) =>\n Option.fromNullable(construct(source, index, ctx))\n\nconst fromBracketRule =\n (rule: BracketRule): InternalBracketRule =>\n (inner, ctx) =>\n Option.fromNullable(rule(inner, ctx))\n\n/** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */\nexport const resolveExtensions = (extensions?: readonly Extension[]): ResolvedExtensions => {\n if (extensions === undefined || extensions.length === 0) {\n return { constructs: inlineConstructs, bracketRules: [] }\n }\n const constructs = extensions.flatMap((extension) => extension.constructs ?? [])\n const bracketRules = extensions.flatMap((extension) => extension.bracketRules ?? [])\n return {\n constructs: [...constructs.map(fromConstruct), ...inlineConstructs],\n bracketRules: bracketRules.map(fromBracketRule),\n }\n}\n\n/**\n * ノード本体に位置を与える。判別共用体へのスプレッドは TS が型を保てないため\n * ここだけキャストする (position 以外のフィールドは触っていない)。\n */\nconst withPosition = (node: InlineNodeInit, position: Position): InlineNode =>\n ({ ...node, position }) as InlineNode\n\n/** ジェネレータにしているのは、最初に成立した時点で残りのルールを評価しないため。 */\nfunction* attempts(\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n) {\n for (const construct of constructs) yield construct(source, index, ctx)\n}\n\nconst firstMatch = (\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n): Option.Option<ConstructMatch> => Option.firstSomeOf(attempts(constructs, source, index, ctx))\n\nconst scan = (\n source: string,\n origin: Origin,\n allowDecoration: boolean,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => {\n const out: InlineNode[] = []\n const ctx: ScanContext = {\n allowDecoration,\n origin,\n bracketRules: rules.bracketRules,\n tokenize: (inner, innerOrigin, innerAllowDecoration) =>\n scan(inner, innerOrigin, innerAllowDecoration, rules),\n }\n\n let textStart = 0\n let index = 0\n\n const flushText = (end: number) => {\n if (end <= textStart) return\n out.push({\n type: \"text\",\n value: source.slice(textStart, end),\n position: spanAt(origin, textStart, end),\n })\n }\n\n while (index < source.length) {\n const match = firstMatch(rules.constructs, source, index, ctx)\n if (Option.isNone(match)) {\n index++\n continue\n }\n flushText(index)\n const { node, length } = match.value\n out.push(withPosition(node, spanAt(origin, index, index + length)))\n index += length\n textStart = index\n }\n flushText(index)\n\n return out\n}\n\nconst resolveOrigin = (origin: TokenizeInlineOptions[\"origin\"]): Origin => ({\n line: origin?.line ?? 0,\n column: origin?.column ?? 0,\n offset: origin?.offset ?? 0,\n})\n\n/**\n * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。\n *\n * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。\n */\nexport const tokenizeInline = (\n source: string,\n options?: TokenizeInlineOptions,\n): readonly InlineNode[] =>\n scan(\n source,\n resolveOrigin(options?.origin),\n options?.allowDecoration ?? true,\n resolveExtensions(options?.extensions),\n )\n\n/**\n * 解決済みのルールで走査する内部向け入口。\n * 行ごとに `tokenizeInline` を呼ぶと拡張の解決が毎回走るので、それを避けたいときに使う。\n */\nexport const tokenizeInlineWith = (\n source: string,\n origin: Origin,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => scan(source, origin, true, rules)\n","/**\n * classify.ts — 1 行が何の行かを判定する。インライン記法の中身には立ち入らない。\n *\n * 結果はタグ付きユニオンなので、行の種類を増やすと分岐している側が\n * exhaustive チェックで対応漏れを教えてくれる。\n */\nimport { Option, pipe } from \"effect\"\n\nimport { leadingWhitespace } from \"../core/scan\"\n\nconst CODE_HEADER_RE = /^code:(.+)$/\nconst TABLE_HEADER_RE = /^table:(.+)$/\n/** 引用記号と、その直後の空白 1 つ (あれば本文から除く)。 */\nconst QUOTE_RE = /^>\\s?/\nconst MONOSPACE_RE = /^[$%]/\n\nexport interface CodeHeaderLine {\n readonly _tag: \"codeHeader\"\n readonly indent: number\n readonly filename: string\n}\n\nexport interface TableHeaderLine {\n readonly _tag: \"tableHeader\"\n readonly indent: number\n readonly name: string\n}\n\nexport interface ContentLine {\n readonly _tag: \"content\"\n readonly indent: number\n readonly quote: boolean\n readonly monospace: boolean\n /** 行の先頭から数えて、インライン記法の解析を始める位置 (インデントと引用記号の分だけ進む) */\n readonly contentOffset: number\n}\n\nexport type LineRole = CodeHeaderLine | TableHeaderLine | ContentLine\n\nconst codeHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(CODE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"codeHeader\", indent, filename: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst tableHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(TABLE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"tableHeader\", indent, name: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst content = (rest: string, indent: number): LineRole => {\n const quoteMark = rest.match(QUOTE_RE)?.[0] ?? \"\"\n const body = rest.slice(quoteMark.length)\n return {\n _tag: \"content\",\n indent,\n quote: quoteMark !== \"\",\n monospace: MONOSPACE_RE.test(body),\n contentOffset: indent + quoteMark.length,\n }\n}\n\n/**\n * 行の役割を判定する。`code:` / `table:` は行の意味がページの文脈\n * (コードブロックの中かどうか) で変わるので、ここでは「ヘッダの形をしている」ことだけを見る。\n */\nexport const classifyLine = (raw: string): LineRole => {\n const indent = leadingWhitespace(raw).length\n const rest = raw.slice(indent)\n return pipe(\n Option.firstSomeOf([codeHeader(rest, indent), tableHeader(rest, indent)]),\n Option.getOrElse(() => content(rest, indent)),\n )\n}\n\n/** 行頭の空白の文字数。全角スペースも 1 文字として数える。 */\nexport const indentOf = (raw: string): number => leadingWhitespace(raw).length\n","/**\n * build.ts — 行の並びをブロックに畳む。\n *\n * `code:` / `table:` は「ヘッダ行 + それより深いインデントの行」で 1 つのブロックになる。\n * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる\n * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。\n */\nimport { Match } from \"effect\"\n\nimport { type Origin, originOfLine, shiftOrigin, spanAt } from \"../core/position\"\nimport type {\n CodeBlock,\n CodeLine,\n InlineNode,\n LineBlock,\n TableBlock,\n TableCell,\n TableRow,\n TitleBlock,\n TopLevelBlock,\n} from \"../types\"\nimport { classifyLine, type ContentLine, indentOf } from \"./classify\"\n\n/** 1 行分の入力。`offset` はソース全文における行頭の位置。 */\nexport interface SourceLine {\n readonly index: number\n readonly text: string\n readonly offset: number\n}\n\n/**\n * 行の一部をインライン記法として解析する関数。\n * 記法ルールの構成は呼び出し側が束ねて渡すので、この層は拡張の有無を知らずに済む。\n */\nexport type TokenizeLine = (source: string, origin: Origin) => readonly InlineNode[]\n\n/** 行の中身と、テーブルのセルの中身の読み方。セルは行と違う規則で読むので別に受け取る。 */\nexport interface Tokenizers {\n readonly line: TokenizeLine\n readonly tableCell: TokenizeLine\n}\n\nconst lineOrigin = (line: SourceLine): Origin => originOfLine(line.index, line.offset)\n\nconst wholeLine = (line: SourceLine) => spanAt(lineOrigin(line), 0, line.text.length)\n\n/** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */\nconst dedent = (text: string, amount: number): string =>\n text.slice(Math.min(amount, indentOf(text)))\n\nconst titleBlock = (line: SourceLine, tokenize: TokenizeLine): TitleBlock => ({\n type: \"title\",\n value: line.text,\n children: tokenize(line.text, lineOrigin(line)),\n position: wholeLine(line),\n})\n\nconst lineBlock = (line: SourceLine, role: ContentLine, tokenize: TokenizeLine): LineBlock => ({\n type: \"line\",\n indent: role.indent,\n quote: role.quote,\n monospace: role.monospace,\n children: tokenize(line.text.slice(role.contentOffset), {\n line: line.index,\n column: role.contentOffset,\n offset: line.offset + role.contentOffset,\n }),\n position: wholeLine(line),\n})\n\nconst codeLine = (line: SourceLine, headerIndent: number): CodeLine => ({\n type: \"codeLine\",\n // ヘッダより 1 段深いところがコードの左端。それより深いインデントは中身なので保つ。\n value: dedent(line.text, headerIndent + 1),\n position: wholeLine(line),\n})\n\nconst tableCells = (line: SourceLine, tokenize: TokenizeLine): readonly TableCell[] => {\n const origin = lineOrigin(line)\n const indent = indentOf(line.text)\n const values = line.text.slice(indent).split(\"\\t\")\n return values.map((value, index) => {\n // 前にあるセルと、その区切りのタブ 1 文字ずつのぶんだけ右から始まる。\n const start = indent + values.slice(0, index).reduce((sum, cell) => sum + cell.length + 1, 0)\n return {\n type: \"tableCell\",\n value,\n children: tokenize(value, shiftOrigin(origin, start)),\n position: spanAt(origin, start, start + value.length),\n }\n })\n}\n\nconst tableRow = (line: SourceLine, tokenize: TokenizeLine): TableRow => ({\n type: \"tableRow\",\n cells: tableCells(line, tokenize),\n position: wholeLine(line),\n})\n\n/** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */\nconst bodyEnd = (lines: readonly SourceLine[], start: number, headerIndent: number): number => {\n let end = start\n while (end < lines.length) {\n const line = lines[end]\n if (line === undefined || indentOf(line.text) <= headerIndent) break\n end++\n }\n return end\n}\n\nconst blockPosition = (header: SourceLine, body: readonly SourceLine[]) => ({\n start: wholeLine(header).start,\n end: wholeLine(body[body.length - 1] ?? header).end,\n})\n\nconst codeBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n filename: string,\n indent: number,\n): CodeBlock => ({\n type: \"codeBlock\",\n filename,\n indent,\n lines: body.map((line) => codeLine(line, indent)),\n position: blockPosition(header, body),\n})\n\nconst tableBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n name: string,\n indent: number,\n tokenize: TokenizeLine,\n): TableBlock => ({\n type: \"table\",\n name,\n indent,\n rows: body.map((line) => tableRow(line, tokenize)),\n position: blockPosition(header, body),\n})\n\n/**\n * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる\n * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。\n */\nexport const buildBlocks = (\n lines: readonly SourceLine[],\n tokenizers: Tokenizers,\n): readonly TopLevelBlock[] => {\n const tokenize = tokenizers.line\n const blocks: TopLevelBlock[] = []\n const head = lines[0]\n if (head === undefined) return blocks\n blocks.push(titleBlock(head, tokenize))\n\n let index = 1\n while (index < lines.length) {\n const line = lines[index]\n if (line === undefined) break\n\n index = Match.value(classifyLine(line.text)).pipe(\n Match.tag(\"codeHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(codeBlock(line, lines.slice(index + 1, end), role.filename, role.indent))\n return end\n }),\n Match.tag(\"tableHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(\n tableBlock(\n line,\n lines.slice(index + 1, end),\n role.name,\n role.indent,\n tokenizers.tableCell,\n ),\n )\n return end\n }),\n Match.tag(\"content\", (role) => {\n blocks.push(lineBlock(line, role, tokenize))\n return index + 1\n }),\n Match.exhaustive,\n )\n }\n\n return blocks\n}\n\n/**\n * 1 行だけを通常行として組み立てる。\n * ページの文脈が無いので `code:` / `table:` ヘッダもブロックにはせず、通常行として扱う。\n */\nexport const buildLineBlock = (line: SourceLine, tokenize: TokenizeLine): LineBlock => {\n const role = classifyLine(line.text)\n return lineBlock(\n line,\n role._tag === \"content\"\n ? role\n : {\n _tag: \"content\",\n indent: role.indent,\n quote: false,\n monospace: false,\n contentOffset: role.indent,\n },\n tokenize,\n )\n}\n","/**\n * table-cell.ts — テーブルのセルの中の記法。\n *\n * Cosense Web はセルの中ではリンクの記法 (`[title]` / `[https://…]` / `[/project/page]` /\n * 裸の URL / `#tag`) だけを読み、それ以外は書いたままの文字として出す。\n * ここでは行と同じ規則でいったん読み、残さないノードを書いたままの文字に戻す。\n * 最初からリンクの規則だけで読むと、`[* 太字]` が `* 太字` というページへのリンクになってしまうため。\n *\n * どのノードを残すかは拡張の `keepInTableCell` で足せる (`tableCellNotation`)。\n */\nimport { Match, Option, pipe } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { Decoration, InlineNode, TextNode } from \"../types\"\nimport type { Extension } from \"./types\"\n\n/** セルの中でも記法として残すかどうか。 */\nexport type KeepInTableCell = (node: InlineNode) => boolean\n\n/** Cosense Web がセルの中でも読む記法。 */\nconst LINK_TYPES: ReadonlySet<InlineNode[\"type\"]> = new Set([\n \"internalLink\",\n \"externalLink\",\n \"projectLink\",\n \"hashtag\",\n])\n\n/** リンクの記法は必ず残し、それに加えて拡張のどれかが残すと言ったノードを残す。 */\nexport const keepInTableCellOf = (extensions: readonly Extension[] = []): KeepInTableCell => {\n const keeps = extensions.flatMap((extension) =>\n extension.keepInTableCell === undefined ? [] : [extension.keepInTableCell],\n )\n return (node) => LINK_TYPES.has(node.type) || keeps.some((keep) => keep(node))\n}\n\nconst joinTexts = (previous: TextNode, next: TextNode): TextNode => ({\n type: \"text\",\n value: previous.value + next.value,\n position: { start: previous.position.start, end: next.position.end },\n})\n\nconst isText = (node: InlineNode): node is TextNode => node.type === \"text\"\n\n/** `start` から続く text ノードの並び。 */\nconst textRunFrom = (nodes: readonly InlineNode[], start: number): readonly TextNode[] => {\n const end = nodes.findIndex((node, index) => index > start && !isText(node))\n return nodes.slice(start, end === -1 ? nodes.length : end).filter(isText)\n}\n\n/**\n * 隣り合う text ノードを 1 つにまとめる。行を読んだときと同じ形にするため。\n * text の並びの先頭で並び全体をまとめ、続きの text は先頭に含まれているので出さない。\n */\nconst mergeTexts = (nodes: readonly InlineNode[]): readonly InlineNode[] =>\n nodes.flatMap((node, index): readonly InlineNode[] =>\n !isText(node)\n ? [node]\n : pipe(Option.fromNullable(nodes[index - 1]), Option.exists(isText))\n ? []\n : [textRunFrom(nodes, index).reduce(joinTexts)],\n )\n\n/**\n * 行と同じ規則で読んだセルのノード列から、`keep` が残さないノードを書いたままの文字に戻す。\n * `source` はセルの中身、`origin` はその先頭のソース上の位置。\n *\n * 装飾を残さないときは記号の部分だけを文字に戻し、中身は同じように辿る。\n * `[* [リンク]]` の中のリンクは残す。装飾を残すときも、その中身は同じ規則で辿る。\n */\nexport const keepNotation = (\n nodes: readonly InlineNode[],\n source: string,\n origin: Origin,\n keep: KeepInTableCell,\n): readonly InlineNode[] => {\n /** ソース上の `[start, end)` を、書いたままの文字のノードにする。 */\n const literal = (start: number, end: number): readonly TextNode[] =>\n start >= end\n ? []\n : [\n {\n type: \"text\",\n value: source.slice(start - origin.offset, end - origin.offset),\n position: spanAt(origin, start - origin.offset, end - origin.offset),\n },\n ]\n\n const inside = (children: readonly InlineNode[]): readonly InlineNode[] =>\n mergeTexts(children.flatMap(demote))\n\n /** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */\n const unwrap = (node: Decoration): readonly InlineNode[] =>\n pipe(\n Option.all([\n Option.fromNullable(node.children[0]),\n Option.fromNullable(node.children[node.children.length - 1]),\n ]),\n Option.match({\n onNone: () => literal(node.position.start.offset, node.position.end.offset),\n onSome: ([first, last]) => [\n ...literal(node.position.start.offset, first.position.start.offset),\n ...node.children.flatMap(demote),\n ...literal(last.position.end.offset, node.position.end.offset),\n ],\n }),\n )\n\n const demote = (node: InlineNode): readonly InlineNode[] =>\n Match.value(node).pipe(\n Match.when({ type: \"text\" }, (text) => [text]),\n Match.when({ type: \"decoration\" }, (decoration) =>\n keep(decoration)\n ? [{ ...decoration, children: inside(decoration.children) }]\n : unwrap(decoration),\n ),\n Match.when(keep, (kept) => [kept]),\n Match.orElse((other) => literal(other.position.start.offset, other.position.end.offset)),\n )\n\n return inside(nodes)\n}\n","/**\n * parse.ts — ページ全文の入口。\n */\nimport { buildBlocks, buildLineBlock, type SourceLine } from \"./block/build\"\nimport { keepInTableCellOf, keepNotation } from \"./inline/table-cell\"\nimport { resolveExtensions, tokenizeInlineWith } from \"./inline/tokenize\"\nimport type { Extension } from \"./inline/types\"\nimport type { LineBlock, Page } from \"./types\"\n\n/**\n * CR / CRLF を LF に揃える。\n *\n * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の\n * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。\n */\nexport const normalizeLineEndings = (source: string): string => source.replace(/\\r\\n?/g, \"\\n\")\n\nexport interface ParseOptions {\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\nconst toSourceLines = (source: string): readonly SourceLine[] => {\n const lines: SourceLine[] = []\n let offset = 0\n for (const [index, text] of source.split(\"\\n\").entries()) {\n lines.push({ index, text, offset })\n offset += text.length + 1 // 改行 1 文字ぶん進める\n }\n return lines\n}\n\n/**\n * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。\n * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。\n *\n * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。\n */\nexport const parse = (source: string, options?: ParseOptions): Page => {\n const normalized = normalizeLineEndings(source)\n const lines = toSourceLines(normalized)\n const rules = resolveExtensions(options?.extensions)\n const keepInTableCell = keepInTableCellOf(options?.extensions)\n const last = lines[lines.length - 1]\n\n return {\n type: \"page\",\n children: buildBlocks(lines, {\n line: (text, origin) => tokenizeInlineWith(text, origin, rules),\n tableCell: (text, origin) =>\n keepNotation(tokenizeInlineWith(text, origin, rules), text, origin, keepInTableCell),\n }),\n position: {\n start: { line: 0, column: 0, offset: 0 },\n end: {\n line: last?.index ?? 0,\n column: last?.text.length ?? 0,\n offset: normalized.length,\n },\n },\n }\n}\n\nexport interface ParseLineOptions extends ParseOptions {\n /** この行がページ内の何行目か (既定: 0) */\n readonly line?: number\n /** この行の行頭がソース全文のどこにあるか (既定: 0) */\n readonly offset?: number\n}\n\n/**\n * 1 行だけを通常行としてパースする。\n *\n * `code:` / `table:` はページの文脈があって初めてブロックになるので、\n * ここでは通常行として扱う。\n */\nexport const parseLine = (raw: string, options?: ParseLineOptions): LineBlock => {\n const rules = resolveExtensions(options?.extensions)\n return buildLineBlock(\n { index: options?.line ?? 0, text: raw, offset: options?.offset ?? 0 },\n (text, origin) => tokenizeInlineWith(text, origin, rules),\n )\n}\n\nexport interface Parser {\n readonly parse: (source: string) => Page\n readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, \"extensions\">) => LineBlock\n}\n\n/**\n * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、\n * 呼び出しごとに options を渡さずに済む。\n */\nexport const createParser = (options?: ParseOptions): Parser => ({\n parse: (source) => parse(source, options),\n parseLine: (raw, lineOptions) => parseLine(raw, { ...lineOptions, ...options }),\n})\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { n as hasImageExtension, r as isImageUrl, t as asImageSrc } from "./image-url-eQ6X-oN0.mjs";
|
|
2
|
-
import { a as shiftOrigin, i as originOfLine, o as spanAt, r as decorationRule } from "./decoration-BB1cS4lH.mjs";
|
|
3
2
|
import { Match, Option, pipe } from "effect";
|
|
3
|
+
//#region src/core/position.ts
|
|
4
|
+
const originOfLine = (line, offset) => ({
|
|
5
|
+
line,
|
|
6
|
+
column: 0,
|
|
7
|
+
offset
|
|
8
|
+
});
|
|
9
|
+
/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */
|
|
10
|
+
const shiftOrigin = (origin, by) => ({
|
|
11
|
+
line: origin.line,
|
|
12
|
+
column: origin.column + by,
|
|
13
|
+
offset: origin.offset + by
|
|
14
|
+
});
|
|
15
|
+
const pointAt = (origin, index) => ({
|
|
16
|
+
line: origin.line,
|
|
17
|
+
column: origin.column + index,
|
|
18
|
+
offset: origin.offset + index
|
|
19
|
+
});
|
|
20
|
+
/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */
|
|
21
|
+
const spanAt = (origin, start, end) => ({
|
|
22
|
+
start: pointAt(origin, start),
|
|
23
|
+
end: pointAt(origin, end)
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
4
26
|
//#region src/inline/constructs/bare-url.ts
|
|
5
27
|
const URL_RE = /^https?:\/\/[^\s\]]+/i;
|
|
6
28
|
/**
|
|
@@ -53,6 +75,61 @@ const isTagBoundary = (s, index) => {
|
|
|
53
75
|
return prev === " " || prev === " " || prev === " ";
|
|
54
76
|
};
|
|
55
77
|
//#endregion
|
|
78
|
+
//#region src/inline/bracket-rules/decoration.ts
|
|
79
|
+
/**
|
|
80
|
+
* `[<記号の並び> 中身]`。記号は Cosense の文字装飾の記号 `` !"#%&'()*+,-./{|}<>_~= `` で、
|
|
81
|
+
* Cosense Web はこの集合からなる並びをすべて装飾として読む (help-jp「文字装飾記法」)。
|
|
82
|
+
* `=` は help-jp が将来の別用途のために使わないよう求めているが、今の Cosense Web は
|
|
83
|
+
* 装飾として読むので含める。
|
|
84
|
+
* どの記号が装飾になるかは構文なので、設定で変えられるようにしない。
|
|
85
|
+
* `$` と `[` はこの一覧に無い。`[$ x]` は数式、`[[x]]` は太字の別の記法で、どちらも別のルールが読む。
|
|
86
|
+
*
|
|
87
|
+
* 文字列から組み立てずに正規表現のリテラルで持つのは、トップレベルで関数を呼ばないため
|
|
88
|
+
* (`sideEffects: false`)。
|
|
89
|
+
*/
|
|
90
|
+
const DECORATION_PATTERN = /^([!"#%&'()*+,\-./{|}<>_~=]+)\s+([\s\S]+)$/;
|
|
91
|
+
/**
|
|
92
|
+
* 見た目が決まっている記号。bold などのフラグはこれだけから決める。
|
|
93
|
+
* ほかの記号は `markers` に残るだけで、見た目はプロジェクトの UserCSS が付ける。
|
|
94
|
+
*/
|
|
95
|
+
const STYLED_MARKERS = {
|
|
96
|
+
bold: "*",
|
|
97
|
+
italic: "/",
|
|
98
|
+
strike: "-",
|
|
99
|
+
underline: "_"
|
|
100
|
+
};
|
|
101
|
+
const MAX_SIZE_LEVEL = 4;
|
|
102
|
+
/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */
|
|
103
|
+
const uniqueChars = (marks) => [...new Set(marks)];
|
|
104
|
+
/**
|
|
105
|
+
* `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)、
|
|
106
|
+
* および見た目の付かない記号の装飾 (`[! 注意]`)。
|
|
107
|
+
*
|
|
108
|
+
* 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。
|
|
109
|
+
* そのため子の走査は allowDecoration=false で行う。
|
|
110
|
+
* 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
|
|
111
|
+
*/
|
|
112
|
+
const decorationRule = (inner, ctx) => {
|
|
113
|
+
if (!ctx.allowDecoration) return Option.none();
|
|
114
|
+
const match = inner.match(DECORATION_PATTERN);
|
|
115
|
+
if (!match) return Option.none();
|
|
116
|
+
const marks = match[1] ?? "";
|
|
117
|
+
const value = match[2] ?? "";
|
|
118
|
+
const stars = [...marks].filter((mark) => mark === STYLED_MARKERS.bold).length;
|
|
119
|
+
const valueOffset = inner.length - value.length;
|
|
120
|
+
return Option.some({
|
|
121
|
+
type: "decoration",
|
|
122
|
+
value,
|
|
123
|
+
markers: uniqueChars(marks),
|
|
124
|
+
bold: stars > 0,
|
|
125
|
+
italic: marks.includes(STYLED_MARKERS.italic),
|
|
126
|
+
strike: marks.includes(STYLED_MARKERS.strike),
|
|
127
|
+
underline: marks.includes(STYLED_MARKERS.underline),
|
|
128
|
+
sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
|
|
129
|
+
children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
56
133
|
//#region src/inline/bracket-rules/formula.ts
|
|
57
134
|
/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */
|
|
58
135
|
const formulaRule = (inner) => inner.startsWith("$") ? Option.some({
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["attempts"],"sources":["../src/inline/constructs/bare-url.ts","../src/core/scan.ts","../src/inline/bracket-rules/formula.ts","../src/inline/bracket-rules/icon.ts","../src/inline/bracket-rules/image-extension.ts","../src/inline/bracket-rules/internal-link.ts","../src/inline/bracket-rules/project-link.ts","../src/inline/bracket-rules/url.ts","../src/inline/bracket-rules/index.ts","../src/inline/constructs/bracket.ts","../src/inline/constructs/hashtag.ts","../src/inline/constructs/inline-code.ts","../src/inline/constructs/strong-bracket.ts","../src/inline/constructs/index.ts","../src/inline/tokenize.ts","../src/block/classify.ts","../src/block/build.ts","../src/inline/table-cell.ts","../src/parse.ts"],"sourcesContent":["import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\nconst URL_RE = /^https?:\\/\\/[^\\s\\]]+/i\n\n/**\n * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない\n * (Cosense Web に合わせている。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。\n */\nexport const bareUrlConstruct: InternalConstruct = (source, index) => {\n const head = source[index]\n if (head !== \"h\" && head !== \"H\") return Option.none()\n\n const match = source.slice(index).match(URL_RE)\n if (!match) return Option.none()\n\n const url = match[0]\n return Option.some({\n node: { type: \"externalLink\", label: url, target: url },\n length: url.length,\n })\n}\n","/**\n * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。\n */\nimport { Option } from \"effect\"\n\n/** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */\nconst LEADING_WHITESPACE_RE = /^[ \\t\\u3000]*/\n\nexport const leadingWhitespace = (s: string): string => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? \"\"\n\n/**\n * `openIdx` の `[` に対応する `]` の位置。深さを数えるので\n * `[* a [b] c]` のような入れ子でも外側の `]` を返す。対応が閉じなければ None。\n */\nexport const findClosingBracket = (s: string, openIdx: number): Option.Option<number> => {\n let depth = 0\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"[\") depth++\n else if (ch === \"]\") {\n depth--\n if (depth === 0) return Option.some(i)\n }\n }\n return Option.none()\n}\n\n/** `#` がタグの開始位置か (行頭、または直前が空白)。単語の途中の `#` を除くため。 */\nexport const isTagBoundary = (s: string, index: number): boolean => {\n if (index === 0) return true\n const prev = s[index - 1]\n return prev === \" \" || prev === \"\\t\" || prev === \" \"\n}\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */\nexport const formulaRule: InternalBracketRule = (inner) =>\n inner.startsWith(\"$\")\n ? Option.some({ type: \"formula\", value: inner.slice(1).trim() })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst ICON_RE = /^(.+)\\.icon(?:\\*(\\d+))?$/\n\nconst MIN_COUNT = 1\nconst MAX_COUNT = 20\n\n/** `[user.icon]` と連打 `[user.icon*5]`。個数は 1..20 に収める (描画の暴走を防ぐため)。 */\nexport const iconRule: InternalBracketRule = (inner) => {\n const match = inner.match(ICON_RE)\n if (!match) return Option.none()\n\n const raw = match[2]\n const count = raw ? Math.min(Math.max(Number.parseInt(raw, 10), MIN_COUNT), MAX_COUNT) : MIN_COUNT\n\n return Option.some({ type: \"icon\", user: match[1] ?? \"\", count })\n}\n","import { Option } from \"effect\"\n\nimport { hasImageExtension } from \"../../core/image-url\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。\n *\n * 装飾の中では画像にせずリンク扱いにする (Cosense Web に合わせている)。`[* [a.png]]` の内側は\n * 画像ではなく `a.png` というタイトルの内部リンクになる。\n */\nexport const imageExtensionRule: InternalBracketRule = (inner, ctx) =>\n ctx.allowDecoration && hasImageExtension(inner)\n ? Option.some({ type: \"image\", src: inner })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[title]` — 他のどのルールにも当たらなかった角括弧は内部リンクになる。\n * 常に Some を返す catch-all なので、ルール配列の最後に置くこと。\n */\nexport const internalLinkRule: InternalBracketRule = (inner) =>\n Option.some({ type: \"internalLink\", label: inner, target: inner })\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[/project/title]` — 別プロジェクトのページへのリンク。\n *\n * タイトルは `/` を含みうるので最初の `/` だけで分割する。\n * `[/project]` のようにタイトルが無い書き方も記法としては成立するので、\n * その場合は title を空文字にする (利用側が「プロジェクトそのものへのリンク」と判断できる)。\n */\nexport const projectLinkRule: InternalBracketRule = (inner) => {\n if (!inner.startsWith(\"/\")) return Option.none()\n\n const rest = inner.slice(1)\n const slash = rest.indexOf(\"/\")\n const project = slash < 0 ? rest : rest.slice(0, slash)\n const title = slash < 0 ? \"\" : rest.slice(slash + 1)\n\n return Option.some({ type: \"projectLink\", label: inner, target: inner, project, title })\n}\n","import { Option, pipe } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst URLS_RE = /https?:\\/\\/[^\\s\\]]+/gi\n\n/** 画像 URL が複数あるとき最後のものを採るのはCosense Web の挙動。 */\nconst lastImage = (urls: readonly string[]): Option.Option<string> => {\n const images = urls.filter(isImageUrl)\n return Option.fromNullable(images[images.length - 1])\n}\n\n/** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */\nconst linkFor = (urls: readonly string[], src: string): Option.Option<string> =>\n Option.fromNullable(urls.find((url) => url !== src) ?? urls[0])\n\n/**\n * 中身に URL を含む角括弧。Cosense Web の挙動に合わせて次の順で決める:\n *\n * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする\n * (`[ラベル https://x/a.png]` は画像にならない)。\n * 2. URL だけが 2 つ以上:\n * - 画像 URL があれば「リンク付き画像」。画像が複数なら最後を表示し、それ以外の先頭をリンク先にする。\n * - 画像が無ければ 先頭 = リンク先 / 2 番目 = 表示テキスト。\n * 3. URL が 1 つだけ → 画像なら画像、違えば裸の外部リンク。\n */\nexport const urlRule: InternalBracketRule = (inner) => {\n const urls = inner.match(URLS_RE) ?? []\n if (urls.length === 0) return Option.none()\n\n const label = urls.reduce((rest, url) => rest.replace(url, \" \"), inner).trim()\n if (label !== \"\") {\n return Option.some({\n type: \"externalLink\",\n label,\n target: urls[0] ?? inner,\n })\n }\n\n if (urls.length >= 2) {\n return Option.some(\n pipe(\n lastImage(urls),\n Option.match({\n onSome: (src): InlineNodeInit =>\n pipe(\n linkFor(urls, src),\n Option.match({\n onNone: (): InlineNodeInit => ({ type: \"image\", src }),\n onSome: (link): InlineNodeInit => ({ type: \"image\", src, link }),\n }),\n ),\n onNone: (): InlineNodeInit => ({\n type: \"externalLink\",\n label: urls[1] ?? urls[0] ?? \"\",\n target: urls[0] ?? inner,\n }),\n }),\n ),\n )\n }\n\n const only = urls[0] ?? inner\n return Option.some(\n pipe(\n Option.liftPredicate(only, isImageUrl),\n Option.match({\n onSome: (src): InlineNodeInit => ({ type: \"image\", src }),\n onNone: (): InlineNodeInit => ({ type: \"externalLink\", label: only, target: only }),\n }),\n ),\n )\n}\n","/**\n * `[...]` の中身を解釈するルールの登録場所。**配列の順序が仕様**なので、\n * 既存の順序を動かさないこと (順序が変わると `[* [リンク]ですね]` のような\n * 入れ子ケースの解釈が変わる)。新しいルールは 1 ファイル 1 ルールで追加する。\n */\nimport type { InternalBracketRule } from \"../internal-types\"\nimport { decorationRule } from \"./decoration\"\nimport { formulaRule } from \"./formula\"\nimport { iconRule } from \"./icon\"\nimport { imageExtensionRule } from \"./image-extension\"\nimport { internalLinkRule } from \"./internal-link\"\nimport { projectLinkRule } from \"./project-link\"\nimport { urlRule } from \"./url\"\n\n/** 中身に角括弧を含んでいても成立しうるルール。 */\nexport const bracketRules: readonly InternalBracketRule[] = [formulaRule, decorationRule]\n\n/**\n * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。\n * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。\n * 末尾の internalLinkRule は常に成立する catch-all。\n */\nexport const simpleTargetRules: readonly InternalBracketRule[] = [\n iconRule,\n urlRule,\n imageExtensionRule,\n projectLinkRule,\n internalLinkRule,\n]\n\nexport { decorationRule, formulaRule, iconRule, imageExtensionRule, internalLinkRule }\nexport { projectLinkRule, urlRule }\n","import { Option, pipe } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport { findClosingBracket } from \"../../core/scan\"\nimport { bracketRules, simpleTargetRules } from \"../bracket-rules\"\nimport type { BracketScanContext, InternalBracketRule, InternalConstruct } from \"../internal-types\"\n\n/** ルールを順に試す。ジェネレータにしているのは最初に成立した時点で残りを評価しないため。 */\nfunction* attempts(rules: readonly InternalBracketRule[], inner: string, ctx: BracketScanContext) {\n for (const rule of rules) yield rule(inner, ctx)\n}\n\nconst parseInner = (inner: string, ctx: BracketScanContext) =>\n pipe(\n // 拡張のルールが最優先。既定の記法を上書きできるようにするため。\n Option.firstSomeOf(attempts(ctx.bracketRules, inner, ctx)),\n Option.orElse(() => Option.firstSomeOf(attempts(bracketRules, inner, ctx))),\n Option.orElse(() =>\n // 中に角括弧を含む中身は単純ターゲットではないので、記法として成立させない。\n inner.includes(\"[\") || inner.includes(\"]\")\n ? Option.none()\n : Option.firstSomeOf(attempts(simpleTargetRules, inner, ctx)),\n ),\n )\n\n/**\n * `[...]` 全般。閉じ括弧は深さを数えて探すので `[* a [b] c]` でも外側で閉じる。\n *\n * 中身が空 (`[]` / `[ ]`) のときと、どのルールにも当たらなかったときは None を返す。\n * 呼び出し側は先頭の `[` を素の文字として 1 文字進めるので、内側が改めて走査される。\n */\nexport const bracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\") return Option.none()\n\n return pipe(\n findClosingBracket(source, index),\n Option.flatMap((end) => {\n const inner = source.slice(index + 1, end)\n if (inner.trim() === \"\") return Option.none()\n\n const innerCtx: BracketScanContext = {\n ...ctx,\n innerOrigin: shiftOrigin(ctx.origin, index + 1),\n }\n return pipe(\n parseInner(inner, innerCtx),\n Option.map((node) => ({ node, length: end + 1 - index })),\n )\n }),\n )\n}\n","import { Option } from \"effect\"\n\nimport { isTagBoundary } from \"../../core/scan\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** タグ名に使えない文字。空白と角括弧と `#` 自身で終わる。 */\nconst TAG_NAME_RE = /^[^\\s[\\]#]+/\n\n/** `#tag`。行頭または空白の直後でだけ成立する (単語の途中の `#` を拾わないため)。 */\nexport const hashtagConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"#\" || !isTagBoundary(source, index)) return Option.none()\n\n const match = source.slice(index + 1).match(TAG_NAME_RE)\n if (!match) return Option.none()\n\n const value = match[0]\n return Option.some({ node: { type: \"hashtag\", value }, length: value.length + 1 })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** バッククォートで囲んだインラインコード。閉じるバッククォートが無ければ成立しない。 */\nexport const inlineCodeConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"`\") return Option.none()\n\n const end = source.indexOf(\"`\", index + 1)\n if (end < 0) return Option.none()\n\n return Option.some({\n node: { type: \"inlineCode\", value: source.slice(index + 1, end) },\n length: end + 1 - index,\n })\n}\n","import { Option } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/**\n * `[[...]]` — Cosense Web の strong。`]]` で閉じるときだけ成立する\n * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。\n *\n * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。\n */\nexport const strongBracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\" || source[index + 1] !== \"[\") return Option.none()\n\n const end = source.indexOf(\"]]\", index + 2)\n if (end < 0) return Option.none()\n\n const inner = source.slice(index + 2, end)\n const length = end + 2 - index\n\n if (isImageUrl(inner)) {\n const node: InlineNodeInit = { type: \"image\", src: inner, large: true }\n return Option.some({ node, length })\n }\n\n return Option.some({\n node: {\n type: \"decoration\",\n value: inner,\n // `[[x]]` は記号を書かないが、太字なので `[* x]` と同じ扱いにする\n markers: [\"*\"],\n bold: true,\n italic: false,\n strike: false,\n underline: false,\n sizeLevel: 0,\n children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false),\n },\n length,\n })\n}\n","/**\n * 行内の走査ルールの登録場所。**配列の順序が仕様**なので既存の順序を動かさないこと。\n * `[[` は `[` より先に試す必要がある (先に `[` が食べると strong にならない)。\n */\nimport type { InternalConstruct } from \"../internal-types\"\nimport { bareUrlConstruct } from \"./bare-url\"\nimport { bracketConstruct } from \"./bracket\"\nimport { hashtagConstruct } from \"./hashtag\"\nimport { inlineCodeConstruct } from \"./inline-code\"\nimport { strongBracketConstruct } from \"./strong-bracket\"\n\nexport const inlineConstructs: readonly InternalConstruct[] = [\n strongBracketConstruct,\n bracketConstruct,\n inlineCodeConstruct,\n hashtagConstruct,\n bareUrlConstruct,\n]\n\nexport { bareUrlConstruct, bracketConstruct, hashtagConstruct }\nexport { inlineCodeConstruct, strongBracketConstruct }\n","/**\n * tokenize.ts — 行内の走査ループ。\n *\n * 1 文字ずつ進みながら construct を順に試し、成立したらノードにする。どれも成立しなければ\n * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて\n * text ノードにする。位置の付与はこのループだけが行う。\n */\nimport { Option } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { InlineNode, InlineNodeInit, Position } from \"../types\"\nimport { inlineConstructs } from \"./constructs\"\nimport type { InternalBracketRule, InternalConstruct, ScanContext } from \"./internal-types\"\nimport type { BracketRule, ConstructMatch, Extension, InlineConstruct } from \"./types\"\n\nexport interface TokenizeInlineOptions {\n /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */\n readonly allowDecoration?: boolean\n /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */\n readonly origin?: {\n readonly line?: number\n readonly column?: number\n readonly offset?: number\n }\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\n/** 走査に渡せる形まで並べ終えたルール。`constructs` は先頭から順に試される。 */\nexport interface ResolvedExtensions {\n readonly constructs: readonly InternalConstruct[]\n readonly bracketRules: readonly InternalBracketRule[]\n}\n\n/** 拡張のルールは null で返すので、中のルールと同じく Option で返す形に包む。 */\nconst fromConstruct =\n (construct: InlineConstruct): InternalConstruct =>\n (source, index, ctx) =>\n Option.fromNullable(construct(source, index, ctx))\n\nconst fromBracketRule =\n (rule: BracketRule): InternalBracketRule =>\n (inner, ctx) =>\n Option.fromNullable(rule(inner, ctx))\n\n/** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */\nexport const resolveExtensions = (extensions?: readonly Extension[]): ResolvedExtensions => {\n if (extensions === undefined || extensions.length === 0) {\n return { constructs: inlineConstructs, bracketRules: [] }\n }\n const constructs = extensions.flatMap((extension) => extension.constructs ?? [])\n const bracketRules = extensions.flatMap((extension) => extension.bracketRules ?? [])\n return {\n constructs: [...constructs.map(fromConstruct), ...inlineConstructs],\n bracketRules: bracketRules.map(fromBracketRule),\n }\n}\n\n/**\n * ノード本体に位置を与える。判別共用体へのスプレッドは TS が型を保てないため\n * ここだけキャストする (position 以外のフィールドは触っていない)。\n */\nconst withPosition = (node: InlineNodeInit, position: Position): InlineNode =>\n ({ ...node, position }) as InlineNode\n\n/** ジェネレータにしているのは、最初に成立した時点で残りのルールを評価しないため。 */\nfunction* attempts(\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n) {\n for (const construct of constructs) yield construct(source, index, ctx)\n}\n\nconst firstMatch = (\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n): Option.Option<ConstructMatch> => Option.firstSomeOf(attempts(constructs, source, index, ctx))\n\nconst scan = (\n source: string,\n origin: Origin,\n allowDecoration: boolean,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => {\n const out: InlineNode[] = []\n const ctx: ScanContext = {\n allowDecoration,\n origin,\n bracketRules: rules.bracketRules,\n tokenize: (inner, innerOrigin, innerAllowDecoration) =>\n scan(inner, innerOrigin, innerAllowDecoration, rules),\n }\n\n let textStart = 0\n let index = 0\n\n const flushText = (end: number) => {\n if (end <= textStart) return\n out.push({\n type: \"text\",\n value: source.slice(textStart, end),\n position: spanAt(origin, textStart, end),\n })\n }\n\n while (index < source.length) {\n const match = firstMatch(rules.constructs, source, index, ctx)\n if (Option.isNone(match)) {\n index++\n continue\n }\n flushText(index)\n const { node, length } = match.value\n out.push(withPosition(node, spanAt(origin, index, index + length)))\n index += length\n textStart = index\n }\n flushText(index)\n\n return out\n}\n\nconst resolveOrigin = (origin: TokenizeInlineOptions[\"origin\"]): Origin => ({\n line: origin?.line ?? 0,\n column: origin?.column ?? 0,\n offset: origin?.offset ?? 0,\n})\n\n/**\n * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。\n *\n * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。\n */\nexport const tokenizeInline = (\n source: string,\n options?: TokenizeInlineOptions,\n): readonly InlineNode[] =>\n scan(\n source,\n resolveOrigin(options?.origin),\n options?.allowDecoration ?? true,\n resolveExtensions(options?.extensions),\n )\n\n/**\n * 解決済みのルールで走査する内部向け入口。\n * 行ごとに `tokenizeInline` を呼ぶと拡張の解決が毎回走るので、それを避けたいときに使う。\n */\nexport const tokenizeInlineWith = (\n source: string,\n origin: Origin,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => scan(source, origin, true, rules)\n","/**\n * classify.ts — 1 行が何の行かを判定する。インライン記法の中身には立ち入らない。\n *\n * 結果はタグ付きユニオンなので、行の種類を増やすと分岐している側が\n * exhaustive チェックで対応漏れを教えてくれる。\n */\nimport { Option, pipe } from \"effect\"\n\nimport { leadingWhitespace } from \"../core/scan\"\n\nconst CODE_HEADER_RE = /^code:(.+)$/\nconst TABLE_HEADER_RE = /^table:(.+)$/\n/** 引用記号と、その直後の空白 1 つ (あれば本文から除く)。 */\nconst QUOTE_RE = /^>\\s?/\nconst MONOSPACE_RE = /^[$%]/\n\nexport interface CodeHeaderLine {\n readonly _tag: \"codeHeader\"\n readonly indent: number\n readonly filename: string\n}\n\nexport interface TableHeaderLine {\n readonly _tag: \"tableHeader\"\n readonly indent: number\n readonly name: string\n}\n\nexport interface ContentLine {\n readonly _tag: \"content\"\n readonly indent: number\n readonly quote: boolean\n readonly monospace: boolean\n /** 行の先頭から数えて、インライン記法の解析を始める位置 (インデントと引用記号の分だけ進む) */\n readonly contentOffset: number\n}\n\nexport type LineRole = CodeHeaderLine | TableHeaderLine | ContentLine\n\nconst codeHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(CODE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"codeHeader\", indent, filename: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst tableHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(TABLE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"tableHeader\", indent, name: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst content = (rest: string, indent: number): LineRole => {\n const quoteMark = rest.match(QUOTE_RE)?.[0] ?? \"\"\n const body = rest.slice(quoteMark.length)\n return {\n _tag: \"content\",\n indent,\n quote: quoteMark !== \"\",\n monospace: MONOSPACE_RE.test(body),\n contentOffset: indent + quoteMark.length,\n }\n}\n\n/**\n * 行の役割を判定する。`code:` / `table:` は行の意味がページの文脈\n * (コードブロックの中かどうか) で変わるので、ここでは「ヘッダの形をしている」ことだけを見る。\n */\nexport const classifyLine = (raw: string): LineRole => {\n const indent = leadingWhitespace(raw).length\n const rest = raw.slice(indent)\n return pipe(\n Option.firstSomeOf([codeHeader(rest, indent), tableHeader(rest, indent)]),\n Option.getOrElse(() => content(rest, indent)),\n )\n}\n\n/** 行頭の空白の文字数。全角スペースも 1 文字として数える。 */\nexport const indentOf = (raw: string): number => leadingWhitespace(raw).length\n","/**\n * build.ts — 行の並びをブロックに畳む。\n *\n * `code:` / `table:` は「ヘッダ行 + それより深いインデントの行」で 1 つのブロックになる。\n * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる\n * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。\n */\nimport { Match } from \"effect\"\n\nimport { type Origin, originOfLine, shiftOrigin, spanAt } from \"../core/position\"\nimport type {\n CodeBlock,\n CodeLine,\n InlineNode,\n LineBlock,\n TableBlock,\n TableCell,\n TableRow,\n TitleBlock,\n TopLevelBlock,\n} from \"../types\"\nimport { classifyLine, type ContentLine, indentOf } from \"./classify\"\n\n/** 1 行分の入力。`offset` はソース全文における行頭の位置。 */\nexport interface SourceLine {\n readonly index: number\n readonly text: string\n readonly offset: number\n}\n\n/**\n * 行の一部をインライン記法として解析する関数。\n * 記法ルールの構成は呼び出し側が束ねて渡すので、この層は拡張の有無を知らずに済む。\n */\nexport type TokenizeLine = (source: string, origin: Origin) => readonly InlineNode[]\n\n/** 行の中身と、テーブルのセルの中身の読み方。セルは行と違う規則で読むので別に受け取る。 */\nexport interface Tokenizers {\n readonly line: TokenizeLine\n readonly tableCell: TokenizeLine\n}\n\nconst lineOrigin = (line: SourceLine): Origin => originOfLine(line.index, line.offset)\n\nconst wholeLine = (line: SourceLine) => spanAt(lineOrigin(line), 0, line.text.length)\n\n/** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */\nconst dedent = (text: string, amount: number): string =>\n text.slice(Math.min(amount, indentOf(text)))\n\nconst titleBlock = (line: SourceLine, tokenize: TokenizeLine): TitleBlock => ({\n type: \"title\",\n value: line.text,\n children: tokenize(line.text, lineOrigin(line)),\n position: wholeLine(line),\n})\n\nconst lineBlock = (line: SourceLine, role: ContentLine, tokenize: TokenizeLine): LineBlock => ({\n type: \"line\",\n indent: role.indent,\n quote: role.quote,\n monospace: role.monospace,\n children: tokenize(line.text.slice(role.contentOffset), {\n line: line.index,\n column: role.contentOffset,\n offset: line.offset + role.contentOffset,\n }),\n position: wholeLine(line),\n})\n\nconst codeLine = (line: SourceLine, headerIndent: number): CodeLine => ({\n type: \"codeLine\",\n // ヘッダより 1 段深いところがコードの左端。それより深いインデントは中身なので保つ。\n value: dedent(line.text, headerIndent + 1),\n position: wholeLine(line),\n})\n\nconst tableCells = (line: SourceLine, tokenize: TokenizeLine): readonly TableCell[] => {\n const origin = lineOrigin(line)\n const indent = indentOf(line.text)\n const values = line.text.slice(indent).split(\"\\t\")\n return values.map((value, index) => {\n // 前にあるセルと、その区切りのタブ 1 文字ずつのぶんだけ右から始まる。\n const start = indent + values.slice(0, index).reduce((sum, cell) => sum + cell.length + 1, 0)\n return {\n type: \"tableCell\",\n value,\n children: tokenize(value, shiftOrigin(origin, start)),\n position: spanAt(origin, start, start + value.length),\n }\n })\n}\n\nconst tableRow = (line: SourceLine, tokenize: TokenizeLine): TableRow => ({\n type: \"tableRow\",\n cells: tableCells(line, tokenize),\n position: wholeLine(line),\n})\n\n/** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */\nconst bodyEnd = (lines: readonly SourceLine[], start: number, headerIndent: number): number => {\n let end = start\n while (end < lines.length) {\n const line = lines[end]\n if (line === undefined || indentOf(line.text) <= headerIndent) break\n end++\n }\n return end\n}\n\nconst blockPosition = (header: SourceLine, body: readonly SourceLine[]) => ({\n start: wholeLine(header).start,\n end: wholeLine(body[body.length - 1] ?? header).end,\n})\n\nconst codeBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n filename: string,\n indent: number,\n): CodeBlock => ({\n type: \"codeBlock\",\n filename,\n indent,\n lines: body.map((line) => codeLine(line, indent)),\n position: blockPosition(header, body),\n})\n\nconst tableBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n name: string,\n indent: number,\n tokenize: TokenizeLine,\n): TableBlock => ({\n type: \"table\",\n name,\n indent,\n rows: body.map((line) => tableRow(line, tokenize)),\n position: blockPosition(header, body),\n})\n\n/**\n * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる\n * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。\n */\nexport const buildBlocks = (\n lines: readonly SourceLine[],\n tokenizers: Tokenizers,\n): readonly TopLevelBlock[] => {\n const tokenize = tokenizers.line\n const blocks: TopLevelBlock[] = []\n const head = lines[0]\n if (head === undefined) return blocks\n blocks.push(titleBlock(head, tokenize))\n\n let index = 1\n while (index < lines.length) {\n const line = lines[index]\n if (line === undefined) break\n\n index = Match.value(classifyLine(line.text)).pipe(\n Match.tag(\"codeHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(codeBlock(line, lines.slice(index + 1, end), role.filename, role.indent))\n return end\n }),\n Match.tag(\"tableHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(\n tableBlock(\n line,\n lines.slice(index + 1, end),\n role.name,\n role.indent,\n tokenizers.tableCell,\n ),\n )\n return end\n }),\n Match.tag(\"content\", (role) => {\n blocks.push(lineBlock(line, role, tokenize))\n return index + 1\n }),\n Match.exhaustive,\n )\n }\n\n return blocks\n}\n\n/**\n * 1 行だけを通常行として組み立てる。\n * ページの文脈が無いので `code:` / `table:` ヘッダもブロックにはせず、通常行として扱う。\n */\nexport const buildLineBlock = (line: SourceLine, tokenize: TokenizeLine): LineBlock => {\n const role = classifyLine(line.text)\n return lineBlock(\n line,\n role._tag === \"content\"\n ? role\n : {\n _tag: \"content\",\n indent: role.indent,\n quote: false,\n monospace: false,\n contentOffset: role.indent,\n },\n tokenize,\n )\n}\n","/**\n * table-cell.ts — テーブルのセルの中の記法。\n *\n * Cosense Web はセルの中ではリンクの記法 (`[title]` / `[https://…]` / `[/project/page]` /\n * 裸の URL / `#tag`) だけを読み、それ以外は書いたままの文字として出す。\n * ここでは行と同じ規則でいったん読み、残さないノードを書いたままの文字に戻す。\n * 最初からリンクの規則だけで読むと、`[* 太字]` が `* 太字` というページへのリンクになってしまうため。\n *\n * どのノードを残すかは拡張の `keepInTableCell` で足せる (`tableCellNotation`)。\n */\nimport { Match, Option, pipe } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { Decoration, InlineNode, TextNode } from \"../types\"\nimport type { Extension } from \"./types\"\n\n/** セルの中でも記法として残すかどうか。 */\nexport type KeepInTableCell = (node: InlineNode) => boolean\n\n/** Cosense Web がセルの中でも読む記法。 */\nconst LINK_TYPES: ReadonlySet<InlineNode[\"type\"]> = new Set([\n \"internalLink\",\n \"externalLink\",\n \"projectLink\",\n \"hashtag\",\n])\n\n/** リンクの記法は必ず残し、それに加えて拡張のどれかが残すと言ったノードを残す。 */\nexport const keepInTableCellOf = (extensions: readonly Extension[] = []): KeepInTableCell => {\n const keeps = extensions.flatMap((extension) =>\n extension.keepInTableCell === undefined ? [] : [extension.keepInTableCell],\n )\n return (node) => LINK_TYPES.has(node.type) || keeps.some((keep) => keep(node))\n}\n\nconst joinTexts = (previous: TextNode, next: TextNode): TextNode => ({\n type: \"text\",\n value: previous.value + next.value,\n position: { start: previous.position.start, end: next.position.end },\n})\n\nconst isText = (node: InlineNode): node is TextNode => node.type === \"text\"\n\n/** `start` から続く text ノードの並び。 */\nconst textRunFrom = (nodes: readonly InlineNode[], start: number): readonly TextNode[] => {\n const end = nodes.findIndex((node, index) => index > start && !isText(node))\n return nodes.slice(start, end === -1 ? nodes.length : end).filter(isText)\n}\n\n/**\n * 隣り合う text ノードを 1 つにまとめる。行を読んだときと同じ形にするため。\n * text の並びの先頭で並び全体をまとめ、続きの text は先頭に含まれているので出さない。\n */\nconst mergeTexts = (nodes: readonly InlineNode[]): readonly InlineNode[] =>\n nodes.flatMap((node, index): readonly InlineNode[] =>\n !isText(node)\n ? [node]\n : pipe(Option.fromNullable(nodes[index - 1]), Option.exists(isText))\n ? []\n : [textRunFrom(nodes, index).reduce(joinTexts)],\n )\n\n/**\n * 行と同じ規則で読んだセルのノード列から、`keep` が残さないノードを書いたままの文字に戻す。\n * `source` はセルの中身、`origin` はその先頭のソース上の位置。\n *\n * 装飾を残さないときは記号の部分だけを文字に戻し、中身は同じように辿る。\n * `[* [リンク]]` の中のリンクは残す。装飾を残すときも、その中身は同じ規則で辿る。\n */\nexport const keepNotation = (\n nodes: readonly InlineNode[],\n source: string,\n origin: Origin,\n keep: KeepInTableCell,\n): readonly InlineNode[] => {\n /** ソース上の `[start, end)` を、書いたままの文字のノードにする。 */\n const literal = (start: number, end: number): readonly TextNode[] =>\n start >= end\n ? []\n : [\n {\n type: \"text\",\n value: source.slice(start - origin.offset, end - origin.offset),\n position: spanAt(origin, start - origin.offset, end - origin.offset),\n },\n ]\n\n const inside = (children: readonly InlineNode[]): readonly InlineNode[] =>\n mergeTexts(children.flatMap(demote))\n\n /** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */\n const unwrap = (node: Decoration): readonly InlineNode[] =>\n pipe(\n Option.all([\n Option.fromNullable(node.children[0]),\n Option.fromNullable(node.children[node.children.length - 1]),\n ]),\n Option.match({\n onNone: () => literal(node.position.start.offset, node.position.end.offset),\n onSome: ([first, last]) => [\n ...literal(node.position.start.offset, first.position.start.offset),\n ...node.children.flatMap(demote),\n ...literal(last.position.end.offset, node.position.end.offset),\n ],\n }),\n )\n\n const demote = (node: InlineNode): readonly InlineNode[] =>\n Match.value(node).pipe(\n Match.when({ type: \"text\" }, (text) => [text]),\n Match.when({ type: \"decoration\" }, (decoration) =>\n keep(decoration)\n ? [{ ...decoration, children: inside(decoration.children) }]\n : unwrap(decoration),\n ),\n Match.when(keep, (kept) => [kept]),\n Match.orElse((other) => literal(other.position.start.offset, other.position.end.offset)),\n )\n\n return inside(nodes)\n}\n","/**\n * parse.ts — ページ全文の入口。\n */\nimport { buildBlocks, buildLineBlock, type SourceLine } from \"./block/build\"\nimport { keepInTableCellOf, keepNotation } from \"./inline/table-cell\"\nimport { resolveExtensions, tokenizeInlineWith } from \"./inline/tokenize\"\nimport type { Extension } from \"./inline/types\"\nimport type { LineBlock, Page } from \"./types\"\n\n/**\n * CR / CRLF を LF に揃える。\n *\n * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の\n * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。\n */\nexport const normalizeLineEndings = (source: string): string => source.replace(/\\r\\n?/g, \"\\n\")\n\nexport interface ParseOptions {\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\nconst toSourceLines = (source: string): readonly SourceLine[] => {\n const lines: SourceLine[] = []\n let offset = 0\n for (const [index, text] of source.split(\"\\n\").entries()) {\n lines.push({ index, text, offset })\n offset += text.length + 1 // 改行 1 文字ぶん進める\n }\n return lines\n}\n\n/**\n * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。\n * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。\n *\n * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。\n */\nexport const parse = (source: string, options?: ParseOptions): Page => {\n const normalized = normalizeLineEndings(source)\n const lines = toSourceLines(normalized)\n const rules = resolveExtensions(options?.extensions)\n const keepInTableCell = keepInTableCellOf(options?.extensions)\n const last = lines[lines.length - 1]\n\n return {\n type: \"page\",\n children: buildBlocks(lines, {\n line: (text, origin) => tokenizeInlineWith(text, origin, rules),\n tableCell: (text, origin) =>\n keepNotation(tokenizeInlineWith(text, origin, rules), text, origin, keepInTableCell),\n }),\n position: {\n start: { line: 0, column: 0, offset: 0 },\n end: {\n line: last?.index ?? 0,\n column: last?.text.length ?? 0,\n offset: normalized.length,\n },\n },\n }\n}\n\nexport interface ParseLineOptions extends ParseOptions {\n /** この行がページ内の何行目か (既定: 0) */\n readonly line?: number\n /** この行の行頭がソース全文のどこにあるか (既定: 0) */\n readonly offset?: number\n}\n\n/**\n * 1 行だけを通常行としてパースする。\n *\n * `code:` / `table:` はページの文脈があって初めてブロックになるので、\n * ここでは通常行として扱う。\n */\nexport const parseLine = (raw: string, options?: ParseLineOptions): LineBlock => {\n const rules = resolveExtensions(options?.extensions)\n return buildLineBlock(\n { index: options?.line ?? 0, text: raw, offset: options?.offset ?? 0 },\n (text, origin) => tokenizeInlineWith(text, origin, rules),\n )\n}\n\nexport interface Parser {\n readonly parse: (source: string) => Page\n readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, \"extensions\">) => LineBlock\n}\n\n/**\n * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、\n * 呼び出しごとに options を渡さずに済む。\n */\nexport const createParser = (options?: ParseOptions): Parser => ({\n parse: (source) => parse(source, options),\n parseLine: (raw, lineOptions) => parseLine(raw, { ...lineOptions, ...options }),\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["attempts"],"sources":["../src/core/position.ts","../src/inline/constructs/bare-url.ts","../src/core/scan.ts","../src/inline/bracket-rules/decoration.ts","../src/inline/bracket-rules/formula.ts","../src/inline/bracket-rules/icon.ts","../src/inline/bracket-rules/image-extension.ts","../src/inline/bracket-rules/internal-link.ts","../src/inline/bracket-rules/project-link.ts","../src/inline/bracket-rules/url.ts","../src/inline/bracket-rules/index.ts","../src/inline/constructs/bracket.ts","../src/inline/constructs/hashtag.ts","../src/inline/constructs/inline-code.ts","../src/inline/constructs/strong-bracket.ts","../src/inline/constructs/index.ts","../src/inline/tokenize.ts","../src/block/classify.ts","../src/block/build.ts","../src/inline/table-cell.ts","../src/parse.ts"],"sourcesContent":["/**\n * position.ts — 位置情報の組み立て。\n *\n * インラインの走査は「1 行の中の部分文字列」を対象にするため、走査対象の先頭が\n * ソース上のどこかを表す Origin を持ち回り、そこからの相対インデックスで位置を作る。\n * 位置計算をここ 1 箇所に集約することで、装飾の中の再帰でオフセットがずれる事故を防ぐ。\n */\nimport type { Point, Position } from \"../types\"\n\n/** 走査対象の文字列のインデックス 0 が、ソース上のどこにあるか。 */\nexport interface Origin {\n readonly line: number\n readonly column: number\n readonly offset: number\n}\n\nexport const originOfLine = (line: number, offset: number): Origin => ({ line, column: 0, offset })\n\n/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */\nexport const shiftOrigin = (origin: Origin, by: number): Origin => ({\n line: origin.line,\n column: origin.column + by,\n offset: origin.offset + by,\n})\n\nexport const pointAt = (origin: Origin, index: number): Point => ({\n line: origin.line,\n column: origin.column + index,\n offset: origin.offset + index,\n})\n\n/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */\nexport const spanAt = (origin: Origin, start: number, end: number): Position => ({\n start: pointAt(origin, start),\n end: pointAt(origin, end),\n})\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\nconst URL_RE = /^https?:\\/\\/[^\\s\\]]+/i\n\n/**\n * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない\n * (Cosense Web に合わせている。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。\n */\nexport const bareUrlConstruct: InternalConstruct = (source, index) => {\n const head = source[index]\n if (head !== \"h\" && head !== \"H\") return Option.none()\n\n const match = source.slice(index).match(URL_RE)\n if (!match) return Option.none()\n\n const url = match[0]\n return Option.some({\n node: { type: \"externalLink\", label: url, target: url },\n length: url.length,\n })\n}\n","/**\n * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。\n */\nimport { Option } from \"effect\"\n\n/** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */\nconst LEADING_WHITESPACE_RE = /^[ \\t\\u3000]*/\n\nexport const leadingWhitespace = (s: string): string => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? \"\"\n\n/**\n * `openIdx` の `[` に対応する `]` の位置。深さを数えるので\n * `[* a [b] c]` のような入れ子でも外側の `]` を返す。対応が閉じなければ None。\n */\nexport const findClosingBracket = (s: string, openIdx: number): Option.Option<number> => {\n let depth = 0\n for (let i = openIdx; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"[\") depth++\n else if (ch === \"]\") {\n depth--\n if (depth === 0) return Option.some(i)\n }\n }\n return Option.none()\n}\n\n/** `#` がタグの開始位置か (行頭、または直前が空白)。単語の途中の `#` を除くため。 */\nexport const isTagBoundary = (s: string, index: number): boolean => {\n if (index === 0) return true\n const prev = s[index - 1]\n return prev === \" \" || prev === \"\\t\" || prev === \" \"\n}\n","import { Option } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[<記号の並び> 中身]`。記号は Cosense の文字装飾の記号 `` !\"#%&'()*+,-./{|}<>_~= `` で、\n * Cosense Web はこの集合からなる並びをすべて装飾として読む (help-jp「文字装飾記法」)。\n * `=` は help-jp が将来の別用途のために使わないよう求めているが、今の Cosense Web は\n * 装飾として読むので含める。\n * どの記号が装飾になるかは構文なので、設定で変えられるようにしない。\n * `$` と `[` はこの一覧に無い。`[$ x]` は数式、`[[x]]` は太字の別の記法で、どちらも別のルールが読む。\n *\n * 文字列から組み立てずに正規表現のリテラルで持つのは、トップレベルで関数を呼ばないため\n * (`sideEffects: false`)。\n */\nconst DECORATION_PATTERN = /^([!\"#%&'()*+,\\-./{|}<>_~=]+)\\s+([\\s\\S]+)$/\n\n/**\n * 見た目が決まっている記号。bold などのフラグはこれだけから決める。\n * ほかの記号は `markers` に残るだけで、見た目はプロジェクトの UserCSS が付ける。\n */\nconst STYLED_MARKERS = { bold: \"*\", italic: \"/\", strike: \"-\", underline: \"_\" } as const\n\nconst MAX_SIZE_LEVEL = 4\n\n/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */\nconst uniqueChars = (marks: string): readonly string[] => [...new Set(marks)]\n\n/**\n * `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)、\n * および見た目の付かない記号の装飾 (`[! 注意]`)。\n *\n * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。\n * そのため子の走査は allowDecoration=false で行う。\n * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。\n */\nexport const decorationRule: InternalBracketRule = (inner, ctx) => {\n if (!ctx.allowDecoration) return Option.none()\n\n const match = inner.match(DECORATION_PATTERN)\n if (!match) return Option.none()\n\n const marks = match[1] ?? \"\"\n const value = match[2] ?? \"\"\n const stars = [...marks].filter((mark) => mark === STYLED_MARKERS.bold).length\n\n // 正規表現が末尾まで貪欲にマッチするので、中身は inner の末尾側の部分文字列になる。\n const valueOffset = inner.length - value.length\n\n return Option.some({\n type: \"decoration\",\n value,\n markers: uniqueChars(marks),\n bold: stars > 0,\n italic: marks.includes(STYLED_MARKERS.italic),\n strike: marks.includes(STYLED_MARKERS.strike),\n underline: marks.includes(STYLED_MARKERS.underline),\n sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),\n children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false),\n })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */\nexport const formulaRule: InternalBracketRule = (inner) =>\n inner.startsWith(\"$\")\n ? Option.some({ type: \"formula\", value: inner.slice(1).trim() })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst ICON_RE = /^(.+)\\.icon(?:\\*(\\d+))?$/\n\nconst MIN_COUNT = 1\nconst MAX_COUNT = 20\n\n/** `[user.icon]` と連打 `[user.icon*5]`。個数は 1..20 に収める (描画の暴走を防ぐため)。 */\nexport const iconRule: InternalBracketRule = (inner) => {\n const match = inner.match(ICON_RE)\n if (!match) return Option.none()\n\n const raw = match[2]\n const count = raw ? Math.min(Math.max(Number.parseInt(raw, 10), MIN_COUNT), MAX_COUNT) : MIN_COUNT\n\n return Option.some({ type: \"icon\", user: match[1] ?? \"\", count })\n}\n","import { Option } from \"effect\"\n\nimport { hasImageExtension } from \"../../core/image-url\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。\n *\n * 装飾の中では画像にせずリンク扱いにする (Cosense Web に合わせている)。`[* [a.png]]` の内側は\n * 画像ではなく `a.png` というタイトルの内部リンクになる。\n */\nexport const imageExtensionRule: InternalBracketRule = (inner, ctx) =>\n ctx.allowDecoration && hasImageExtension(inner)\n ? Option.some({ type: \"image\", src: inner })\n : Option.none()\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[title]` — 他のどのルールにも当たらなかった角括弧は内部リンクになる。\n * 常に Some を返す catch-all なので、ルール配列の最後に置くこと。\n */\nexport const internalLinkRule: InternalBracketRule = (inner) =>\n Option.some({ type: \"internalLink\", label: inner, target: inner })\n","import { Option } from \"effect\"\n\nimport type { InternalBracketRule } from \"../internal-types\"\n\n/**\n * `[/project/title]` — 別プロジェクトのページへのリンク。\n *\n * タイトルは `/` を含みうるので最初の `/` だけで分割する。\n * `[/project]` のようにタイトルが無い書き方も記法としては成立するので、\n * その場合は title を空文字にする (利用側が「プロジェクトそのものへのリンク」と判断できる)。\n */\nexport const projectLinkRule: InternalBracketRule = (inner) => {\n if (!inner.startsWith(\"/\")) return Option.none()\n\n const rest = inner.slice(1)\n const slash = rest.indexOf(\"/\")\n const project = slash < 0 ? rest : rest.slice(0, slash)\n const title = slash < 0 ? \"\" : rest.slice(slash + 1)\n\n return Option.some({ type: \"projectLink\", label: inner, target: inner, project, title })\n}\n","import { Option, pipe } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\n\nconst URLS_RE = /https?:\\/\\/[^\\s\\]]+/gi\n\n/** 画像 URL が複数あるとき最後のものを採るのはCosense Web の挙動。 */\nconst lastImage = (urls: readonly string[]): Option.Option<string> => {\n const images = urls.filter(isImageUrl)\n return Option.fromNullable(images[images.length - 1])\n}\n\n/** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */\nconst linkFor = (urls: readonly string[], src: string): Option.Option<string> =>\n Option.fromNullable(urls.find((url) => url !== src) ?? urls[0])\n\n/**\n * 中身に URL を含む角括弧。Cosense Web の挙動に合わせて次の順で決める:\n *\n * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする\n * (`[ラベル https://x/a.png]` は画像にならない)。\n * 2. URL だけが 2 つ以上:\n * - 画像 URL があれば「リンク付き画像」。画像が複数なら最後を表示し、それ以外の先頭をリンク先にする。\n * - 画像が無ければ 先頭 = リンク先 / 2 番目 = 表示テキスト。\n * 3. URL が 1 つだけ → 画像なら画像、違えば裸の外部リンク。\n */\nexport const urlRule: InternalBracketRule = (inner) => {\n const urls = inner.match(URLS_RE) ?? []\n if (urls.length === 0) return Option.none()\n\n const label = urls.reduce((rest, url) => rest.replace(url, \" \"), inner).trim()\n if (label !== \"\") {\n return Option.some({\n type: \"externalLink\",\n label,\n target: urls[0] ?? inner,\n })\n }\n\n if (urls.length >= 2) {\n return Option.some(\n pipe(\n lastImage(urls),\n Option.match({\n onSome: (src): InlineNodeInit =>\n pipe(\n linkFor(urls, src),\n Option.match({\n onNone: (): InlineNodeInit => ({ type: \"image\", src }),\n onSome: (link): InlineNodeInit => ({ type: \"image\", src, link }),\n }),\n ),\n onNone: (): InlineNodeInit => ({\n type: \"externalLink\",\n label: urls[1] ?? urls[0] ?? \"\",\n target: urls[0] ?? inner,\n }),\n }),\n ),\n )\n }\n\n const only = urls[0] ?? inner\n return Option.some(\n pipe(\n Option.liftPredicate(only, isImageUrl),\n Option.match({\n onSome: (src): InlineNodeInit => ({ type: \"image\", src }),\n onNone: (): InlineNodeInit => ({ type: \"externalLink\", label: only, target: only }),\n }),\n ),\n )\n}\n","/**\n * `[...]` の中身を解釈するルールの登録場所。**配列の順序が仕様**なので、\n * 既存の順序を動かさないこと (順序が変わると `[* [リンク]ですね]` のような\n * 入れ子ケースの解釈が変わる)。新しいルールは 1 ファイル 1 ルールで追加する。\n */\nimport type { InternalBracketRule } from \"../internal-types\"\nimport { decorationRule } from \"./decoration\"\nimport { formulaRule } from \"./formula\"\nimport { iconRule } from \"./icon\"\nimport { imageExtensionRule } from \"./image-extension\"\nimport { internalLinkRule } from \"./internal-link\"\nimport { projectLinkRule } from \"./project-link\"\nimport { urlRule } from \"./url\"\n\n/** 中身に角括弧を含んでいても成立しうるルール。 */\nexport const bracketRules: readonly InternalBracketRule[] = [formulaRule, decorationRule]\n\n/**\n * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。\n * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。\n * 末尾の internalLinkRule は常に成立する catch-all。\n */\nexport const simpleTargetRules: readonly InternalBracketRule[] = [\n iconRule,\n urlRule,\n imageExtensionRule,\n projectLinkRule,\n internalLinkRule,\n]\n\nexport { decorationRule, formulaRule, iconRule, imageExtensionRule, internalLinkRule }\nexport { projectLinkRule, urlRule }\n","import { Option, pipe } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport { findClosingBracket } from \"../../core/scan\"\nimport { bracketRules, simpleTargetRules } from \"../bracket-rules\"\nimport type { BracketScanContext, InternalBracketRule, InternalConstruct } from \"../internal-types\"\n\n/** ルールを順に試す。ジェネレータにしているのは最初に成立した時点で残りを評価しないため。 */\nfunction* attempts(rules: readonly InternalBracketRule[], inner: string, ctx: BracketScanContext) {\n for (const rule of rules) yield rule(inner, ctx)\n}\n\nconst parseInner = (inner: string, ctx: BracketScanContext) =>\n pipe(\n // 拡張のルールが最優先。既定の記法を上書きできるようにするため。\n Option.firstSomeOf(attempts(ctx.bracketRules, inner, ctx)),\n Option.orElse(() => Option.firstSomeOf(attempts(bracketRules, inner, ctx))),\n Option.orElse(() =>\n // 中に角括弧を含む中身は単純ターゲットではないので、記法として成立させない。\n inner.includes(\"[\") || inner.includes(\"]\")\n ? Option.none()\n : Option.firstSomeOf(attempts(simpleTargetRules, inner, ctx)),\n ),\n )\n\n/**\n * `[...]` 全般。閉じ括弧は深さを数えて探すので `[* a [b] c]` でも外側で閉じる。\n *\n * 中身が空 (`[]` / `[ ]`) のときと、どのルールにも当たらなかったときは None を返す。\n * 呼び出し側は先頭の `[` を素の文字として 1 文字進めるので、内側が改めて走査される。\n */\nexport const bracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\") return Option.none()\n\n return pipe(\n findClosingBracket(source, index),\n Option.flatMap((end) => {\n const inner = source.slice(index + 1, end)\n if (inner.trim() === \"\") return Option.none()\n\n const innerCtx: BracketScanContext = {\n ...ctx,\n innerOrigin: shiftOrigin(ctx.origin, index + 1),\n }\n return pipe(\n parseInner(inner, innerCtx),\n Option.map((node) => ({ node, length: end + 1 - index })),\n )\n }),\n )\n}\n","import { Option } from \"effect\"\n\nimport { isTagBoundary } from \"../../core/scan\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** タグ名に使えない文字。空白と角括弧と `#` 自身で終わる。 */\nconst TAG_NAME_RE = /^[^\\s[\\]#]+/\n\n/** `#tag`。行頭または空白の直後でだけ成立する (単語の途中の `#` を拾わないため)。 */\nexport const hashtagConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"#\" || !isTagBoundary(source, index)) return Option.none()\n\n const match = source.slice(index + 1).match(TAG_NAME_RE)\n if (!match) return Option.none()\n\n const value = match[0]\n return Option.some({ node: { type: \"hashtag\", value }, length: value.length + 1 })\n}\n","import { Option } from \"effect\"\n\nimport type { InternalConstruct } from \"../internal-types\"\n\n/** バッククォートで囲んだインラインコード。閉じるバッククォートが無ければ成立しない。 */\nexport const inlineCodeConstruct: InternalConstruct = (source, index) => {\n if (source[index] !== \"`\") return Option.none()\n\n const end = source.indexOf(\"`\", index + 1)\n if (end < 0) return Option.none()\n\n return Option.some({\n node: { type: \"inlineCode\", value: source.slice(index + 1, end) },\n length: end + 1 - index,\n })\n}\n","import { Option } from \"effect\"\n\nimport { isImageUrl } from \"../../core/image-url\"\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalConstruct } from \"../internal-types\"\n\n/**\n * `[[...]]` — Cosense Web の strong。`]]` で閉じるときだけ成立する\n * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。\n *\n * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。\n */\nexport const strongBracketConstruct: InternalConstruct = (source, index, ctx) => {\n if (source[index] !== \"[\" || source[index + 1] !== \"[\") return Option.none()\n\n const end = source.indexOf(\"]]\", index + 2)\n if (end < 0) return Option.none()\n\n const inner = source.slice(index + 2, end)\n const length = end + 2 - index\n\n if (isImageUrl(inner)) {\n const node: InlineNodeInit = { type: \"image\", src: inner, large: true }\n return Option.some({ node, length })\n }\n\n return Option.some({\n node: {\n type: \"decoration\",\n value: inner,\n // `[[x]]` は記号を書かないが、太字なので `[* x]` と同じ扱いにする\n markers: [\"*\"],\n bold: true,\n italic: false,\n strike: false,\n underline: false,\n sizeLevel: 0,\n children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false),\n },\n length,\n })\n}\n","/**\n * 行内の走査ルールの登録場所。**配列の順序が仕様**なので既存の順序を動かさないこと。\n * `[[` は `[` より先に試す必要がある (先に `[` が食べると strong にならない)。\n */\nimport type { InternalConstruct } from \"../internal-types\"\nimport { bareUrlConstruct } from \"./bare-url\"\nimport { bracketConstruct } from \"./bracket\"\nimport { hashtagConstruct } from \"./hashtag\"\nimport { inlineCodeConstruct } from \"./inline-code\"\nimport { strongBracketConstruct } from \"./strong-bracket\"\n\nexport const inlineConstructs: readonly InternalConstruct[] = [\n strongBracketConstruct,\n bracketConstruct,\n inlineCodeConstruct,\n hashtagConstruct,\n bareUrlConstruct,\n]\n\nexport { bareUrlConstruct, bracketConstruct, hashtagConstruct }\nexport { inlineCodeConstruct, strongBracketConstruct }\n","/**\n * tokenize.ts — 行内の走査ループ。\n *\n * 1 文字ずつ進みながら construct を順に試し、成立したらノードにする。どれも成立しなければ\n * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて\n * text ノードにする。位置の付与はこのループだけが行う。\n */\nimport { Option } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { InlineNode, InlineNodeInit, Position } from \"../types\"\nimport { inlineConstructs } from \"./constructs\"\nimport type { InternalBracketRule, InternalConstruct, ScanContext } from \"./internal-types\"\nimport type { BracketRule, ConstructMatch, Extension, InlineConstruct } from \"./types\"\n\nexport interface TokenizeInlineOptions {\n /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */\n readonly allowDecoration?: boolean\n /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */\n readonly origin?: {\n readonly line?: number\n readonly column?: number\n readonly offset?: number\n }\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\n/** 走査に渡せる形まで並べ終えたルール。`constructs` は先頭から順に試される。 */\nexport interface ResolvedExtensions {\n readonly constructs: readonly InternalConstruct[]\n readonly bracketRules: readonly InternalBracketRule[]\n}\n\n/** 拡張のルールは null で返すので、中のルールと同じく Option で返す形に包む。 */\nconst fromConstruct =\n (construct: InlineConstruct): InternalConstruct =>\n (source, index, ctx) =>\n Option.fromNullable(construct(source, index, ctx))\n\nconst fromBracketRule =\n (rule: BracketRule): InternalBracketRule =>\n (inner, ctx) =>\n Option.fromNullable(rule(inner, ctx))\n\n/** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */\nexport const resolveExtensions = (extensions?: readonly Extension[]): ResolvedExtensions => {\n if (extensions === undefined || extensions.length === 0) {\n return { constructs: inlineConstructs, bracketRules: [] }\n }\n const constructs = extensions.flatMap((extension) => extension.constructs ?? [])\n const bracketRules = extensions.flatMap((extension) => extension.bracketRules ?? [])\n return {\n constructs: [...constructs.map(fromConstruct), ...inlineConstructs],\n bracketRules: bracketRules.map(fromBracketRule),\n }\n}\n\n/**\n * ノード本体に位置を与える。判別共用体へのスプレッドは TS が型を保てないため\n * ここだけキャストする (position 以外のフィールドは触っていない)。\n */\nconst withPosition = (node: InlineNodeInit, position: Position): InlineNode =>\n ({ ...node, position }) as InlineNode\n\n/** ジェネレータにしているのは、最初に成立した時点で残りのルールを評価しないため。 */\nfunction* attempts(\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n) {\n for (const construct of constructs) yield construct(source, index, ctx)\n}\n\nconst firstMatch = (\n constructs: readonly InternalConstruct[],\n source: string,\n index: number,\n ctx: ScanContext,\n): Option.Option<ConstructMatch> => Option.firstSomeOf(attempts(constructs, source, index, ctx))\n\nconst scan = (\n source: string,\n origin: Origin,\n allowDecoration: boolean,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => {\n const out: InlineNode[] = []\n const ctx: ScanContext = {\n allowDecoration,\n origin,\n bracketRules: rules.bracketRules,\n tokenize: (inner, innerOrigin, innerAllowDecoration) =>\n scan(inner, innerOrigin, innerAllowDecoration, rules),\n }\n\n let textStart = 0\n let index = 0\n\n const flushText = (end: number) => {\n if (end <= textStart) return\n out.push({\n type: \"text\",\n value: source.slice(textStart, end),\n position: spanAt(origin, textStart, end),\n })\n }\n\n while (index < source.length) {\n const match = firstMatch(rules.constructs, source, index, ctx)\n if (Option.isNone(match)) {\n index++\n continue\n }\n flushText(index)\n const { node, length } = match.value\n out.push(withPosition(node, spanAt(origin, index, index + length)))\n index += length\n textStart = index\n }\n flushText(index)\n\n return out\n}\n\nconst resolveOrigin = (origin: TokenizeInlineOptions[\"origin\"]): Origin => ({\n line: origin?.line ?? 0,\n column: origin?.column ?? 0,\n offset: origin?.offset ?? 0,\n})\n\n/**\n * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。\n *\n * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。\n */\nexport const tokenizeInline = (\n source: string,\n options?: TokenizeInlineOptions,\n): readonly InlineNode[] =>\n scan(\n source,\n resolveOrigin(options?.origin),\n options?.allowDecoration ?? true,\n resolveExtensions(options?.extensions),\n )\n\n/**\n * 解決済みのルールで走査する内部向け入口。\n * 行ごとに `tokenizeInline` を呼ぶと拡張の解決が毎回走るので、それを避けたいときに使う。\n */\nexport const tokenizeInlineWith = (\n source: string,\n origin: Origin,\n rules: ResolvedExtensions,\n): readonly InlineNode[] => scan(source, origin, true, rules)\n","/**\n * classify.ts — 1 行が何の行かを判定する。インライン記法の中身には立ち入らない。\n *\n * 結果はタグ付きユニオンなので、行の種類を増やすと分岐している側が\n * exhaustive チェックで対応漏れを教えてくれる。\n */\nimport { Option, pipe } from \"effect\"\n\nimport { leadingWhitespace } from \"../core/scan\"\n\nconst CODE_HEADER_RE = /^code:(.+)$/\nconst TABLE_HEADER_RE = /^table:(.+)$/\n/** 引用記号と、その直後の空白 1 つ (あれば本文から除く)。 */\nconst QUOTE_RE = /^>\\s?/\nconst MONOSPACE_RE = /^[$%]/\n\nexport interface CodeHeaderLine {\n readonly _tag: \"codeHeader\"\n readonly indent: number\n readonly filename: string\n}\n\nexport interface TableHeaderLine {\n readonly _tag: \"tableHeader\"\n readonly indent: number\n readonly name: string\n}\n\nexport interface ContentLine {\n readonly _tag: \"content\"\n readonly indent: number\n readonly quote: boolean\n readonly monospace: boolean\n /** 行の先頭から数えて、インライン記法の解析を始める位置 (インデントと引用記号の分だけ進む) */\n readonly contentOffset: number\n}\n\nexport type LineRole = CodeHeaderLine | TableHeaderLine | ContentLine\n\nconst codeHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(CODE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"codeHeader\", indent, filename: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst tableHeader = (rest: string, indent: number): Option.Option<LineRole> => {\n const match = rest.match(TABLE_HEADER_RE)\n return match\n ? Option.some({ _tag: \"tableHeader\", indent, name: (match[1] ?? \"\").trimEnd() })\n : Option.none()\n}\n\nconst content = (rest: string, indent: number): LineRole => {\n const quoteMark = rest.match(QUOTE_RE)?.[0] ?? \"\"\n const body = rest.slice(quoteMark.length)\n return {\n _tag: \"content\",\n indent,\n quote: quoteMark !== \"\",\n monospace: MONOSPACE_RE.test(body),\n contentOffset: indent + quoteMark.length,\n }\n}\n\n/**\n * 行の役割を判定する。`code:` / `table:` は行の意味がページの文脈\n * (コードブロックの中かどうか) で変わるので、ここでは「ヘッダの形をしている」ことだけを見る。\n */\nexport const classifyLine = (raw: string): LineRole => {\n const indent = leadingWhitespace(raw).length\n const rest = raw.slice(indent)\n return pipe(\n Option.firstSomeOf([codeHeader(rest, indent), tableHeader(rest, indent)]),\n Option.getOrElse(() => content(rest, indent)),\n )\n}\n\n/** 行頭の空白の文字数。全角スペースも 1 文字として数える。 */\nexport const indentOf = (raw: string): number => leadingWhitespace(raw).length\n","/**\n * build.ts — 行の並びをブロックに畳む。\n *\n * `code:` / `table:` は「ヘッダ行 + それより深いインデントの行」で 1 つのブロックになる。\n * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる\n * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。\n */\nimport { Match } from \"effect\"\n\nimport { type Origin, originOfLine, shiftOrigin, spanAt } from \"../core/position\"\nimport type {\n CodeBlock,\n CodeLine,\n InlineNode,\n LineBlock,\n TableBlock,\n TableCell,\n TableRow,\n TitleBlock,\n TopLevelBlock,\n} from \"../types\"\nimport { classifyLine, type ContentLine, indentOf } from \"./classify\"\n\n/** 1 行分の入力。`offset` はソース全文における行頭の位置。 */\nexport interface SourceLine {\n readonly index: number\n readonly text: string\n readonly offset: number\n}\n\n/**\n * 行の一部をインライン記法として解析する関数。\n * 記法ルールの構成は呼び出し側が束ねて渡すので、この層は拡張の有無を知らずに済む。\n */\nexport type TokenizeLine = (source: string, origin: Origin) => readonly InlineNode[]\n\n/** 行の中身と、テーブルのセルの中身の読み方。セルは行と違う規則で読むので別に受け取る。 */\nexport interface Tokenizers {\n readonly line: TokenizeLine\n readonly tableCell: TokenizeLine\n}\n\nconst lineOrigin = (line: SourceLine): Origin => originOfLine(line.index, line.offset)\n\nconst wholeLine = (line: SourceLine) => spanAt(lineOrigin(line), 0, line.text.length)\n\n/** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */\nconst dedent = (text: string, amount: number): string =>\n text.slice(Math.min(amount, indentOf(text)))\n\nconst titleBlock = (line: SourceLine, tokenize: TokenizeLine): TitleBlock => ({\n type: \"title\",\n value: line.text,\n children: tokenize(line.text, lineOrigin(line)),\n position: wholeLine(line),\n})\n\nconst lineBlock = (line: SourceLine, role: ContentLine, tokenize: TokenizeLine): LineBlock => ({\n type: \"line\",\n indent: role.indent,\n quote: role.quote,\n monospace: role.monospace,\n children: tokenize(line.text.slice(role.contentOffset), {\n line: line.index,\n column: role.contentOffset,\n offset: line.offset + role.contentOffset,\n }),\n position: wholeLine(line),\n})\n\nconst codeLine = (line: SourceLine, headerIndent: number): CodeLine => ({\n type: \"codeLine\",\n // ヘッダより 1 段深いところがコードの左端。それより深いインデントは中身なので保つ。\n value: dedent(line.text, headerIndent + 1),\n position: wholeLine(line),\n})\n\nconst tableCells = (line: SourceLine, tokenize: TokenizeLine): readonly TableCell[] => {\n const origin = lineOrigin(line)\n const indent = indentOf(line.text)\n const values = line.text.slice(indent).split(\"\\t\")\n return values.map((value, index) => {\n // 前にあるセルと、その区切りのタブ 1 文字ずつのぶんだけ右から始まる。\n const start = indent + values.slice(0, index).reduce((sum, cell) => sum + cell.length + 1, 0)\n return {\n type: \"tableCell\",\n value,\n children: tokenize(value, shiftOrigin(origin, start)),\n position: spanAt(origin, start, start + value.length),\n }\n })\n}\n\nconst tableRow = (line: SourceLine, tokenize: TokenizeLine): TableRow => ({\n type: \"tableRow\",\n cells: tableCells(line, tokenize),\n position: wholeLine(line),\n})\n\n/** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */\nconst bodyEnd = (lines: readonly SourceLine[], start: number, headerIndent: number): number => {\n let end = start\n while (end < lines.length) {\n const line = lines[end]\n if (line === undefined || indentOf(line.text) <= headerIndent) break\n end++\n }\n return end\n}\n\nconst blockPosition = (header: SourceLine, body: readonly SourceLine[]) => ({\n start: wholeLine(header).start,\n end: wholeLine(body[body.length - 1] ?? header).end,\n})\n\nconst codeBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n filename: string,\n indent: number,\n): CodeBlock => ({\n type: \"codeBlock\",\n filename,\n indent,\n lines: body.map((line) => codeLine(line, indent)),\n position: blockPosition(header, body),\n})\n\nconst tableBlock = (\n header: SourceLine,\n body: readonly SourceLine[],\n name: string,\n indent: number,\n tokenize: TokenizeLine,\n): TableBlock => ({\n type: \"table\",\n name,\n indent,\n rows: body.map((line) => tableRow(line, tokenize)),\n position: blockPosition(header, body),\n})\n\n/**\n * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる\n * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。\n */\nexport const buildBlocks = (\n lines: readonly SourceLine[],\n tokenizers: Tokenizers,\n): readonly TopLevelBlock[] => {\n const tokenize = tokenizers.line\n const blocks: TopLevelBlock[] = []\n const head = lines[0]\n if (head === undefined) return blocks\n blocks.push(titleBlock(head, tokenize))\n\n let index = 1\n while (index < lines.length) {\n const line = lines[index]\n if (line === undefined) break\n\n index = Match.value(classifyLine(line.text)).pipe(\n Match.tag(\"codeHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(codeBlock(line, lines.slice(index + 1, end), role.filename, role.indent))\n return end\n }),\n Match.tag(\"tableHeader\", (role) => {\n const end = bodyEnd(lines, index + 1, role.indent)\n blocks.push(\n tableBlock(\n line,\n lines.slice(index + 1, end),\n role.name,\n role.indent,\n tokenizers.tableCell,\n ),\n )\n return end\n }),\n Match.tag(\"content\", (role) => {\n blocks.push(lineBlock(line, role, tokenize))\n return index + 1\n }),\n Match.exhaustive,\n )\n }\n\n return blocks\n}\n\n/**\n * 1 行だけを通常行として組み立てる。\n * ページの文脈が無いので `code:` / `table:` ヘッダもブロックにはせず、通常行として扱う。\n */\nexport const buildLineBlock = (line: SourceLine, tokenize: TokenizeLine): LineBlock => {\n const role = classifyLine(line.text)\n return lineBlock(\n line,\n role._tag === \"content\"\n ? role\n : {\n _tag: \"content\",\n indent: role.indent,\n quote: false,\n monospace: false,\n contentOffset: role.indent,\n },\n tokenize,\n )\n}\n","/**\n * table-cell.ts — テーブルのセルの中の記法。\n *\n * Cosense Web はセルの中ではリンクの記法 (`[title]` / `[https://…]` / `[/project/page]` /\n * 裸の URL / `#tag`) だけを読み、それ以外は書いたままの文字として出す。\n * ここでは行と同じ規則でいったん読み、残さないノードを書いたままの文字に戻す。\n * 最初からリンクの規則だけで読むと、`[* 太字]` が `* 太字` というページへのリンクになってしまうため。\n *\n * どのノードを残すかは拡張の `keepInTableCell` で足せる (`tableCellNotation`)。\n */\nimport { Match, Option, pipe } from \"effect\"\n\nimport { type Origin, spanAt } from \"../core/position\"\nimport type { Decoration, InlineNode, TextNode } from \"../types\"\nimport type { Extension } from \"./types\"\n\n/** セルの中でも記法として残すかどうか。 */\nexport type KeepInTableCell = (node: InlineNode) => boolean\n\n/** Cosense Web がセルの中でも読む記法。 */\nconst LINK_TYPES: ReadonlySet<InlineNode[\"type\"]> = new Set([\n \"internalLink\",\n \"externalLink\",\n \"projectLink\",\n \"hashtag\",\n])\n\n/** リンクの記法は必ず残し、それに加えて拡張のどれかが残すと言ったノードを残す。 */\nexport const keepInTableCellOf = (extensions: readonly Extension[] = []): KeepInTableCell => {\n const keeps = extensions.flatMap((extension) =>\n extension.keepInTableCell === undefined ? [] : [extension.keepInTableCell],\n )\n return (node) => LINK_TYPES.has(node.type) || keeps.some((keep) => keep(node))\n}\n\nconst joinTexts = (previous: TextNode, next: TextNode): TextNode => ({\n type: \"text\",\n value: previous.value + next.value,\n position: { start: previous.position.start, end: next.position.end },\n})\n\nconst isText = (node: InlineNode): node is TextNode => node.type === \"text\"\n\n/** `start` から続く text ノードの並び。 */\nconst textRunFrom = (nodes: readonly InlineNode[], start: number): readonly TextNode[] => {\n const end = nodes.findIndex((node, index) => index > start && !isText(node))\n return nodes.slice(start, end === -1 ? nodes.length : end).filter(isText)\n}\n\n/**\n * 隣り合う text ノードを 1 つにまとめる。行を読んだときと同じ形にするため。\n * text の並びの先頭で並び全体をまとめ、続きの text は先頭に含まれているので出さない。\n */\nconst mergeTexts = (nodes: readonly InlineNode[]): readonly InlineNode[] =>\n nodes.flatMap((node, index): readonly InlineNode[] =>\n !isText(node)\n ? [node]\n : pipe(Option.fromNullable(nodes[index - 1]), Option.exists(isText))\n ? []\n : [textRunFrom(nodes, index).reduce(joinTexts)],\n )\n\n/**\n * 行と同じ規則で読んだセルのノード列から、`keep` が残さないノードを書いたままの文字に戻す。\n * `source` はセルの中身、`origin` はその先頭のソース上の位置。\n *\n * 装飾を残さないときは記号の部分だけを文字に戻し、中身は同じように辿る。\n * `[* [リンク]]` の中のリンクは残す。装飾を残すときも、その中身は同じ規則で辿る。\n */\nexport const keepNotation = (\n nodes: readonly InlineNode[],\n source: string,\n origin: Origin,\n keep: KeepInTableCell,\n): readonly InlineNode[] => {\n /** ソース上の `[start, end)` を、書いたままの文字のノードにする。 */\n const literal = (start: number, end: number): readonly TextNode[] =>\n start >= end\n ? []\n : [\n {\n type: \"text\",\n value: source.slice(start - origin.offset, end - origin.offset),\n position: spanAt(origin, start - origin.offset, end - origin.offset),\n },\n ]\n\n const inside = (children: readonly InlineNode[]): readonly InlineNode[] =>\n mergeTexts(children.flatMap(demote))\n\n /** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */\n const unwrap = (node: Decoration): readonly InlineNode[] =>\n pipe(\n Option.all([\n Option.fromNullable(node.children[0]),\n Option.fromNullable(node.children[node.children.length - 1]),\n ]),\n Option.match({\n onNone: () => literal(node.position.start.offset, node.position.end.offset),\n onSome: ([first, last]) => [\n ...literal(node.position.start.offset, first.position.start.offset),\n ...node.children.flatMap(demote),\n ...literal(last.position.end.offset, node.position.end.offset),\n ],\n }),\n )\n\n const demote = (node: InlineNode): readonly InlineNode[] =>\n Match.value(node).pipe(\n Match.when({ type: \"text\" }, (text) => [text]),\n Match.when({ type: \"decoration\" }, (decoration) =>\n keep(decoration)\n ? [{ ...decoration, children: inside(decoration.children) }]\n : unwrap(decoration),\n ),\n Match.when(keep, (kept) => [kept]),\n Match.orElse((other) => literal(other.position.start.offset, other.position.end.offset)),\n )\n\n return inside(nodes)\n}\n","/**\n * parse.ts — ページ全文の入口。\n */\nimport { buildBlocks, buildLineBlock, type SourceLine } from \"./block/build\"\nimport { keepInTableCellOf, keepNotation } from \"./inline/table-cell\"\nimport { resolveExtensions, tokenizeInlineWith } from \"./inline/tokenize\"\nimport type { Extension } from \"./inline/types\"\nimport type { LineBlock, Page } from \"./types\"\n\n/**\n * CR / CRLF を LF に揃える。\n *\n * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の\n * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。\n */\nexport const normalizeLineEndings = (source: string): string => source.replace(/\\r\\n?/g, \"\\n\")\n\nexport interface ParseOptions {\n /** 記法の拡張。既定のルールより先に試される */\n readonly extensions?: readonly Extension[]\n}\n\nconst toSourceLines = (source: string): readonly SourceLine[] => {\n const lines: SourceLine[] = []\n let offset = 0\n for (const [index, text] of source.split(\"\\n\").entries()) {\n lines.push({ index, text, offset })\n offset += text.length + 1 // 改行 1 文字ぶん進める\n }\n return lines\n}\n\n/**\n * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。\n * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。\n *\n * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。\n */\nexport const parse = (source: string, options?: ParseOptions): Page => {\n const normalized = normalizeLineEndings(source)\n const lines = toSourceLines(normalized)\n const rules = resolveExtensions(options?.extensions)\n const keepInTableCell = keepInTableCellOf(options?.extensions)\n const last = lines[lines.length - 1]\n\n return {\n type: \"page\",\n children: buildBlocks(lines, {\n line: (text, origin) => tokenizeInlineWith(text, origin, rules),\n tableCell: (text, origin) =>\n keepNotation(tokenizeInlineWith(text, origin, rules), text, origin, keepInTableCell),\n }),\n position: {\n start: { line: 0, column: 0, offset: 0 },\n end: {\n line: last?.index ?? 0,\n column: last?.text.length ?? 0,\n offset: normalized.length,\n },\n },\n }\n}\n\nexport interface ParseLineOptions extends ParseOptions {\n /** この行がページ内の何行目か (既定: 0) */\n readonly line?: number\n /** この行の行頭がソース全文のどこにあるか (既定: 0) */\n readonly offset?: number\n}\n\n/**\n * 1 行だけを通常行としてパースする。\n *\n * `code:` / `table:` はページの文脈があって初めてブロックになるので、\n * ここでは通常行として扱う。\n */\nexport const parseLine = (raw: string, options?: ParseLineOptions): LineBlock => {\n const rules = resolveExtensions(options?.extensions)\n return buildLineBlock(\n { index: options?.line ?? 0, text: raw, offset: options?.offset ?? 0 },\n (text, origin) => tokenizeInlineWith(text, origin, rules),\n )\n}\n\nexport interface Parser {\n readonly parse: (source: string) => Page\n readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, \"extensions\">) => LineBlock\n}\n\n/**\n * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、\n * 呼び出しごとに options を渡さずに済む。\n */\nexport const createParser = (options?: ParseOptions): Parser => ({\n parse: (source) => parse(source, options),\n parseLine: (raw, lineOptions) => parseLine(raw, { ...lineOptions, ...options }),\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosense-toolbox/parser",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
4
|
"description": "Cosense (Scrapbox) notation parser — every node carries its source position. Zero runtime deps except effect.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -100,7 +100,6 @@
|
|
|
100
100
|
},
|
|
101
101
|
"publishConfig": {
|
|
102
102
|
"access": "public",
|
|
103
|
-
"tag": "beta",
|
|
104
103
|
"registry": "https://registry.npmjs.org/"
|
|
105
104
|
},
|
|
106
105
|
"keywords": [
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Option } from "effect";
|
|
2
|
-
//#region src/core/position.ts
|
|
3
|
-
const originOfLine = (line, offset) => ({
|
|
4
|
-
line,
|
|
5
|
-
column: 0,
|
|
6
|
-
offset
|
|
7
|
-
});
|
|
8
|
-
/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */
|
|
9
|
-
const shiftOrigin = (origin, by) => ({
|
|
10
|
-
line: origin.line,
|
|
11
|
-
column: origin.column + by,
|
|
12
|
-
offset: origin.offset + by
|
|
13
|
-
});
|
|
14
|
-
const pointAt = (origin, index) => ({
|
|
15
|
-
line: origin.line,
|
|
16
|
-
column: origin.column + index,
|
|
17
|
-
offset: origin.offset + index
|
|
18
|
-
});
|
|
19
|
-
/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */
|
|
20
|
-
const spanAt = (origin, start, end) => ({
|
|
21
|
-
start: pointAt(origin, start),
|
|
22
|
-
end: pointAt(origin, end)
|
|
23
|
-
});
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/inline/bracket-rules/decoration.ts
|
|
26
|
-
/** 意味を持つ文字装飾記法の記号。 */
|
|
27
|
-
const OFFICIAL_MARKERS = "*/-_";
|
|
28
|
-
const MAX_SIZE_LEVEL = 4;
|
|
29
|
-
/** 文字クラスの中で特別扱いされる文字を潰す。 */
|
|
30
|
-
const escapeForCharClass = (chars) => chars.replace(/[\\\]^-]/g, "\\$&");
|
|
31
|
-
/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */
|
|
32
|
-
const uniqueChars = (marks) => [...new Set(marks)];
|
|
33
|
-
/**
|
|
34
|
-
* `[<記号> 中身]` を文字装飾記法として読む。`markerChars` に含まれる記号だけを受け付ける。
|
|
35
|
-
*
|
|
36
|
-
* 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。
|
|
37
|
-
* そのため子の走査は allowDecoration=false で行う。
|
|
38
|
-
* 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* 文脈は拡張に渡るものと同じ形 (`BracketRuleContext`) だけを使う。
|
|
42
|
-
* `customDecorations` が拡張のルールとしても使うため。
|
|
43
|
-
*/
|
|
44
|
-
const buildDecorationRule = (markerChars) => {
|
|
45
|
-
const pattern = new RegExp(`^([${escapeForCharClass(markerChars)}]+)\\s+([\\s\\S]+)$`);
|
|
46
|
-
return (inner, ctx) => {
|
|
47
|
-
if (!ctx.allowDecoration) return Option.none();
|
|
48
|
-
const match = inner.match(pattern);
|
|
49
|
-
if (!match) return Option.none();
|
|
50
|
-
const marks = match[1] ?? "";
|
|
51
|
-
const value = match[2] ?? "";
|
|
52
|
-
const stars = (marks.match(/\*/g) ?? []).length;
|
|
53
|
-
const valueOffset = inner.length - value.length;
|
|
54
|
-
return Option.some({
|
|
55
|
-
type: "decoration",
|
|
56
|
-
value,
|
|
57
|
-
markers: uniqueChars(marks),
|
|
58
|
-
bold: stars > 0,
|
|
59
|
-
italic: marks.includes("/"),
|
|
60
|
-
strike: marks.includes("-"),
|
|
61
|
-
underline: marks.includes("_"),
|
|
62
|
-
sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
|
|
63
|
-
children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
|
|
64
|
-
});
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
/** `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。 */
|
|
68
|
-
const decorationRule = buildDecorationRule(OFFICIAL_MARKERS);
|
|
69
|
-
//#endregion
|
|
70
|
-
export { shiftOrigin as a, originOfLine as i, buildDecorationRule as n, spanAt as o, decorationRule as r, OFFICIAL_MARKERS as t };
|
|
71
|
-
|
|
72
|
-
//# sourceMappingURL=decoration-BB1cS4lH.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decoration-BB1cS4lH.mjs","names":[],"sources":["../src/core/position.ts","../src/inline/bracket-rules/decoration.ts"],"sourcesContent":["/**\n * position.ts — 位置情報の組み立て。\n *\n * インラインの走査は「1 行の中の部分文字列」を対象にするため、走査対象の先頭が\n * ソース上のどこかを表す Origin を持ち回り、そこからの相対インデックスで位置を作る。\n * 位置計算をここ 1 箇所に集約することで、装飾の中の再帰でオフセットがずれる事故を防ぐ。\n */\nimport type { Point, Position } from \"../types\"\n\n/** 走査対象の文字列のインデックス 0 が、ソース上のどこにあるか。 */\nexport interface Origin {\n readonly line: number\n readonly column: number\n readonly offset: number\n}\n\nexport const originOfLine = (line: number, offset: number): Origin => ({ line, column: 0, offset })\n\n/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */\nexport const shiftOrigin = (origin: Origin, by: number): Origin => ({\n line: origin.line,\n column: origin.column + by,\n offset: origin.offset + by,\n})\n\nexport const pointAt = (origin: Origin, index: number): Point => ({\n line: origin.line,\n column: origin.column + index,\n offset: origin.offset + index,\n})\n\n/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */\nexport const spanAt = (origin: Origin, start: number, end: number): Position => ({\n start: pointAt(origin, start),\n end: pointAt(origin, end),\n})\n","import { Option } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\nimport type { BracketRuleContext } from \"../types\"\n\n/** 意味を持つ文字装飾記法の記号。 */\nexport const OFFICIAL_MARKERS = \"*/-_\"\n\nconst MAX_SIZE_LEVEL = 4\n\n/** 文字クラスの中で特別扱いされる文字を潰す。 */\nconst escapeForCharClass = (chars: string): string => chars.replace(/[\\\\\\]^-]/g, \"\\\\$&\")\n\n/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */\nconst uniqueChars = (marks: string): readonly string[] => [...new Set(marks)]\n\n/**\n * `[<記号> 中身]` を文字装飾記法として読む。`markerChars` に含まれる記号だけを受け付ける。\n *\n * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。\n * そのため子の走査は allowDecoration=false で行う。\n * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。\n */\n/**\n * 文脈は拡張に渡るものと同じ形 (`BracketRuleContext`) だけを使う。\n * `customDecorations` が拡張のルールとしても使うため。\n */\nexport const buildDecorationRule = (\n markerChars: string,\n): ((inner: string, ctx: BracketRuleContext) => Option.Option<InlineNodeInit>) => {\n const pattern = new RegExp(`^([${escapeForCharClass(markerChars)}]+)\\\\s+([\\\\s\\\\S]+)$`)\n\n return (inner, ctx) => {\n if (!ctx.allowDecoration) return Option.none()\n\n const match = inner.match(pattern)\n if (!match) return Option.none()\n\n const marks = match[1] ?? \"\"\n const value = match[2] ?? \"\"\n const stars = (marks.match(/\\*/g) ?? []).length\n\n // 正規表現が末尾まで貪欲にマッチするので、中身は inner の末尾側の部分文字列になる。\n const valueOffset = inner.length - value.length\n\n return Option.some({\n type: \"decoration\",\n value,\n markers: uniqueChars(marks),\n bold: stars > 0,\n italic: marks.includes(\"/\"),\n strike: marks.includes(\"-\"),\n underline: marks.includes(\"_\"),\n sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),\n children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false),\n })\n }\n}\n\n/** `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。 */\nexport const decorationRule: InternalBracketRule = buildDecorationRule(OFFICIAL_MARKERS)\n"],"mappings":";;AAgBA,MAAa,gBAAgB,MAAc,YAA4B;CAAE;CAAM,QAAQ;CAAG;AAAO;;AAGjG,MAAa,eAAe,QAAgB,QAAwB;CAClE,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS;CACxB,QAAQ,OAAO,SAAS;AAC1B;AAEA,MAAa,WAAW,QAAgB,WAA0B;CAChE,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS;CACxB,QAAQ,OAAO,SAAS;AAC1B;;AAGA,MAAa,UAAU,QAAgB,OAAe,SAA2B;CAC/E,OAAO,QAAQ,QAAQ,KAAK;CAC5B,KAAK,QAAQ,QAAQ,GAAG;AAC1B;;;;AC3BA,MAAa,mBAAmB;AAEhC,MAAM,iBAAiB;;AAGvB,MAAM,sBAAsB,UAA0B,MAAM,QAAQ,aAAa,MAAM;;AAGvF,MAAM,eAAe,UAAqC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;;;;;;;;;;;;AAa5E,MAAa,uBACX,gBACgF;CAChF,MAAM,UAAU,IAAI,OAAO,MAAM,mBAAmB,WAAW,EAAE,oBAAoB;CAErF,QAAQ,OAAO,QAAQ;EACrB,IAAI,CAAC,IAAI,iBAAiB,OAAO,OAAO,KAAK;EAE7C,MAAM,QAAQ,MAAM,MAAM,OAAO;EACjC,IAAI,CAAC,OAAO,OAAO,OAAO,KAAK;EAE/B,MAAM,QAAQ,MAAM,MAAM;EAC1B,MAAM,QAAQ,MAAM,MAAM;EAC1B,MAAM,SAAS,MAAM,MAAM,KAAK,KAAK,CAAC,EAAA,CAAG;EAGzC,MAAM,cAAc,MAAM,SAAS,MAAM;EAEzC,OAAO,OAAO,KAAK;GACjB,MAAM;GACN;GACA,SAAS,YAAY,KAAK;GAC1B,MAAM,QAAQ;GACd,QAAQ,MAAM,SAAS,GAAG;GAC1B,QAAQ,MAAM,SAAS,GAAG;GAC1B,WAAW,MAAM,SAAS,GAAG;GAC7B,WAAW,KAAK,IAAI,KAAK,IAAI,QAAQ,GAAG,CAAC,GAAG,cAAc;GAC1D,UAAU,IAAI,SAAS,OAAO,YAAY,IAAI,aAAa,WAAW,GAAG,KAAK;EAChF,CAAC;CACH;AACF;;AAGA,MAAa,iBAAsC,oBAAoB,gBAAgB"}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
let effect = require("effect");
|
|
2
|
-
//#region src/core/position.ts
|
|
3
|
-
const originOfLine = (line, offset) => ({
|
|
4
|
-
line,
|
|
5
|
-
column: 0,
|
|
6
|
-
offset
|
|
7
|
-
});
|
|
8
|
-
/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */
|
|
9
|
-
const shiftOrigin = (origin, by) => ({
|
|
10
|
-
line: origin.line,
|
|
11
|
-
column: origin.column + by,
|
|
12
|
-
offset: origin.offset + by
|
|
13
|
-
});
|
|
14
|
-
const pointAt = (origin, index) => ({
|
|
15
|
-
line: origin.line,
|
|
16
|
-
column: origin.column + index,
|
|
17
|
-
offset: origin.offset + index
|
|
18
|
-
});
|
|
19
|
-
/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */
|
|
20
|
-
const spanAt = (origin, start, end) => ({
|
|
21
|
-
start: pointAt(origin, start),
|
|
22
|
-
end: pointAt(origin, end)
|
|
23
|
-
});
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/inline/bracket-rules/decoration.ts
|
|
26
|
-
/** 意味を持つ文字装飾記法の記号。 */
|
|
27
|
-
const OFFICIAL_MARKERS = "*/-_";
|
|
28
|
-
const MAX_SIZE_LEVEL = 4;
|
|
29
|
-
/** 文字クラスの中で特別扱いされる文字を潰す。 */
|
|
30
|
-
const escapeForCharClass = (chars) => chars.replace(/[\\\]^-]/g, "\\$&");
|
|
31
|
-
/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */
|
|
32
|
-
const uniqueChars = (marks) => [...new Set(marks)];
|
|
33
|
-
/**
|
|
34
|
-
* `[<記号> 中身]` を文字装飾記法として読む。`markerChars` に含まれる記号だけを受け付ける。
|
|
35
|
-
*
|
|
36
|
-
* 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。
|
|
37
|
-
* そのため子の走査は allowDecoration=false で行う。
|
|
38
|
-
* 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* 文脈は拡張に渡るものと同じ形 (`BracketRuleContext`) だけを使う。
|
|
42
|
-
* `customDecorations` が拡張のルールとしても使うため。
|
|
43
|
-
*/
|
|
44
|
-
const buildDecorationRule = (markerChars) => {
|
|
45
|
-
const pattern = new RegExp(`^([${escapeForCharClass(markerChars)}]+)\\s+([\\s\\S]+)$`);
|
|
46
|
-
return (inner, ctx) => {
|
|
47
|
-
if (!ctx.allowDecoration) return effect.Option.none();
|
|
48
|
-
const match = inner.match(pattern);
|
|
49
|
-
if (!match) return effect.Option.none();
|
|
50
|
-
const marks = match[1] ?? "";
|
|
51
|
-
const value = match[2] ?? "";
|
|
52
|
-
const stars = (marks.match(/\*/g) ?? []).length;
|
|
53
|
-
const valueOffset = inner.length - value.length;
|
|
54
|
-
return effect.Option.some({
|
|
55
|
-
type: "decoration",
|
|
56
|
-
value,
|
|
57
|
-
markers: uniqueChars(marks),
|
|
58
|
-
bold: stars > 0,
|
|
59
|
-
italic: marks.includes("/"),
|
|
60
|
-
strike: marks.includes("-"),
|
|
61
|
-
underline: marks.includes("_"),
|
|
62
|
-
sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
|
|
63
|
-
children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
|
|
64
|
-
});
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
/** `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。 */
|
|
68
|
-
const decorationRule = buildDecorationRule(OFFICIAL_MARKERS);
|
|
69
|
-
//#endregion
|
|
70
|
-
Object.defineProperty(exports, "OFFICIAL_MARKERS", {
|
|
71
|
-
enumerable: true,
|
|
72
|
-
get: function() {
|
|
73
|
-
return OFFICIAL_MARKERS;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
Object.defineProperty(exports, "buildDecorationRule", {
|
|
77
|
-
enumerable: true,
|
|
78
|
-
get: function() {
|
|
79
|
-
return buildDecorationRule;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
Object.defineProperty(exports, "decorationRule", {
|
|
83
|
-
enumerable: true,
|
|
84
|
-
get: function() {
|
|
85
|
-
return decorationRule;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
Object.defineProperty(exports, "originOfLine", {
|
|
89
|
-
enumerable: true,
|
|
90
|
-
get: function() {
|
|
91
|
-
return originOfLine;
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
Object.defineProperty(exports, "shiftOrigin", {
|
|
95
|
-
enumerable: true,
|
|
96
|
-
get: function() {
|
|
97
|
-
return shiftOrigin;
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
Object.defineProperty(exports, "spanAt", {
|
|
101
|
-
enumerable: true,
|
|
102
|
-
get: function() {
|
|
103
|
-
return spanAt;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
//# sourceMappingURL=decoration-BYXIXxCA.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decoration-BYXIXxCA.cjs","names":["Option"],"sources":["../src/core/position.ts","../src/inline/bracket-rules/decoration.ts"],"sourcesContent":["/**\n * position.ts — 位置情報の組み立て。\n *\n * インラインの走査は「1 行の中の部分文字列」を対象にするため、走査対象の先頭が\n * ソース上のどこかを表す Origin を持ち回り、そこからの相対インデックスで位置を作る。\n * 位置計算をここ 1 箇所に集約することで、装飾の中の再帰でオフセットがずれる事故を防ぐ。\n */\nimport type { Point, Position } from \"../types\"\n\n/** 走査対象の文字列のインデックス 0 が、ソース上のどこにあるか。 */\nexport interface Origin {\n readonly line: number\n readonly column: number\n readonly offset: number\n}\n\nexport const originOfLine = (line: number, offset: number): Origin => ({ line, column: 0, offset })\n\n/** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */\nexport const shiftOrigin = (origin: Origin, by: number): Origin => ({\n line: origin.line,\n column: origin.column + by,\n offset: origin.offset + by,\n})\n\nexport const pointAt = (origin: Origin, index: number): Point => ({\n line: origin.line,\n column: origin.column + index,\n offset: origin.offset + index,\n})\n\n/** 走査対象の `[start, end)` が占める Position。end は exclusive。 */\nexport const spanAt = (origin: Origin, start: number, end: number): Position => ({\n start: pointAt(origin, start),\n end: pointAt(origin, end),\n})\n","import { Option } from \"effect\"\n\nimport { shiftOrigin } from \"../../core/position\"\nimport type { InlineNodeInit } from \"../../types\"\nimport type { InternalBracketRule } from \"../internal-types\"\nimport type { BracketRuleContext } from \"../types\"\n\n/** 意味を持つ文字装飾記法の記号。 */\nexport const OFFICIAL_MARKERS = \"*/-_\"\n\nconst MAX_SIZE_LEVEL = 4\n\n/** 文字クラスの中で特別扱いされる文字を潰す。 */\nconst escapeForCharClass = (chars: string): string => chars.replace(/[\\\\\\]^-]/g, \"\\\\$&\")\n\n/** 出現順を保ったまま重複を落とす。`[*** x]` の markers は `['*']` になる。 */\nconst uniqueChars = (marks: string): readonly string[] => [...new Set(marks)]\n\n/**\n * `[<記号> 中身]` を文字装飾記法として読む。`markerChars` に含まれる記号だけを受け付ける。\n *\n * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**。\n * そのため子の走査は allowDecoration=false で行う。\n * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。\n */\n/**\n * 文脈は拡張に渡るものと同じ形 (`BracketRuleContext`) だけを使う。\n * `customDecorations` が拡張のルールとしても使うため。\n */\nexport const buildDecorationRule = (\n markerChars: string,\n): ((inner: string, ctx: BracketRuleContext) => Option.Option<InlineNodeInit>) => {\n const pattern = new RegExp(`^([${escapeForCharClass(markerChars)}]+)\\\\s+([\\\\s\\\\S]+)$`)\n\n return (inner, ctx) => {\n if (!ctx.allowDecoration) return Option.none()\n\n const match = inner.match(pattern)\n if (!match) return Option.none()\n\n const marks = match[1] ?? \"\"\n const value = match[2] ?? \"\"\n const stars = (marks.match(/\\*/g) ?? []).length\n\n // 正規表現が末尾まで貪欲にマッチするので、中身は inner の末尾側の部分文字列になる。\n const valueOffset = inner.length - value.length\n\n return Option.some({\n type: \"decoration\",\n value,\n markers: uniqueChars(marks),\n bold: stars > 0,\n italic: marks.includes(\"/\"),\n strike: marks.includes(\"-\"),\n underline: marks.includes(\"_\"),\n sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),\n children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false),\n })\n }\n}\n\n/** `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。 */\nexport const decorationRule: InternalBracketRule = buildDecorationRule(OFFICIAL_MARKERS)\n"],"mappings":";;AAgBA,MAAa,gBAAgB,MAAc,YAA4B;CAAE;CAAM,QAAQ;CAAG;AAAO;;AAGjG,MAAa,eAAe,QAAgB,QAAwB;CAClE,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS;CACxB,QAAQ,OAAO,SAAS;AAC1B;AAEA,MAAa,WAAW,QAAgB,WAA0B;CAChE,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS;CACxB,QAAQ,OAAO,SAAS;AAC1B;;AAGA,MAAa,UAAU,QAAgB,OAAe,SAA2B;CAC/E,OAAO,QAAQ,QAAQ,KAAK;CAC5B,KAAK,QAAQ,QAAQ,GAAG;AAC1B;;;;AC3BA,MAAa,mBAAmB;AAEhC,MAAM,iBAAiB;;AAGvB,MAAM,sBAAsB,UAA0B,MAAM,QAAQ,aAAa,MAAM;;AAGvF,MAAM,eAAe,UAAqC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;;;;;;;;;;;;AAa5E,MAAa,uBACX,gBACgF;CAChF,MAAM,UAAU,IAAI,OAAO,MAAM,mBAAmB,WAAW,EAAE,oBAAoB;CAErF,QAAQ,OAAO,QAAQ;EACrB,IAAI,CAAC,IAAI,iBAAiB,OAAOA,OAAAA,OAAO,KAAK;EAE7C,MAAM,QAAQ,MAAM,MAAM,OAAO;EACjC,IAAI,CAAC,OAAO,OAAOA,OAAAA,OAAO,KAAK;EAE/B,MAAM,QAAQ,MAAM,MAAM;EAC1B,MAAM,QAAQ,MAAM,MAAM;EAC1B,MAAM,SAAS,MAAM,MAAM,KAAK,KAAK,CAAC,EAAA,CAAG;EAGzC,MAAM,cAAc,MAAM,SAAS,MAAM;EAEzC,OAAOA,OAAAA,OAAO,KAAK;GACjB,MAAM;GACN;GACA,SAAS,YAAY,KAAK;GAC1B,MAAM,QAAQ;GACd,QAAQ,MAAM,SAAS,GAAG;GAC1B,QAAQ,MAAM,SAAS,GAAG;GAC1B,WAAW,MAAM,SAAS,GAAG;GAC7B,WAAW,KAAK,IAAI,KAAK,IAAI,QAAQ,GAAG,CAAC,GAAG,cAAc;GAC1D,UAAU,IAAI,SAAS,OAAO,YAAY,IAAI,aAAa,WAAW,GAAG,KAAK;EAChF,CAAC;CACH;AACF;;AAGA,MAAa,iBAAsC,oBAAoB,gBAAgB"}
|