@cosense-toolbox/parser 0.1.0-beta.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +76 -758
  2. package/dist/{ast-uYWtkHwT.cjs → ast-C0BGz7X2.cjs} +2 -2
  3. package/dist/ast-C0BGz7X2.cjs.map +1 -0
  4. package/dist/{ast-CIKXwSl5.mjs → ast-CMQYawYl.mjs} +2 -2
  5. package/dist/ast-CMQYawYl.mjs.map +1 -0
  6. package/dist/compile.cjs +2 -212
  7. package/dist/compile.cjs.map +1 -1
  8. package/dist/compile.d.cts +17 -125
  9. package/dist/compile.d.cts.map +1 -1
  10. package/dist/compile.d.mts +17 -125
  11. package/dist/compile.d.mts.map +1 -1
  12. package/dist/compile.mjs +3 -206
  13. package/dist/compile.mjs.map +1 -1
  14. package/dist/decoration-BB1cS4lH.mjs +72 -0
  15. package/dist/decoration-BB1cS4lH.mjs.map +1 -0
  16. package/dist/decoration-BYXIXxCA.cjs +107 -0
  17. package/dist/decoration-BYXIXxCA.cjs.map +1 -0
  18. package/dist/extensions.cjs +32 -0
  19. package/dist/extensions.cjs.map +1 -0
  20. package/dist/extensions.d.cts +26 -0
  21. package/dist/extensions.d.cts.map +1 -0
  22. package/dist/extensions.d.mts +26 -0
  23. package/dist/extensions.d.mts.map +1 -0
  24. package/dist/extensions.mjs +30 -0
  25. package/dist/extensions.mjs.map +1 -0
  26. package/dist/html.cjs +458 -0
  27. package/dist/html.cjs.map +1 -0
  28. package/dist/html.d.cts +274 -0
  29. package/dist/html.d.cts.map +1 -0
  30. package/dist/html.d.mts +274 -0
  31. package/dist/html.d.mts.map +1 -0
  32. package/dist/html.mjs +447 -0
  33. package/dist/html.mjs.map +1 -0
  34. package/dist/image-url-YcTed8tg.cjs.map +1 -1
  35. package/dist/image-url-eQ6X-oN0.mjs.map +1 -1
  36. package/dist/index-CrIlOW1k.d.mts +78 -0
  37. package/dist/index-CrIlOW1k.d.mts.map +1 -0
  38. package/dist/index-_o1STNhP.d.cts +78 -0
  39. package/dist/index-_o1STNhP.d.cts.map +1 -0
  40. package/dist/index.cjs +118 -94
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +4 -77
  43. package/dist/index.d.mts +4 -77
  44. package/dist/index.mjs +110 -86
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/schema.cjs +2 -0
  47. package/dist/schema.cjs.map +1 -1
  48. package/dist/schema.d.cts +1 -1
  49. package/dist/schema.d.cts.map +1 -1
  50. package/dist/schema.d.mts +1 -1
  51. package/dist/schema.d.mts.map +1 -1
  52. package/dist/schema.mjs +2 -0
  53. package/dist/schema.mjs.map +1 -1
  54. package/dist/{types-BXlnUFr0.d.mts → types-CdiHju1P.d.mts} +15 -12
  55. package/dist/types-CdiHju1P.d.mts.map +1 -0
  56. package/dist/{types-DVwlUtla.d.cts → types-_-ZAyNkz.d.cts} +15 -12
  57. package/dist/types-_-ZAyNkz.d.cts.map +1 -0
  58. package/dist/{types-Bo_BrmvK.d.cts → types-gWMrsXPX.d.cts} +31 -22
  59. package/dist/types-gWMrsXPX.d.cts.map +1 -0
  60. package/dist/{types-Bo_BrmvK.d.mts → types-gWMrsXPX.d.mts} +31 -22
  61. package/dist/types-gWMrsXPX.d.mts.map +1 -0
  62. package/dist/utils.cjs +2 -2
  63. package/dist/utils.cjs.map +1 -1
  64. package/dist/utils.d.cts +2 -2
  65. package/dist/utils.d.cts.map +1 -1
  66. package/dist/utils.d.mts +2 -2
  67. package/dist/utils.d.mts.map +1 -1
  68. package/dist/utils.mjs +2 -2
  69. package/dist/utils.mjs.map +1 -1
  70. package/package.json +33 -10
  71. package/dist/ast-CIKXwSl5.mjs.map +0 -1
  72. package/dist/ast-uYWtkHwT.cjs.map +0 -1
  73. package/dist/create-compiler-CvlndKOA.d.cts +0 -23
  74. package/dist/create-compiler-CvlndKOA.d.cts.map +0 -1
  75. package/dist/create-compiler-jdA408ny.d.mts +0 -23
  76. package/dist/create-compiler-jdA408ny.d.mts.map +0 -1
  77. package/dist/index.d.cts.map +0 -1
  78. package/dist/index.d.mts.map +0 -1
  79. package/dist/plugin.cjs +0 -0
  80. package/dist/plugin.d.cts +0 -4
  81. package/dist/plugin.d.mts +0 -4
  82. package/dist/plugin.mjs +0 -1
  83. package/dist/types-BXlnUFr0.d.mts.map +0 -1
  84. package/dist/types-Bo_BrmvK.d.cts.map +0 -1
  85. package/dist/types-Bo_BrmvK.d.mts.map +0 -1
  86. package/dist/types-DVwlUtla.d.cts.map +0 -1
