securemark 0.259.2 → 0.260.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 (69) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/design.md +14 -4
  3. package/dist/index.js +253 -207
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/block.ts +2 -3
  6. package/src/combinator/control/constraint/contract.ts +5 -7
  7. package/src/combinator/control/constraint/line.ts +1 -2
  8. package/src/combinator/control/manipulation/convert.ts +1 -2
  9. package/src/combinator/control/manipulation/fence.ts +1 -2
  10. package/src/combinator/control/manipulation/match.ts +2 -3
  11. package/src/combinator/control/manipulation/scope.ts +3 -5
  12. package/src/combinator/control/manipulation/surround.ts +2 -2
  13. package/src/combinator/control/monad/bind.ts +2 -3
  14. package/src/combinator/data/parser/context.ts +20 -22
  15. package/src/combinator/data/parser/inits.ts +1 -2
  16. package/src/combinator/data/parser/sequence.ts +1 -2
  17. package/src/combinator/data/parser/some.ts +1 -2
  18. package/src/parser/api/parse.test.ts +19 -19
  19. package/src/parser/autolink.test.ts +6 -6
  20. package/src/parser/block/blockquote.test.ts +8 -8
  21. package/src/parser/block/blockquote.ts +3 -3
  22. package/src/parser/block/codeblock.test.ts +4 -4
  23. package/src/parser/block/dlist.test.ts +1 -1
  24. package/src/parser/block/dlist.ts +2 -2
  25. package/src/parser/block/extension/figure.test.ts +1 -1
  26. package/src/parser/block/extension/table.ts +3 -3
  27. package/src/parser/block/heading.test.ts +5 -5
  28. package/src/parser/block/ilist.ts +1 -1
  29. package/src/parser/block/olist.test.ts +1 -1
  30. package/src/parser/block/olist.ts +1 -1
  31. package/src/parser/block/paragraph.test.ts +14 -14
  32. package/src/parser/block/reply/cite.test.ts +11 -11
  33. package/src/parser/block/reply/cite.ts +1 -1
  34. package/src/parser/block/reply/quote.test.ts +3 -3
  35. package/src/parser/block/reply/quote.ts +1 -1
  36. package/src/parser/block/reply.test.ts +8 -8
  37. package/src/parser/block/sidefence.test.ts +6 -6
  38. package/src/parser/block/sidefence.ts +1 -1
  39. package/src/parser/block/table.ts +4 -4
  40. package/src/parser/block/ulist.test.ts +1 -1
  41. package/src/parser/block/ulist.ts +2 -2
  42. package/src/parser/block.ts +1 -1
  43. package/src/parser/context.ts +8 -7
  44. package/src/parser/inline/annotation.test.ts +3 -3
  45. package/src/parser/inline/autolink/account.test.ts +11 -11
  46. package/src/parser/inline/autolink/account.ts +4 -2
  47. package/src/parser/inline/autolink/anchor.test.ts +9 -9
  48. package/src/parser/inline/autolink/anchor.ts +14 -11
  49. package/src/parser/inline/autolink/channel.test.ts +3 -3
  50. package/src/parser/inline/autolink/hashnum.ts +4 -2
  51. package/src/parser/inline/autolink/hashtag.test.ts +20 -20
  52. package/src/parser/inline/autolink/hashtag.ts +4 -2
  53. package/src/parser/inline/autolink/url.test.ts +55 -55
  54. package/src/parser/inline/html.test.ts +0 -2
  55. package/src/parser/inline/html.ts +1 -1
  56. package/src/parser/inline/link.test.ts +110 -110
  57. package/src/parser/inline/link.ts +30 -28
  58. package/src/parser/inline/media.test.ts +1 -0
  59. package/src/parser/inline/media.ts +1 -1
  60. package/src/parser/inline/reference.test.ts +3 -3
  61. package/src/parser/inline/ruby.ts +1 -1
  62. package/src/parser/inline.test.ts +51 -51
  63. package/src/parser/source/escapable.ts +1 -1
  64. package/src/parser/source/str.ts +4 -4
  65. package/src/parser/source/text.ts +2 -3
  66. package/src/parser/source/unescapable.ts +1 -1
  67. package/src/renderer/render/media/pdf.ts +1 -0
  68. package/src/renderer/render/media/twitter.ts +7 -1
  69. package/src/util/info.ts +2 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.259.2",
3
+ "version": "0.260.2",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -6,11 +6,10 @@ import { firstline, isEmpty } from './line';
6
6
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
7
7
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
8
8
  assert(parser);
9
- return input => {
10
- const { source, context } = input;
9
+ return ({ source, context }) => {
11
10
  if (source === '') return;
12
11
  context.memo ??= new Memo();
13
- const result = parser(input);
12
+ const result = parser({ source, context });
14
13
  if (!result) return;
15
14
  const rest = exec(result);
16
15
  if (separation && !isEmpty(firstline(rest))) return;
@@ -24,11 +24,10 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
24
24
  ? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
25
25
  : `|| /${pattern.source}/${pattern.flags}.test(source)`),
26
26
  ].join(''))();