@@ -0,0 +1,78 @@
1
+ import { g as InlineNode, w as Page, x as LineBlock } from "./types-gWMrsXPX.mjs";
2
+ import { i as Extension } from "./types-CdiHju1P.mjs";
3
+ import { Option } from "effect";
4
+ //#region src/core/image-url.d.ts
5
+ /** URL が画像として表示されるものか。判定だけを行い、URL は書き換えない。 */
6
+ declare const isImageUrl: (url: string) => boolean;
7
+ /**
8
+ * 画像 URL なら `<img src>` に入れられる形にして返す。画像でなければ null。
9
+ *
10
+ * Gyazo のページ URL はここでだけ `https://i.gyazo.com/{hash}.png` に差し替わる。
11
+ * `parse()` はこの変換を行わない (AST はソースに書かれた文字列を保つ)。
12
+ */
13
+ declare const asImageSrc: (url: string) => string | null;
14
+ //#endregion
15
+ //#region src/inline/tokenize.d.ts
16
+ interface TokenizeInlineOptions {
17
+ /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */
18
+ readonly allowDecoration?: boolean;
19
+ /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */
20
+ readonly origin?: {
21
+ readonly line?: number;
22
+ readonly column?: number;
23
+ readonly offset?: number;
24
+ };
25
+ /** 記法の拡張。既定のルールより先に試される */
26
+ readonly extensions?: readonly Extension[];
27
+ }
28
+ /**
29
+ * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。
30
+ *
31
+ * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。
32
+ */
33
+ declare const tokenizeInline: (source: string, options?: TokenizeInlineOptions) => readonly InlineNode[];
34
+ //#endregion
35
+ //#region src/parse.d.ts
36
+ /**
37
+ * CR / CRLF を LF に揃える。
38
+ *
39
+ * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の
40
+ * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。
41
+ */
42
+ declare const normalizeLineEndings: (source: string) => string;
43
+ interface ParseOptions {
44
+ /** 記法の拡張。既定のルールより先に試される */
45
+ readonly extensions?: readonly Extension[];
46
+ }
47
+ /**
48
+ * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。
49
+ * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。
50
+ *
51
+ * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。
52
+ */
53
+ declare const parse: (source: string, options?: ParseOptions) => Page;
54
+ interface ParseLineOptions extends ParseOptions {
55
+ /** この行がページ内の何行目か (既定: 0) */
56
+ readonly line?: number;
57
+ /** この行の行頭がソース全文のどこにあるか (既定: 0) */
58
+ readonly offset?: number;
59
+ }
60
+ /**
61
+ * 1 行だけを通常行としてパースする。
62
+ *
63
+ * `code:` / `table:` はページの文脈があって初めてブロックになるので、
64
+ * ここでは通常行として扱う。
65
+ */
66
+ declare const parseLine: (raw: string, options?: ParseLineOptions) => LineBlock;
67
+ interface Parser {
68
+ readonly parse: (source: string) => Page;
69
+ readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, "extensions">) => LineBlock;
70
+ }
71
+ /**
72
+ * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、
73
+ * 呼び出しごとに options を渡さずに済む。
74
+ */
75
+ declare const createParser: (options?: ParseOptions) => Parser;
76
+ //#endregion
77
+ export { normalizeLineEndings as a, TokenizeInlineOptions as c, isImageUrl as d, createParser as i, tokenizeInline as l, ParseOptions as n, parse as o, Parser as r, parseLine as s, ParseLineOptions as t, asImageSrc as u };
78
+ //# sourceMappingURL=index-CrIlOW1k.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CrIlOW1k.d.mts","names":[],"sources":["../src/core/image-url.ts","../src/inline/tokenize.ts","../src/parse.ts"],"mappings":";;;;;cAwDa,aAAc;;;;;;;cAQd,aAAc;;;UCjDV;;WAEN;;WAEA;aACE;aACA;aACA;;;WAGF,sBAAsB;;;;;;;cAgHpB,iBACX,gBACA,UAAU,mCACA;;;;;;;;;cC7HC,uBAAwB;UAEpB;;WAEN,sBAAsB;;;;;;;;cAmBpB,QAAS,gBAAgB,UAAU,iBAAe;UAyB9C,yBAAyB;;WAE/B;;WAEA;;;;;;;;cASE,YAAa,aAAa,UAAU,qBAAmB;UAQnD;WACN,QAAQ,mBAAmB;WAC3B,YAAY,aAAa,UAAU,KAAK,oCAAoC;;;;;;cAO1E,eAAgB,UAAU,iBAAe"}
@@ -0,0 +1,78 @@
1
+ import { g as InlineNode, w as Page, x as LineBlock } from "./types-gWMrsXPX.cjs";
2
+ import { i as Extension } from "./types-_-ZAyNkz.cjs";
3
+ import "effect";
4
+ //#region src/core/image-url.d.ts
5
+ /** URL が画像として表示されるものか。判定だけを行い、URL は書き換えない。 */
6
+ declare const isImageUrl: (url: string) => boolean;
7
+ /**
8
+ * 画像 URL なら `<img src>` に入れられる形にして返す。画像でなければ null。
9
+ *
10
+ * Gyazo のページ URL はここでだけ `https://i.gyazo.com/{hash}.png` に差し替わる。
11
+ * `parse()` はこの変換を行わない (AST はソースに書かれた文字列を保つ)。
12
+ */
13
+ declare const asImageSrc: (url: string) => string | null;
14
+ //#endregion
15
+ //#region src/inline/tokenize.d.ts
16
+ interface TokenizeInlineOptions {
17
+ /** 装飾記法を解釈するか (既定: true)。装飾の中身を解析するときだけ false になる */
18
+ readonly allowDecoration?: boolean;
19
+ /** この文字列がページ上のどこにあるか。省略時は 0 行目の先頭 */
20
+ readonly origin?: {
21
+ readonly line?: number;
22
+ readonly column?: number;
23
+ readonly offset?: number;
24
+ };
25
+ /** 記法の拡張。既定のルールより先に試される */
26
+ readonly extensions?: readonly Extension[];
27
+ }
28
+ /**
29
+ * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。
30
+ *
31
+ * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。
32
+ */
33
+ declare const tokenizeInline: (source: string, options?: TokenizeInlineOptions) => readonly InlineNode[];
34
+ //#endregion
35
+ //#region src/parse.d.ts
36
+ /**
37
+ * CR / CRLF を LF に揃える。
38
+ *
39
+ * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の
40
+ * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。
41
+ */
42
+ declare const normalizeLineEndings: (source: string) => string;
43
+ interface ParseOptions {
44
+ /** 記法の拡張。既定のルールより先に試される */
45
+ readonly extensions?: readonly Extension[];
46
+ }
47
+ /**
48
+ * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。
49
+ * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。
50
+ *
51
+ * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。
52
+ */
53
+ declare const parse: (source: string, options?: ParseOptions) => Page;
54
+ interface ParseLineOptions extends ParseOptions {
55
+ /** この行がページ内の何行目か (既定: 0) */
56
+ readonly line?: number;
57
+ /** この行の行頭がソース全文のどこにあるか (既定: 0) */
58
+ readonly offset?: number;
59
+ }
60
+ /**
61
+ * 1 行だけを通常行としてパースする。
62
+ *
63
+ * `code:` / `table:` はページの文脈があって初めてブロックになるので、
64
+ * ここでは通常行として扱う。
65
+ */
66
+ declare const parseLine: (raw: string, options?: ParseLineOptions) => LineBlock;
67
+ interface Parser {
68
+ readonly parse: (source: string) => Page;
69
+ readonly parseLine: (raw: string, options?: Omit<ParseLineOptions, "extensions">) => LineBlock;
70
+ }
71
+ /**
72
+ * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、
73
+ * 呼び出しごとに options を渡さずに済む。
74
+ */
75
+ declare const createParser: (options?: ParseOptions) => Parser;
76
+ //#endregion
77
+ export { normalizeLineEndings as a, TokenizeInlineOptions as c, isImageUrl as d, createParser as i, tokenizeInline as l, ParseOptions as n, parse as o, Parser as r, parseLine as s, ParseLineOptions as t, asImageSrc as u };
78
+ //# sourceMappingURL=index-_o1STNhP.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-_o1STNhP.d.cts","names":[],"sources":["../src/core/image-url.ts","../src/inline/tokenize.ts","../src/parse.ts"],"mappings":";;;;;cAwDa,aAAc;;;;;;;cAQd,aAAc;;;UCjDV;;WAEN;;WAEA;aACE;aACA;aACA;;;WAGF,sBAAsB;;;;;;;cAgHpB,iBACX,gBACA,UAAU,mCACA;;;;;;;;;cC7HC,uBAAwB;UAEpB;;WAEN,sBAAsB;;;;;;;;cAmBpB,QAAS,gBAAgB,UAAU,iBAAe;UAyB9C,yBAAyB;;WAE/B;;WAEA;;;;;;;;cASE,YAAa,aAAa,UAAU,qBAAmB;UAQnD;WACN,QAAQ,mBAAmB;WAC3B,YAAY,aAAa,UAAU,KAAK,oCAAoC;;;;;;cAO1E,eAAgB,UAAU,iBAAe"}
package/dist/index.cjs CHANGED
@@ -1,34 +1,12 @@
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");
3
4
  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
27
5
  //#region src/inline/constructs/bare-url.ts
28
6
  const URL_RE = /^https?:\/\/[^\s\]]+/i;
29
7
  /**
30
8
  * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない
31
- * (本家準拠。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。
9
+ * (Cosense Web に合わせている。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。
32
10
  */
33
11
  const bareUrlConstruct = (source, index) => {
34
12
  const head = source[index];
@@ -51,7 +29,7 @@ const bareUrlConstruct = (source, index) => {
51
29
  * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。
52
30
  */
53
31
  /** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */
54
- const LEADING_WHITESPACE_RE = /^[ \t ]*/;
32
+ const LEADING_WHITESPACE_RE = /^[ \t\u3000]*/;
55
33
  const leadingWhitespace = (s) => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? "";
56
34
  /**
57
35
  * `openIdx` の `[` に対応する `]` の位置。深さを数えるので
@@ -76,37 +54,6 @@ const isTagBoundary = (s, index) => {
76
54
  return prev === " " || prev === " " || prev === " ";
77
55
  };
78
56
  //#endregion
79
- //#region src/inline/bracket-rules/decoration.ts
80
- /** 先頭の装飾記号の run と、空白を挟んだ中身。 */
81
- const DECORATION_RE = /^([*/\-_]+)\s+([\s\S]+)$/;
82
- const MAX_SIZE_LEVEL = 4;
83
- /**
84
- * `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。
85
- *
86
- * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**
87
- * (本家準拠)。そのため子の走査は allowDecoration=false で行う。
88
- * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
89
- */
90
- const decorationRule = (inner, ctx) => {
91
- if (!ctx.allowDecoration) return effect.Option.none();
92
- const match = inner.match(DECORATION_RE);
93
- if (!match) return effect.Option.none();
94
- const marks = match[1] ?? "";
95
- const value = match[2] ?? "";
96
- const stars = (marks.match(/\*/g) ?? []).length;
97
- const valueOffset = inner.length - value.length;
98
- return effect.Option.some({
99
- type: "decoration",
100
- value,
101
- bold: stars > 0,
102
- italic: marks.includes("/"),
103
- strike: marks.includes("-"),
104
- underline: marks.includes("_"),
105
- sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
106
- children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
107
- });
108
- };
109
- //#endregion
110
57
  //#region src/inline/bracket-rules/formula.ts
111
58
  /** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */
112
59
  const formulaRule = (inner) => inner.startsWith("$") ? effect.Option.some({
@@ -135,7 +82,7 @@ const iconRule = (inner) => {
135
82
  /**
136
83
  * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。
137
84
  *
138
- * 装飾の中では画像にせずリンク扱いにする (本家準拠)。`[* [a.png]]` の内側は
85
+ * 装飾の中では画像にせずリンク扱いにする (Cosense Web に合わせている)。`[* [a.png]]` の内側は
139
86
  * 画像ではなく `a.png` というタイトルの内部リンクになる。
140
87
  */
141
88
  const imageExtensionRule = (inner, ctx) => ctx.allowDecoration && require_image_url.hasImageExtension(inner) ? effect.Option.some({
@@ -179,7 +126,7 @@ const projectLinkRule = (inner) => {
179
126
  //#endregion
180
127
  //#region src/inline/bracket-rules/url.ts
181
128
  const URLS_RE = /https?:\/\/[^\s\]]+/gi;
182
- /** 画像 URL が複数あるとき最後のものを採るのは本家の挙動。 */
129
+ /** 画像 URL が複数あるとき最後のものを採るのはCosense Web の挙動。 */
183
130
  const lastImage = (urls) => {
184
131
  const images = urls.filter(require_image_url.isImageUrl);
185
132
  return effect.Option.fromNullable(images[images.length - 1]);
@@ -187,7 +134,7 @@ const lastImage = (urls) => {
187
134
  /** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */
188
135
  const linkFor = (urls, src) => effect.Option.fromNullable(urls.find((url) => url !== src) ?? urls[0]);
189
136
  /**
190
- * 中身に URL を含む角括弧。本家の挙動に合わせて次の順で決める:
137
+ * 中身に URL を含む角括弧。Cosense Web の挙動に合わせて次の順で決める:
191
138
  *
192
139
  * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする
193
140
  * (`[ラベル https://x/a.png]` は画像にならない)。
@@ -239,9 +186,9 @@ const urlRule = (inner) => {
239
186
  //#endregion
240
187
  //#region src/inline/bracket-rules/index.ts
241
188
  /** 中身に角括弧を含んでいても成立しうるルール。 */
242
- const bracketRules = [formulaRule, decorationRule];
189
+ const bracketRules = [formulaRule, require_decoration.decorationRule];
243
190
  /**
244
- * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (本家準拠)。
191
+ * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (Cosense Web に合わせている)。
245
192
  * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。
246
193
  * 末尾の internalLinkRule は常に成立する catch-all。
247
194
  */
@@ -272,7 +219,7 @@ const bracketConstruct = (source, index, ctx) => {
272
219
  if (inner.trim() === "") return effect.Option.none();
273
220
  const innerCtx = {
274
221
  ...ctx,
275
- innerOrigin: shiftOrigin(ctx.origin, index + 1)
222
+ innerOrigin: require_decoration.shiftOrigin(ctx.origin, index + 1)
276
223
  };
277
224
  return (0, effect.pipe)(parseInner(inner, innerCtx), effect.Option.map((node) => ({
278
225
  node,
@@ -316,7 +263,7 @@ const inlineCodeConstruct = (source, index) => {
316
263
  //#endregion
317
264
  //#region src/inline/constructs/strong-bracket.ts
318
265
  /**
319
- * `[[...]]` — 本家の strong。`]]` で閉じるときだけ成立する
266
+ * `[[...]]` — Cosense Web の strong。`]]` で閉じるときだけ成立する
320
267
  * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。
321
268
  *
322
269
  * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。
@@ -342,12 +289,13 @@ const strongBracketConstruct = (source, index, ctx) => {
342
289
  node: {
343
290
  type: "decoration",
344
291
  value: inner,
292
+ markers: ["*"],
345
293
  bold: true,
346
294
  italic: false,
347
295
  strike: false,
348
296
  underline: false,
349
297
  sizeLevel: 0,
350
- children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false)
298
+ children: ctx.tokenize(inner, require_decoration.shiftOrigin(ctx.origin, index + 2), false)
351
299
  },
352
300
  length
353
301
  });
@@ -370,21 +318,20 @@ const inlineConstructs = [
370
318
  * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて
371
319
  * text ノードにする。位置の付与はこのループだけが行う。
372
320
  */
321
+ /** 拡張のルールは null で返すので、中のルールと同じく Option で返す形に包む。 */
322
+ const fromConstruct = (construct) => (source, index, ctx) => effect.Option.fromNullable(construct(source, index, ctx));
323
+ const fromBracketRule = (rule) => (inner, ctx) => effect.Option.fromNullable(rule(inner, ctx));
373
324
  /** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */
374
325
  const resolveExtensions = (extensions) => {
375
326
  if (extensions === void 0 || extensions.length === 0) return {
376
327
  constructs: inlineConstructs,
377
328
  bracketRules: []
378
329
  };
379
- const constructs = [];
380
- const bracketRules = [];
381
- for (const extension of extensions) {
382
- if (extension.constructs) constructs.push(...extension.constructs);
383
- if (extension.bracketRules) bracketRules.push(...extension.bracketRules);
384
- }
330
+ const constructs = extensions.flatMap((extension) => extension.constructs ?? []);
331
+ const bracketRules = extensions.flatMap((extension) => extension.bracketRules ?? []);
385
332
  return {
386
- constructs: [...constructs, ...inlineConstructs],
387
- bracketRules
333
+ constructs: [...constructs.map(fromConstruct), ...inlineConstructs],
334
+ bracketRules: bracketRules.map(fromBracketRule)
388
335
  };
389
336
  };
390
337
  /**
@@ -415,7 +362,7 @@ const scan = (source, origin, allowDecoration, rules) => {
415
362
  out.push({
416
363
  type: "text",
417
364
  value: source.slice(textStart, end),
418
- position: spanAt(origin, textStart, end)
365
+ position: require_decoration.spanAt(origin, textStart, end)
419
366
  });
420
367
  };
421
368
  while (index < source.length) {
@@ -426,7 +373,7 @@ const scan = (source, origin, allowDecoration, rules) => {
426
373
  }
427
374
  flushText(index);
428
375
  const { node, length } = match.value;
429
- out.push(withPosition(node, spanAt(origin, index, index + length)));
376
+ out.push(withPosition(node, require_decoration.spanAt(origin, index, index + length)));
430
377
  index += length;
431
378
  textStart = index;
432
379
  }
@@ -509,8 +456,8 @@ const indentOf = (raw) => leadingWhitespace(raw).length;
509
456
  * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる
510
457
  * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。
511
458
  */
512
- const lineOrigin = (line) => originOfLine(line.index, line.offset);
513
- const wholeLine = (line) => spanAt(lineOrigin(line), 0, line.text.length);
459
+ const lineOrigin = (line) => require_decoration.originOfLine(line.index, line.offset);
460
+ const wholeLine = (line) => require_decoration.spanAt(lineOrigin(line), 0, line.text.length);
514
461
  /** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */
515
462
  const dedent = (text, amount) => text.slice(Math.min(amount, indentOf(text)));
516
463
  const titleBlock = (line, tokenize) => ({
@@ -536,24 +483,23 @@ const codeLine = (line, headerIndent) => ({
536
483
  value: dedent(line.text, headerIndent + 1),
537
484
  position: wholeLine(line)
538
485
  });
539
- const tableCells = (line) => {
540
- const indent = indentOf(line.text);
486
+ const tableCells = (line, tokenize) => {
541
487
  const origin = lineOrigin(line);
542
- const cells = [];
543
- let start = indent;
544
- for (const value of line.text.slice(indent).split(" ")) {
545
- cells.push({
488
+ const indent = indentOf(line.text);
489
+ const values = line.text.slice(indent).split(" ");
490
+ return values.map((value, index) => {
491
+ const start = indent + values.slice(0, index).reduce((sum, cell) => sum + cell.length + 1, 0);
492
+ return {
546
493
  type: "tableCell",
547
494
  value,
548
- position: spanAt(origin, start, start + value.length)
549
- });
550
- start += value.length + 1;
551
- }
552
- return cells;
495
+ children: tokenize(value, require_decoration.shiftOrigin(origin, start)),
496
+ position: require_decoration.spanAt(origin, start, start + value.length)
497
+ };
498
+ });
553
499
  };
554
- const tableRow = (line) => ({
500
+ const tableRow = (line, tokenize) => ({
555
501
  type: "tableRow",
556
- cells: tableCells(line),
502
+ cells: tableCells(line, tokenize),
557
503
  position: wholeLine(line)
558
504
  });
559
505
  /** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */
@@ -577,18 +523,19 @@ const codeBlock = (header, body, filename, indent) => ({
577
523
  lines: body.map((line) => codeLine(line, indent)),
578
524
  position: blockPosition(header, body)
579
525
  });
580
- const tableBlock = (header, body, name, indent) => ({
526
+ const tableBlock = (header, body, name, indent, tokenize) => ({
581
527
  type: "table",
582
528
  name,
583
529
  indent,
584
- rows: body.map(tableRow),
530
+ rows: body.map((line) => tableRow(line, tokenize)),
585
531
  position: blockPosition(header, body)
586
532
  });
587
533
  /**
588
534
  * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる
589
535
  * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。
590
536
  */
591
- const buildBlocks = (lines, tokenize) => {
537
+ const buildBlocks = (lines, tokenizers) => {
538
+ const tokenize = tokenizers.line;
592
539
  const blocks = [];
593
540
  const head = lines[0];
594
541
  if (head === void 0) return blocks;
@@ -603,7 +550,7 @@ const buildBlocks = (lines, tokenize) => {
603
550
  return end;
604
551
  }), effect.Match.tag("tableHeader", (role) => {
605
552
  const end = bodyEnd(lines, index + 1, role.indent);
606
- blocks.push(tableBlock(line, lines.slice(index + 1, end), role.name, role.indent));
553
+ blocks.push(tableBlock(line, lines.slice(index + 1, end), role.name, role.indent, tokenizers.tableCell));
607
554
  return end;
608
555
  }), effect.Match.tag("content", (role) => {
609
556
  blocks.push(lineBlock(line, role, tokenize));
@@ -627,6 +574,79 @@ const buildLineBlock = (line, tokenize) => {
627
574
  }, tokenize);
628
575
  };
629
576
  //#endregion
577
+ //#region src/inline/table-cell.ts
578
+ /**
579
+ * table-cell.ts — テーブルのセルの中の記法。
580
+ *
581
+ * Cosense Web はセルの中ではリンクの記法 (`[title]` / `[https://…]` / `[/project/page]` /
582
+ * 裸の URL / `#tag`) だけを読み、それ以外は書いたままの文字として出す。
583
+ * ここでは行と同じ規則でいったん読み、残さないノードを書いたままの文字に戻す。
584
+ * 最初からリンクの規則だけで読むと、`[* 太字]` が `* 太字` というページへのリンクになってしまうため。
585
+ *
586
+ * どのノードを残すかは拡張の `keepInTableCell` で足せる (`tableCellNotation`)。
587
+ */
588
+ /** Cosense Web がセルの中でも読む記法。 */
589
+ const LINK_TYPES = /* @__PURE__ */ new Set([
590
+ "internalLink",
591
+ "externalLink",
592
+ "projectLink",
593
+ "hashtag"
594
+ ]);
595
+ /** リンクの記法は必ず残し、それに加えて拡張のどれかが残すと言ったノードを残す。 */
596
+ const keepInTableCellOf = (extensions = []) => {
597
+ const keeps = extensions.flatMap((extension) => extension.keepInTableCell === void 0 ? [] : [extension.keepInTableCell]);
598
+ return (node) => LINK_TYPES.has(node.type) || keeps.some((keep) => keep(node));
599
+ };
600
+ const joinTexts = (previous, next) => ({
601
+ type: "text",
602
+ value: previous.value + next.value,
603
+ position: {
604
+ start: previous.position.start,
605
+ end: next.position.end
606
+ }
607
+ });
608
+ const isText = (node) => node.type === "text";
609
+ /** `start` から続く text ノードの並び。 */
610
+ const textRunFrom = (nodes, start) => {
611
+ const end = nodes.findIndex((node, index) => index > start && !isText(node));
612
+ return nodes.slice(start, end === -1 ? nodes.length : end).filter(isText);
613
+ };
614
+ /**
615
+ * 隣り合う text ノードを 1 つにまとめる。行を読んだときと同じ形にするため。
616
+ * text の並びの先頭で並び全体をまとめ、続きの text は先頭に含まれているので出さない。
617
+ */
618
+ const mergeTexts = (nodes) => nodes.flatMap((node, index) => !isText(node) ? [node] : (0, effect.pipe)(effect.Option.fromNullable(nodes[index - 1]), effect.Option.exists(isText)) ? [] : [textRunFrom(nodes, index).reduce(joinTexts)]);
619
+ /**
620
+ * 行と同じ規則で読んだセルのノード列から、`keep` が残さないノードを書いたままの文字に戻す。
621
+ * `source` はセルの中身、`origin` はその先頭のソース上の位置。
622
+ *
623
+ * 装飾を残さないときは記号の部分だけを文字に戻し、中身は同じように辿る。
624
+ * `[* [リンク]]` の中のリンクは残す。装飾を残すときも、その中身は同じ規則で辿る。
625
+ */
626
+ const keepNotation = (nodes, source, origin, keep) => {
627
+ /** ソース上の `[start, end)` を、書いたままの文字のノードにする。 */
628
+ const literal = (start, end) => start >= end ? [] : [{
629
+ type: "text",
630
+ value: source.slice(start - origin.offset, end - origin.offset),
631
+ position: require_decoration.spanAt(origin, start - origin.offset, end - origin.offset)
632
+ }];
633
+ const inside = (children) => mergeTexts(children.flatMap(demote));
634
+ /** 装飾の記号だけを文字に戻し、中身は辿る。中身が空なら丸ごと文字にする。 */
635
+ const unwrap = (node) => (0, effect.pipe)(effect.Option.all([effect.Option.fromNullable(node.children[0]), effect.Option.fromNullable(node.children[node.children.length - 1])]), effect.Option.match({
636
+ onNone: () => literal(node.position.start.offset, node.position.end.offset),
637
+ onSome: ([first, last]) => [
638
+ ...literal(node.position.start.offset, first.position.start.offset),
639
+ ...node.children.flatMap(demote),
640
+ ...literal(last.position.end.offset, node.position.end.offset)
641
+ ]
642
+ }));
643
+ const demote = (node) => effect.Match.value(node).pipe(effect.Match.when({ type: "text" }, (text) => [text]), effect.Match.when({ type: "decoration" }, (decoration) => keep(decoration) ? [{
644
+ ...decoration,
645
+ children: inside(decoration.children)
646
+ }] : unwrap(decoration)), effect.Match.when(keep, (kept) => [kept]), effect.Match.orElse((other) => literal(other.position.start.offset, other.position.end.offset)));
647
+ return inside(nodes);
648
+ };
649
+ //#endregion
630
650
  //#region src/parse.ts
631
651
  /**
632
652
  * parse.ts — ページ全文の入口。
@@ -661,10 +681,14 @@ const parse = (source, options) => {
661
681
  const normalized = normalizeLineEndings(source);
662
682
  const lines = toSourceLines(normalized);
663
683
  const rules = resolveExtensions(options?.extensions);
684
+ const keepInTableCell = keepInTableCellOf(options?.extensions);
664
685
  const last = lines[lines.length - 1];
665
686
  return {
666
687
  type: "page",
667
- children: buildBlocks(lines, (text, origin) => tokenizeInlineWith(text, origin, rules)),
688
+ children: buildBlocks(lines, {
689
+ line: (text, origin) => tokenizeInlineWith(text, origin, rules),
690
+ tableCell: (text, origin) => keepNotation(tokenizeInlineWith(text, origin, rules), text, origin, keepInTableCell)
691
+ }),
668
692
  position: {
669
693
  start: {
670
694
  line: 0,