27
- return input => {
28
- const { source } = input;
27
+ return ({ source, context }) => {
29
28
  if (source === '') return;
30
29
  if (!match(source)) return;
31
- const result = parser!(input);
30
+ const result = parser!({ source, context });
32
31
  assert(check(source, result));
33
32
  if (!result) return;
34
33
  assert(exec(result).length < source.length);
@@ -41,13 +40,12 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
41
40
  export function verify<P extends Parser<unknown>>(parser: P, cond: (results: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
42
41
  export function verify<T>(parser: Parser<T>, cond: (results: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
43
42
  assert(parser);
44
- return input => {
45
- const { source } = input;
43
+ return ({ source, context }) => {
46
44
  if (source === '') return;
47
- const result = parser(input);
45
+ const result = parser({ source, context });
48
46
  assert(check(source, result));
49
47
  if (!result) return;
50
- if (!cond(eval(result), exec(result), input.context)) return;
48
+ if (!cond(eval(result), exec(result), context)) return;
51
49
  assert(exec(result).length < source.length);
52
50
  return exec(result).length < source.length
53
51
  ? result
@@ -5,8 +5,7 @@ import { Memo } from '../../data/parser/context/memo';
5
5
  export function line<P extends Parser<unknown>>(parser: P): P;
6
6
  export function line<T>(parser: Parser<T>): Parser<T> {
7
7
  assert(parser);
8
- return input => {
9
- const { source, context } = input;
8
+ return ({ source, context }) => {
10
9
  if (source === '') return;
11
10
  context.memo ??= new Memo();
12
11
  const line = firstline(source);
@@ -3,8 +3,7 @@ import { Parser, check } from '../../data/parser';
3
3
  export function convert<P extends Parser<unknown>>(conv: (source: string) => string, parser: P): P;
4
4
  export function convert<T>(conv: (source: string) => string, parser: Parser<T>): Parser<T> {
5
5
  assert(parser);
6
- return input => {
7
- const { source, context } = input;
6
+ return ({ source, context }) => {
8
7
  if (source === '') return;
9
8
  const src = conv(source);
10
9
  if (src === '') return [[], ''];
@@ -3,8 +3,7 @@ import { firstline, isEmpty } from '../constraint/line';
3
3
  import { unshift } from 'spica/array';
4
4
 
5
5
  export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
6
- return input => {
7
- const { source } = input;
6
+ return ({ source }) => {
8
7
  if (source === '') return;
9
8
  const matches = source.match(opener);
10
9
  if (!matches) return;
@@ -4,13 +4,12 @@ import { Parser, exec, check } from '../../data/parser';
4
4
  export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
5
  export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>): Parser<T> {
6
6
  assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
7
- return input => {
8
- const { source } = input;
7
+ return ({ source, context }) => {
9
8
  if (source === '') return;
10
9
  const param = source.match(pattern);
11
10
  if (!param) return;
12
11
  assert(source.startsWith(param[0]));
13
- const result = f(param)(input);
12
+ const result = f(param)({ source, context });
14
13
  assert(check(source, result, false));
15
14
  if (!result) return;
16
15
  return exec(result).length < source.length && exec(result).length <= source.length
@@ -8,8 +8,7 @@ export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
8
8
  const match: (source: string) => string = typeof scope === 'string'
9
9
  ? source => source.slice(0, scope.length) === scope ? scope : ''
10
10
  : source => source.match(scope)?.[0] ?? '';
11
- return input => {
12
- const { source, context } = input;
11
+ return ({ source, context }) => {
13
12
  if (source === '') return;
14
13
  const src = match(source);
15
14
  assert(source.startsWith(src));
@@ -32,12 +31,11 @@ export function rewrite<P extends Parser<unknown>>(scope: Parser<unknown, Contex
32
31
  export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T> {
33
32
  assert(scope);
34
33
  assert(parser);
35
- return input => {
36
- const { source, context } = input;
34
+ return ({ source, context }) => {
37
35
  if (source === '') return;
38
36
  const memo = context.memo;
39
37
  context.memo = undefined;
40
- const res1 = scope(input);
38
+ const res1 = scope({ source, context });
41
39
  assert(check(source, res1));
42
40
  context.memo = memo;
43
41
  if (!res1 || exec(res1).length >= source.length) return;
@@ -38,8 +38,8 @@ export function surround<T>(
38
38
  case 'object':
39
39
  return surround(opener, parser, match(closer), optional, f, g);
40
40
  }
41
- return input => {
42
- const { source: lmr_, context } = input;
41
+ return ({ source, context }) => {
42
+ const lmr_ = source;
43
43
  if (lmr_ === '') return;
44
44
  const res1 = opener({ source: lmr_, context });
45
45
  assert(check(lmr_, res1, false));
@@ -7,10 +7,9 @@ export function bind<T, P extends Parser<unknown>>(parser: Parser<T, Context<P>,
7
7
  export function bind<U, P extends Parser<unknown>>(parser: P, f: (nodes: Tree<P>[], rest: string, context: Context<P>) => Result<U, Context<P>, SubParsers<P>>): Parser<U, Context<P>, SubParsers<P>>;
8
8
  export function bind<T, U>(parser: Parser<T>, f: (nodes: T[], rest: string, context: Ctx) => Result<U>): Parser<U> {
9
9
  assert(parser);
10
- return input => {
11
- const { source, context } = input;
10
+ return ({ source, context }) => {
12
11
  if (source === '') return;
13
- const res1 = parser(input);
12
+ const res1 = parser({ source, context });
14
13
  assert(check(source, res1));
15
14
  if (!res1) return;
16
15
  const res2 = f(eval(res1), exec(res1), context);
@@ -58,8 +58,7 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
58
58
 
59
59
  export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, state: number, parser: P): P;
60
60
  export function syntax<T>(syntax: number, prec: number, cost: number, state: number, parser?: Parser<T>): Parser<T> {
61
- return creation(cost, precedence(prec, input => {
62
- const { source, context } = input;
61
+ return creation(cost, precedence(prec, ({ source, context }) => {
63
62
  if (source === '') return;
64
63
  const memo = context.memo ??= new Memo();
65
64
  context.memorable ??= ~0;
@@ -72,7 +71,7 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
72
71
  ? cache.length === 0
73
72
  ? undefined
74
73
  : [cache[0], source.slice(cache[1])]
75
- : parser!(input);
74
+ : parser!({ source, context });
76
75
  if (syntax && st0 & context.memorable!) {
77
76
  cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
78
77
  assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
@@ -88,16 +87,18 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
88
87
 
89
88
  export function creation<P extends Parser<unknown>>(parser: P): P;
90
89
  export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
91
- export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
92
- if (typeof cost === 'function') return creation(1, cost);
90
+ export function creation<P extends Parser<unknown>>(cost: number, recursion: boolean, parser: P): P;
91
+ export function creation(cost: number | Parser<unknown>, recursion?: boolean | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
92
+ if (typeof cost === 'function') return creation(1, true, cost);
93
+ if (typeof recursion === 'function') return creation(cost, true, recursion);
93
94
  assert(cost > 0);
94
- return input => {
95
- const resources = input.context.resources ?? { clock: 1, recursion: 1 };
95
+ return ({ source, context }) => {
96
+ const resources = context.resources ?? { clock: 1, recursion: 1 };
96
97
  if (resources.clock <= 0) throw new Error('Too many creations');
97
98
  if (resources.recursion <= 0) throw new Error('Too much recursion');
98
- --resources.recursion;
99
- const result = parser!(input);
100
- ++resources.recursion;
99
+ recursion && --resources.recursion;
100
+ const result = parser!({ source, context });
101
+ recursion && ++resources.recursion;
101
102
  if (result) {
102
103
  resources.clock -= cost;
103
104
  }
@@ -108,11 +109,10 @@ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown
108
109
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
109
110
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
110
111
  assert(precedence > 0);
111
- return input => {
112
- const { context } = input;
112
+ return ({ source, context }) => {
113
113
  const p = context.precedence;
114
114
  context.precedence = precedence;
115
- const result = parser(input);
115
+ const result = parser({ source, context });
116
116
  context.precedence = p;
117
117
  return result;
118
118
  };
@@ -120,9 +120,9 @@ export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T>
120
120
 
121
121
  export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
122
122
  export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
123
- return input =>
124
- f(input.context)
125
- ? parser(input)
123
+ return ({ source, context }) =>
124
+ f(context)
125
+ ? parser({ source, context })
126
126
  : undefined;
127
127
  }
128
128
 
@@ -134,13 +134,12 @@ export function constraint<T>(state: number, positive: boolean | Parser<T>, pars
134
134
  positive = true;
135
135
  }
136
136
  assert(state);
137
- return input => {
138
- const { context } = input;
137
+ return ({ source, context }) => {
139
138
  const s = positive
140
139
  ? state & context.state!
141
140
  : state & ~context.state!;
142
141
  return s === state
143
- ? parser!(input)
142
+ ? parser!({ source, context })
144
143
  : undefined;
145
144
  };
146
145
  }
@@ -153,13 +152,12 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
153
152
  positive = true;
154
153
  }
155
154
  assert(state);
156
- return input => {
157
- const { context } = input;
155
+ return ({ source, context }) => {
158
156
  const s = context.state ?? 0;
159
157
  context.state = positive
160
158
  ? s | state
161
159
  : s & ~state;
162
- const result = parser!(input);
160
+ const result = parser!({ source, context });
163
161
  context.state = s;
164
162
  return result;
165
163
  };
@@ -6,8 +6,7 @@ export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?
6
6
  export function inits<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  if (parsers.length === 1) return parsers[0];
9
- return input => {
10
- const { source, context } = input;
9
+ return ({ source, context }) => {
11
10
  let rest = source;
12
11
  let nodes: T[] | undefined;
13
12
  for (let i = 0, len = parsers.length; i < len; ++i) {
@@ -6,8 +6,7 @@ export function sequence<P extends Parser<unknown>>(parsers: SubParsers<P>, resu
6
6
  export function sequence<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  if (parsers.length === 1) return parsers[0];
9
- return input => {
10
- const { source, context } = input;
9
+ return ({ source, context }) => {
11
10
  let rest = source;
12
11
  let nodes: T[] | undefined;
13
12
  for (let i = 0, len = parsers.length; i < len; ++i) {
@@ -16,8 +16,7 @@ export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delim
16
16
  matcher: Delimiters.matcher(delimiter),
17
17
  precedence,
18
18
  }));
19
- return input => {
20
- const { source, context } = input;
19
+ return ({ source, context }) => {
21
20
  if (source === '') return;
22
21
  let rest = source;
23
22
  let nodes: T[] | undefined;
@@ -117,23 +117,23 @@ describe('Unit: parser/api/parse', () => {
117
117
  ].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
118
118
  [
119
119
  '<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span>\n</span></details></aside>',
120
- '<p><a href="https://source/@a" target="_blank" class="account">@a</a></p>',
121
- '<p><a href="https://domain/@a" target="_blank" class="account">@domain/a</a></p>',
122
- '<p><a href="https://source/@a?ch=b" target="_blank" class="channel">@a#b</a></p>',
123
- '<p><a href="https://domain/@a?ch=b" target="_blank" class="channel">@domain/a#b</a></p>',
124
- '<p><a href="https://source/hashtags/a" target="_blank" class="hashtag">#a</a></p>',
125
- '<p><a href="https://domain/hashtags/a" target="_blank" class="hashtag">#domain/a</a></p>',
120
+ '<p><a class="account" href="https://source/@a" target="_blank">@a</a></p>',
121
+ '<p><a class="account" href="https://domain/@a" target="_blank">@domain/a</a></p>',
122
+ '<p><a class="channel" href="https://source/@a?ch=b" target="_blank">@a#b</a></p>',
123
+ '<p><a class="channel" href="https://domain/@a?ch=b" target="_blank">@domain/a#b</a></p>',
124
+ '<p><a class="hashtag" href="https://source/hashtags/a" target="_blank">#a</a></p>',
125
+ '<p><a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a></p>',
126
126
  '<p><a class="index" href="#index:a">a</a></p>',
127
127
  '<figure data-type="math" data-label="$-a" data-group="$" data-number="1" id="label:$-a"><figcaption><span class="figindex">(1)</span><span class="figtext"></span></figcaption><div><div class="math" translate="no">$$\n$$</div></div></figure>',
128
128
  '<p><a class="label" data-label="$-a" href="#label:$-a">(1)</a></p>',
129
129
  '<p><sup class="annotation" id="annotation:ref:1" title="a"><span hidden="">a</span><a href="#annotation:def:1">*1</a></sup></p>',
130
- '<p><a href="https://source/x/a" target="_blank">a</a></p>',
131
- '<p><a href="https://source/a" target="_blank">/a</a></p>',
132
- '<p><a href="/z/a">^/a</a></p>',
133
- '<p><a href="https://source/x/a" target="_blank">./a</a></p>',
134
- '<p><a href="https://source/a" target="_blank">../a</a></p>',
135
- '<p><a href="https://source/a" target="_blank">../../a</a></p>',
136
- '<p><a href="//domain/a" target="_blank">//domain/a</a></p>',
130
+ '<p><a class="url" href="https://source/x/a" target="_blank">a</a></p>',
131
+ '<p><a class="url" href="https://source/a" target="_blank">/a</a></p>',
132
+ '<p><a class="url" href="/z/a">^/a</a></p>',
133
+ '<p><a class="url" href="https://source/x/a" target="_blank">./a</a></p>',
134
+ '<p><a class="url" href="https://source/a" target="_blank">../a</a></p>',
135
+ '<p><a class="url" href="https://source/a" target="_blank">../../a</a></p>',
136
+ '<p><a class="url" href="//domain/a" target="_blank">//domain/a</a></p>',
137
137
  '<p><a href="https://source/x/a" target="_blank"><img class="media" data-src="https://source/x/a" alt=""></a></p>',
138
138
  '<p><a href="/z/a" target="_blank"><img class="media" data-src="/z/a" alt=""></a></p>',
139
139
  '<p><a href="https://source/a" target="_blank"><img class="media" data-src="https://source/a" alt=""></a></p>',
@@ -151,8 +151,8 @@ describe('Unit: parser/api/parse', () => {
151
151
  ].join('\n\n'), { host: new URL(`${location.origin}/index.md`) }).children].map(el => el.outerHTML),
152
152
  [
153
153
  '<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span>\n</span></details></aside>',
154
- '<p><a href="/a">^/a</a></p>',
155
- '<p><a href="https://source/x/a" target="_blank">./a</a></p>',
154
+ '<p><a class="url" href="/a">^/a</a></p>',
155
+ '<p><a class="url" href="https://source/x/a" target="_blank">./a</a></p>',
156
156
  ]);
157
157
  assert.deepStrictEqual(
158
158
  [...parse([
@@ -166,8 +166,8 @@ describe('Unit: parser/api/parse', () => {
166
166
  ].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
167
167
  [
168
168
  `<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="${location.origin}/x/y"><span class="field-name">URL</span>: <span class="field-value">${location.origin}/x/y</span>\n</span></details></aside>`,
169
- '<p><a href="/z/a">^/a</a></p>',
170
- '<p><a href="/x/a">./a</a></p>',
169
+ '<p><a class="url" href="/z/a">^/a</a></p>',
170
+ '<p><a class="url" href="/x/a">./a</a></p>',
171
171
  ]);
172
172
  });
173
173
 
@@ -198,8 +198,8 @@ describe('Unit: parser/api/parse', () => {
198
198
  [
199
199
  `<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://example/x"><span class="field-name">URL</span>: <span class="field-value">https://example/x</span>\n</span></details></aside>`,
200
200
  '<pre class="invalid" translate="no" data-invalid-syntax="header" data-invalid-type="syntax" data-invalid-message="Invalid syntax">---\nURL: https://example/y\n---\n</pre>',
201
- '<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://example/y"><span class="field-name">URL</span>: <span class="field-value">https://example/y</span>\n</span></details></aside><p><a href="https://example/y#" target="_blank">#</a></p><ol class="references"></ol></section></aside>',
202
- '<p><a href="https://example/x#" target="_blank">#</a></p>',
201
+ '<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://example/y"><span class="field-name">URL</span>: <span class="field-value">https://example/y</span>\n</span></details></aside><p><a class="url" href="https://example/y#" target="_blank">#</a></p><ol class="references"></ol></section></aside>',
202
+ '<p><a class="url" href="https://example/x#" target="_blank">#</a></p>',
203
203
  ]);
204
204
  });
205
205
 
@@ -7,10 +7,10 @@ describe('Unit: parser/autolink', () => {
7
7
  const parser = (source: string) => some(autolink)({ source, context: {} });
8
8
 
9
9
  it('basic', () => {
10
- assert.deepStrictEqual(inspect(parser(' http://host')), [[' ', '<a href="http://host" target="_blank">http://host</a>'], '']);
11
- assert.deepStrictEqual(inspect(parser('!http://host')), [['!', '<a href="http://host" target="_blank">http://host</a>'], '']);
12
- assert.deepStrictEqual(inspect(parser('#a')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '']);
13
- assert.deepStrictEqual(inspect(parser('@a#b')), [['<a href="/@a?ch=b" class="channel">@a#b</a>'], '']);
10
+ assert.deepStrictEqual(inspect(parser(' http://host')), [[' ', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
11
+ assert.deepStrictEqual(inspect(parser('!http://host')), [['!', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
12
+ assert.deepStrictEqual(inspect(parser('#a')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '']);
13
+ assert.deepStrictEqual(inspect(parser('@a#b')), [['<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
14
14
  assert.deepStrictEqual(inspect(parser('\\\n')), [['\\', '<br>'], '']);
15
15
  assert.deepStrictEqual(inspect(parser('a#b')), [['a#b'], '']);
16
16
  assert.deepStrictEqual(inspect(parser('0a#b')), [['0a#b'], '']);
@@ -18,8 +18,8 @@ describe('Unit: parser/autolink', () => {
18
18
  assert.deepStrictEqual(inspect(parser('あい#b')), [['あ', 'い#b'], '']);
19
19
  assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0aあ#b'], '']);
20
20
  assert.deepStrictEqual(inspect(parser('0aあい#b')), [['0a', 'あ', 'い#b'], '']);
21
- assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
22
- assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '\\', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
21
+ assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a class="hashtag" href="/hashtags/b">#b</a>'], '']);
22
+ assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '\\', '<br>', '<a class="hashtag" href="/hashtags/b">#b</a>'], '']);
23
23
  assert.deepStrictEqual(inspect(parser('0a>>b')), [['0a>>b'], '']);
24
24
  });
25
25
 
@@ -25,13 +25,13 @@ describe('Unit: parser/block/blockquote', () => {
25
25
  assert.deepStrictEqual(inspect(parser('> a\nb')), [['<blockquote><pre>a<br>b</pre></blockquote>'], '']);
26
26
  assert.deepStrictEqual(inspect(parser('> a\n b ')), [['<blockquote><pre>a<br> b </pre></blockquote>'], '']);
27
27
  assert.deepStrictEqual(inspect(parser('> a\n>')), [['<blockquote><pre>a<br></pre></blockquote>'], '']);
28
- assert.deepStrictEqual(inspect(parser('> a\n>>1')), [['<blockquote><pre>a<br><a href="?at=1" class="anchor">&gt;&gt;1</a></pre></blockquote>'], '']);
28
+ assert.deepStrictEqual(inspect(parser('> a\n>>1')), [['<blockquote><pre>a<br><a class="anchor" href="?at=1">&gt;&gt;1</a></pre></blockquote>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser('> a\n> b ')), [['<blockquote><pre>a<br>b </pre></blockquote>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('> a\n>\n')), [['<blockquote><pre>a<br></pre></blockquote>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('> a\n>\nb')), [['<blockquote><pre>a<br><br>b</pre></blockquote>'], '']);
32
32
  assert.deepStrictEqual(inspect(parser('> a\n>\n b ')), [['<blockquote><pre>a<br><br> b </pre></blockquote>'], '']);
33
33
  assert.deepStrictEqual(inspect(parser('> a\n>\n>')), [['<blockquote><pre>a<br><br></pre></blockquote>'], '']);
34
- assert.deepStrictEqual(inspect(parser('> a\n>\n>>1')), [['<blockquote><pre>a<br><br><a href="?at=1" class="anchor">&gt;&gt;1</a></pre></blockquote>'], '']);
34
+ assert.deepStrictEqual(inspect(parser('> a\n>\n>>1')), [['<blockquote><pre>a<br><br><a class="anchor" href="?at=1">&gt;&gt;1</a></pre></blockquote>'], '']);
35
35
  assert.deepStrictEqual(inspect(parser('> a\n>\n> b ')), [['<blockquote><pre>a<br><br>b </pre></blockquote>'], '']);
36
36
  assert.deepStrictEqual(inspect(parser('> a\\\nb')), [['<blockquote><pre>a\\<br>b</pre></blockquote>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser('> a ')), [['<blockquote><pre> a </pre></blockquote>'], '']);
@@ -40,12 +40,12 @@ describe('Unit: parser/block/blockquote', () => {
40
40
  assert.deepStrictEqual(inspect(parser('>\n a')), [['<blockquote><pre><br> a</pre></blockquote>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('>\n>')), [['<blockquote><pre><br></pre></blockquote>'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('>\n> a')), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
43
- assert.deepStrictEqual(inspect(parser('> http://host')), [['<blockquote><pre><a href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
44
- assert.deepStrictEqual(inspect(parser('> !http://host')), [['<blockquote><pre>!<a href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
45
- assert.deepStrictEqual(inspect(parser('> #a')), [['<blockquote><pre><a href="/hashtags/a" class="hashtag">#a</a></pre></blockquote>'], '']);
46
- assert.deepStrictEqual(inspect(parser('> @a#b')), [['<blockquote><pre><a href="/@a?ch=b" class="channel">@a#b</a></pre></blockquote>'], '']);
47
- assert.deepStrictEqual(inspect(parser('> >>1\n> > b')), [['<blockquote><pre><a href="?at=1" class="anchor">&gt;&gt;1</a><br>&gt; b</pre></blockquote>'], '']);
48
- assert.deepStrictEqual(inspect(parser('> >>1\n> > b\n> c')), [['<blockquote><pre><a href="?at=1" class="anchor">&gt;&gt;1</a><br>&gt; b<br>c</pre></blockquote>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('> http://host')), [['<blockquote><pre><a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('> !http://host')), [['<blockquote><pre>!<a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('> #a')), [['<blockquote><pre><a class="hashtag" href="/hashtags/a">#a</a></pre></blockquote>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('> @a#b')), [['<blockquote><pre><a class="channel" href="/@a?ch=b">@a#b</a></pre></blockquote>'], '']);
47
+ assert.deepStrictEqual(inspect(parser('> >>1\n> > b')), [['<blockquote><pre><a class="anchor" href="?at=1">&gt;&gt;1</a><br>&gt; b</pre></blockquote>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('> >>1\n> > b\n> c')), [['<blockquote><pre><a class="anchor" href="?at=1">&gt;&gt;1</a><br>&gt; b<br>c</pre></blockquote>'], '']);
49
49
  });
50
50
 
51
51
  it('nest', () => {
@@ -19,7 +19,7 @@ const indent = block(open(opener, some(contentline, /^>(?:$|\s)/)), false);
19
19
  const unindent = (source: string) => source.replace(/(^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '$1');
20
20
 
21
21
  const source: BlockquoteParser.SourceParser = lazy(() => fmap(
22
- some(creation(union([
22
+ some(creation(1, false, union([
23
23
  rewrite(
24
24
  indent,
25
25
  convert(unindent, source)),
@@ -30,11 +30,11 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
30
30
  ns => [html('blockquote', ns)]));
31
31
 
32
32
  const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
33
- some(creation(union([
33
+ some(creation(1, false, union([
34
34
  rewrite(
35
35
  indent,
36
36
  convert(unindent, markdown)),
37
- creation(99,
37
+ creation(99, false,
38
38
  rewrite(
39
39
  some(contentline, opener),
40
40
  convert(unindent, ({ source, context }) => {
@@ -37,10 +37,10 @@ describe('Unit: parser/block/codeblock', () => {
37
37
  assert.deepStrictEqual(inspect(parser('````\n```\n````')), [['<pre class="text">```</pre>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser('````\n```\n\n````')), [['<pre class="text">```<br></pre>'], '']);
39
39
  assert.deepStrictEqual(inspect(parser('```\n\n```\n')), [['<pre class="text"></pre>'], '']);
40
- assert.deepStrictEqual(inspect(parser('```\nhttp://host\n```')), [['<pre class="text"><a href="http://host" target="_blank">http://host</a></pre>'], '']);
41
- assert.deepStrictEqual(inspect(parser('```\n!http://host\n```')), [['<pre class="text">!<a href="http://host" target="_blank">http://host</a></pre>'], '']);
42
- assert.deepStrictEqual(inspect(parser('```\n#a\n```')), [['<pre class="text"><a href="/hashtags/a" class="hashtag">#a</a></pre>'], '']);
43
- assert.deepStrictEqual(inspect(parser('```\n@a#b\n```')), [['<pre class="text"><a href="/@a?ch=b" class="channel">@a#b</a></pre>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('```\nhttp://host\n```')), [['<pre class="text"><a class="url" href="http://host" target="_blank">http://host</a></pre>'], '']);
41
+ assert.deepStrictEqual(inspect(parser('```\n!http://host\n```')), [['<pre class="text">!<a class="url" href="http://host" target="_blank">http://host</a></pre>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('```\n#a\n```')), [['<pre class="text"><a class="hashtag" href="/hashtags/a">#a</a></pre>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('```\n@a#b\n```')), [['<pre class="text"><a class="channel" href="/@a?ch=b">@a#b</a></pre>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser(`\`\`\`\n0${'\n'.repeat(300)}\`\`\``), '>'), [['<pre class="text">'], '']);
45
45
  });
46
46
 
@@ -66,7 +66,7 @@ describe('Unit: parser/block/dlist', () => {
66
66
 
67
67
  it('index', () => {
68
68
  assert.deepStrictEqual(inspect(parser('~ a [#b]')), [['<dl><dt id="index:b">a<span class="indexer" data-index="b"></span></dt><dd></dd></dl>'], '']);
69
- assert.deepStrictEqual(inspect(parser('~ a [#b]\\')), [['<dl><dt id="index:a_[#b]">a [<a href="/hashtags/b" class="hashtag">#b</a>]</dt><dd></dd></dl>'], '']);
69
+ assert.deepStrictEqual(inspect(parser('~ a [#b]\\')), [['<dl><dt id="index:a_[#b]">a [<a class="hashtag" href="/hashtags/b">#b</a>]</dt><dd></dd></dl>'], '']);
70
70
  assert.deepStrictEqual(inspect(parser('~ A')), [['<dl><dt id="index:A">A</dt><dd></dd></dl>'], '']);
71
71
  assert.deepStrictEqual(inspect(parser('~ *A*')), [['<dl><dt id="index:A"><em>A</em></dt><dd></dd></dl>'], '']);
72
72
  assert.deepStrictEqual(inspect(parser('~ `A`')), [['<dl><dt id="index:`A`"><code data-src="`A`">A</code></dt><dd></dd></dl>'], '']);
@@ -17,13 +17,13 @@ export const dlist: DListParser = lazy(() => block(localize(fmap(validate(
17
17
  ]))),
18
18
  es => [html('dl', fillTrailingDescription(es))]))));
19
19
 
20
- const term: DListParser.TermParser = creation(line(indexee(fmap(open(
20
+ const term: DListParser.TermParser = creation(1, false, line(indexee(fmap(open(
21
21
  /^~[^\S\n]+(?=\S)/,
22
22
  visualize(trimBlank(some(union([indexer, inline])))),
23
23
  true),
24
24
  ns => [html('dt', defrag(ns))]))));
25
25
 
26
- const desc: DListParser.DescriptionParser = creation(block(fmap(open(
26
+ const desc: DListParser.DescriptionParser = creation(1, false, block(fmap(open(
27
27
  /^:[^\S\n]+(?=\S)|/,
28
28
  rewrite(
29
29
  some(anyline, /^[~:][^\S\n]+\S/),
@@ -43,7 +43,7 @@ describe('Unit: parser/block/extension/figure', () => {
43
43
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n!https://host\n\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n!https://host\n\n\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n!https://host\n\n\\\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext">\\</span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
46
- assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n!https://host\n\n!https://caption\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext">!<a href="https://caption" target="_blank">https://caption</a></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n!https://host\n\n!https://caption\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext">!<a class="url" href="https://caption" target="_blank">https://caption</a></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n- a\n~~~')), [['<figure data-type="list" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><ul><li>a</li></ul></div></figure>'], '']);
48
48
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n1. a\n~~~')), [['<figure data-type="list" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><ol><li>a</li></ol></div></figure>'], '']);
49
49
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]\n|\n|-\n|\n~~~')), [['<figure data-type="table" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><table><thead><tr></tr></thead><tbody><tr></tr></tbody></table></div></figure>'], '']);
@@ -79,7 +79,7 @@ const align: AlignParser = line(fmap(
79
79
 
80
80
  const delimiter = /^[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|^[#:](?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/;
81
81
 
82
- const head: CellParser.HeadParser = creation(block(fmap(open(
82
+ const head: CellParser.HeadParser = creation(1, false, block(fmap(open(
83
83
  str(/^#(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
84
84
  rewrite(
85
85
  inits([
@@ -91,7 +91,7 @@ const head: CellParser.HeadParser = creation(block(fmap(open(
91
91
  ns => [html('th', attributes(ns.shift()! as string), defrag(ns))]),
92
92
  false));
93
93
 
94
- const data: CellParser.DataParser = creation(block(fmap(open(
94
+ const data: CellParser.DataParser = creation(1, false, block(fmap(open(
95
95
  str(/^:(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
96
96
  rewrite(
97
97
  inits([
@@ -103,7 +103,7 @@ const data: CellParser.DataParser = creation(block(fmap(open(
103
103
  ns => [html('td', attributes(ns.shift()! as string), defrag(ns))]),
104
104
  false));
105
105
 
106
- const dataline: CellParser.DatalineParser = creation(line(
106
+ const dataline: CellParser.DatalineParser = creation(1, false, line(
107
107
  rewrite(
108
108
  contentline,
109
109
  union([
@@ -43,11 +43,11 @@ describe('Unit: parser/block/heading', () => {
43
43
  assert.deepStrictEqual(inspect(parser('# \\ a')), [['<h1 id="index:a">a</h1>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('## \\')), [['<h2 id="index:\\">\\</h2>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser('# @a')), [['<h1 id="index:@a">@a</h1>'], '']);
46
- assert.deepStrictEqual(inspect(parser('## @a')), [['<h2 id="index:@a"><a href="/@a" class="account">@a</a></h2>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('## @a')), [['<h2 id="index:@a"><a class="account" href="/@a">@a</a></h2>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser('# http://host')), [['<h1 id="index:http://host">http://host</h1>'], '']);
48
- assert.deepStrictEqual(inspect(parser('## http://host')), [['<h2 id="index:http://host"><a href="http://host" target="_blank">http://host</a></h2>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('## http://host')), [['<h2 id="index:http://host"><a class="url" href="http://host" target="_blank">http://host</a></h2>'], '']);
49
49
  assert.deepStrictEqual(inspect(parser('# !http://host')), [['<h1 id="index:!http://host">!http://host</h1>'], '']);
50
- assert.deepStrictEqual(inspect(parser('## !http://host')), [['<h2 id="index:!http://host">!<a href="http://host" target="_blank">http://host</a></h2>'], '']);
50
+ assert.deepStrictEqual(inspect(parser('## !http://host')), [['<h2 id="index:!http://host">!<a class="url" href="http://host" target="_blank">http://host</a></h2>'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('# a((b))')), [['<h1 id="index:a((b))">a<span class="paren">((b))</span></h1>'], '']);
52
52
  assert.deepStrictEqual(inspect(parser('## a((b))')), [['<h2 id="index:a((b))">a<span class="paren">((b))</span></h2>'], '']);
53
53
  assert.deepStrictEqual(inspect(parser('# a[[b]]')), [['<h1 id="index:a[[b]]">a[[b]]</h1>'], '']);
@@ -79,8 +79,8 @@ describe('Unit: parser/block/heading', () => {
79
79
  assert.deepStrictEqual(inspect(parser('# a [#b] [#c]')), [['<h1 id="index:c">a [#b]<span class="indexer" data-index="c"></span></h1>'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('# a [#b] \n')), [['<h1 id="index:b">a<span class="indexer" data-index="b"></span></h1>'], '']);
81
81
  assert.deepStrictEqual(inspect(parser('# a \\[#b]')), [['<h1 id="index:a_[#b]">a [#b]</h1>'], '']);
82
- assert.deepStrictEqual(inspect(parser('## a [#b] [#c]')), [['<h2 id="index:c">a [<a href="/hashtags/b" class="hashtag">#b</a>]<span class="indexer" data-index="c"></span></h2>'], '']);
83
- assert.deepStrictEqual(inspect(parser('## a [#b ] [#c ]')), [['<h2 id="index:c">a [<a href="/hashtags/b" class="hashtag">#b</a> ]<span class="indexer" data-index="c"></span></h2>'], '']);
82
+ assert.deepStrictEqual(inspect(parser('## a [#b] [#c]')), [['<h2 id="index:c">a [<a class="hashtag" href="/hashtags/b">#b</a>]<span class="indexer" data-index="c"></span></h2>'], '']);
83
+ assert.deepStrictEqual(inspect(parser('## a [#b ] [#c ]')), [['<h2 id="index:c">a [<a class="hashtag" href="/hashtags/b">#b</a> ]<span class="indexer" data-index="c"></span></h2>'], '']);
84
84
  });
85
85
 
86
86
  });
@@ -13,7 +13,7 @@ export const ilist: IListParser = lazy(() => block(validate(
13
13
 
14
14
  export const ilist_: IListParser = lazy(() => block(fmap(validate(
15
15
  /^[-+*](?:$|\s)/,
16
- some(creation(union([
16
+ some(creation(1, false, union([
17
17
  fmap(fallback(
18
18
  inits([
19
19
  line(open(/^[-+*](?:$|\s)/, some(inline), true)),