securemark 0.247.2 → 0.249.0

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 (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/design.md +7 -4
  3. package/dist/index.js +111 -246
  4. package/markdown.d.ts +10 -11
  5. package/package.json +6 -6
  6. package/src/combinator/control/manipulation/indent.ts +1 -2
  7. package/src/parser/inline/annotation.test.ts +2 -2
  8. package/src/parser/inline/annotation.ts +4 -5
  9. package/src/parser/inline/comment.ts +1 -1
  10. package/src/parser/inline/deletion.ts +3 -3
  11. package/src/parser/inline/emphasis.test.ts +5 -4
  12. package/src/parser/inline/emphasis.ts +8 -3
  13. package/src/parser/inline/emstrong.ts +14 -6
  14. package/src/parser/inline/extension/index.ts +1 -2
  15. package/src/parser/inline/extension/label.ts +1 -2
  16. package/src/parser/inline/html.test.ts +8 -5
  17. package/src/parser/inline/html.ts +17 -86
  18. package/src/parser/inline/insertion.ts +3 -3
  19. package/src/parser/inline/link.test.ts +2 -2
  20. package/src/parser/inline/link.ts +3 -4
  21. package/src/parser/inline/mark.test.ts +5 -4
  22. package/src/parser/inline/mark.ts +3 -3
  23. package/src/parser/inline/media.ts +3 -3
  24. package/src/parser/inline/reference.test.ts +5 -5
  25. package/src/parser/inline/reference.ts +8 -10
  26. package/src/parser/inline/ruby.ts +4 -4
  27. package/src/parser/inline/strong.test.ts +5 -4
  28. package/src/parser/inline/strong.ts +7 -3
  29. package/src/parser/inline.test.ts +1 -0
  30. package/src/parser/processor/figure.ts +18 -19
  31. package/src/parser/source/escapable.test.ts +4 -3
  32. package/src/parser/source/escapable.ts +3 -3
  33. package/src/parser/source/text.test.ts +4 -4
  34. package/src/parser/source/text.ts +3 -3
  35. package/src/parser/source/unescapable.test.ts +4 -3
  36. package/src/parser/source/unescapable.ts +3 -3
  37. package/src/parser/util.ts +26 -13
  38. package/src/renderer/render/media/image.ts +3 -3
  39. package/src/renderer/render/media/video.ts +2 -2
@@ -7,7 +7,7 @@ import { unsafehtmlentity } from './htmlentity';
7
7
  import { txt, str } from '../source';
8
8
  import { html, define } from 'typed-dom/dom';
9
9
  import { ReadonlyURL } from 'spica/url';
10
- import { unshift, push, join } from 'spica/array';
10
+ import { unshift, push } from 'spica/array';
11
11
 
12
12
  const optspec = {
13
13
  'width': [],
@@ -28,7 +28,7 @@ export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'],
28
28
  true)),
29
29
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
30
30
  ]))),
31
- ([as, bs]) => bs ? [[join(as).trim() || join(as)], bs] : [[''], as]),
31
+ ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]),
32
32
  ([[text]]) => text === '' || text.trim() !== ''),
33
33
  ([[text], params], rest, context) => {
34
34
  assert(text === text.trim());
@@ -52,7 +52,7 @@ export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'],
52
52
  return fmap(
53
53
  link as MediaParser,
54
54
  ([link]) => [define(link, { target: '_blank' }, [el])])
55
- (`{ ${INSECURE_URI}${join(params)} }${rest}`, context);
55
+ (`{ ${INSECURE_URI}${params.join('')} }${rest}`, context);
56
56
  }))));
57
57
 
58
58
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => union([
@@ -16,9 +16,7 @@ describe('Unit: parser/inline/reference', () => {
16
16
  assert.deepStrictEqual(inspect(parser('[[ ]]')), undefined);
17
17
  assert.deepStrictEqual(inspect(parser('[[\n]]')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('[[\na]]')), undefined);
19
- assert.deepStrictEqual(inspect(parser('[[\\ a]]')), undefined);
20
19
  assert.deepStrictEqual(inspect(parser('[[\\\na]]')), undefined);
21
- assert.deepStrictEqual(inspect(parser('[[<wbr>a]]')), undefined);
22
20
  assert.deepStrictEqual(inspect(parser('[[a\n]]')), undefined);
23
21
  assert.deepStrictEqual(inspect(parser('[[a\\\n]]')), undefined);
24
22
  assert.deepStrictEqual(inspect(parser('[[a\nb]]')), undefined);
@@ -33,6 +31,8 @@ describe('Unit: parser/inline/reference', () => {
33
31
  it('basic', () => {
34
32
  assert.deepStrictEqual(inspect(parser('[[ a]]')), [['<sup class="reference">a</sup>'], '']);
35
33
  assert.deepStrictEqual(inspect(parser('[[ a ]]')), [['<sup class="reference">a</sup>'], '']);
34
+ assert.deepStrictEqual(inspect(parser('[[\\ a]]')), [['<sup class="reference">a</sup>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('[[<wbr>a]]')), [['<sup class="reference">a</sup>'], '']);
36
36
  assert.deepStrictEqual(inspect(parser('[[a]]')), [['<sup class="reference">a</sup>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser('[[a ]]')), [['<sup class="reference">a</sup>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser('[[a ]]')), [['<sup class="reference">a</sup>'], '']);
@@ -68,8 +68,8 @@ describe('Unit: parser/inline/reference', () => {
68
68
  assert.deepStrictEqual(inspect(parser('[[^a|b]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
69
69
  assert.deepStrictEqual(inspect(parser('[[^a|b ]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
70
70
  assert.deepStrictEqual(inspect(parser('[[^a|b ]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
71
- assert.deepStrictEqual(inspect(parser('[[^a|<wbr>]]')), [['<sup class="invalid">^a|</sup>'], '']);
72
- assert.deepStrictEqual(inspect(parser('[[^a|<wbr>b]]')), [['<sup class="invalid">^a|<wbr>b</sup>'], '']);
71
+ assert.deepStrictEqual(inspect(parser('[[^a|<wbr>]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
72
+ assert.deepStrictEqual(inspect(parser('[[^a|<wbr>b]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('[[^a|^b]]')), [['<sup class="reference" data-abbr="a">^b</sup>'], '']);
74
74
  assert.deepStrictEqual(inspect(parser('[[^a| ]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
75
75
  assert.deepStrictEqual(inspect(parser('[[^a| b]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
@@ -87,7 +87,7 @@ describe('Unit: parser/inline/reference', () => {
87
87
  assert.deepStrictEqual(inspect(parser(`[[^A's, Aces']]`)), [[`<sup class="reference" data-abbr="A's, Aces'"></sup>`], '']);
88
88
  assert.deepStrictEqual(inspect(parser('[[^^]]')), [['<sup class="invalid">^^</sup>'], '']);
89
89
  assert.deepStrictEqual(inspect(parser('[[\\^]]')), [['<sup class="reference">^</sup>'], '']);
90
- assert.deepStrictEqual(inspect(parser('[[^ ]]')), [['<sup class="invalid">^</sup>'], '']);
90
+ assert.deepStrictEqual(inspect(parser('[[^ ]]')), [['<sup class="invalid">^ </sup>'], '']);
91
91
  assert.deepStrictEqual(inspect(parser('[[^ a]]')), [['<sup class="invalid">^ a</sup>'], '']);
92
92
  assert.deepStrictEqual(inspect(parser('[[^ |]]')), [['<sup class="invalid">^ |</sup>'], '']);
93
93
  assert.deepStrictEqual(inspect(parser('[[^ |b]]')), [['<sup class="invalid">^ |b</sup>'], '']);
@@ -1,15 +1,14 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { ReferenceParser } from '../inline';
3
- import { union, subsequence, some, validate, verify, focus, guard, context, creator, surround, lazy, fmap } from '../../combinator';
3
+ import { union, subsequence, some, validate, focus, guard, context, creator, surround, lazy, fmap, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { startLoose, isStartLoose, trimSpaceStart, trimNodeEnd, stringify } from '../util';
6
+ import { regBlankInlineStart, trimBlankInline, stringify } from '../util';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
 
9
9
  export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]', '\n', fmap(surround(
10
10
  '[[',
11
11
  guard(context => context.syntax?.inline?.reference ?? true,
12
- startLoose(
13
12
  context({ syntax: { inline: {
14
13
  annotation: false,
15
14
  reference: false,
@@ -19,21 +18,20 @@ export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]'
19
18
  //label: true,
20
19
  //link: true,
21
20
  //autolink: true,
22
- }}, state: undefined, delimiters: undefined },
21
+ }}, delimiters: undefined },
23
22
  subsequence([
24
23
  abbr,
25
24
  focus(/^\^[^\S\n]*/, source => [['', source], '']),
26
- trimSpaceStart(some(inline, ']', /^\\?\n/)),
27
- ])))),
25
+ trimBlankInline(some(inline, ']', /^\\?\n/)),
26
+ ]))),
28
27
  ']]'),
29
- ns => [html('sup', attributes(ns), trimNodeEnd(defrag(ns)))]))));
28
+ ns => [html('sup', attributes(ns), defrag(ns))]))));
30
29
 
31
- const abbr: ReferenceParser.AbbrParser = creator(fmap(verify(surround(
30
+ const abbr: ReferenceParser.AbbrParser = creator(bind(surround(
32
31
  '^',
33
32
  union([str(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]),
34
33
  /^\|?(?=]])|^\|[^\S\n]*/),
35
- (_, rest, context) => isStartLoose(rest, context)),
36
- ([source]) => [html('abbr', source)]));
34
+ ([source], rest) => [[html('abbr', source)], rest.replace(regBlankInlineStart, '')]));
37
35
 
38
36
  function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
39
37
  return typeof ns[0] === 'object' && ns[0].tagName === 'ABBR'
@@ -6,7 +6,7 @@ import { unsafehtmlentity } from './htmlentity';
6
6
  import { text as txt } from '../source';
7
7
  import { isStartTightNodes } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
- import { unshift, push, join } from 'spica/array';
9
+ import { unshift, push } from 'spica/array';
10
10
 
11
11
  export const ruby: RubyParser = lazy(() => creator(validate('[', ')', '\n', bind(verify(
12
12
  sequence([
@@ -39,8 +39,8 @@ export const ruby: RubyParser = lazy(() => creator(validate('[', ')', '\n', bind
39
39
  default:
40
40
  assert(rubies.length > 0);
41
41
  return [[html('ruby', attributes(texts, rubies), defrag(push(unshift(
42
- [join(texts, ' ')],
43
- [html('rp', '('), html('rt', join(rubies, ' ').trim()), html('rp', ')')]), tail)))
42
+ [texts.join(' ')],
43
+ [html('rp', '('), html('rt', rubies.join(' ').trim()), html('rp', ')')]), tail)))
44
44
  ], rest];
45
45
  }
46
46
  }))));
@@ -74,7 +74,7 @@ const text: RubyParser.TextParser = creator((source, context) => {
74
74
  }
75
75
  }
76
76
  }
77
- return join(acc).trimStart()
77
+ return acc.join('').trimStart()
78
78
  ? [[acc], '']
79
79
  : undefined;
80
80
  });
@@ -9,10 +9,11 @@ describe('Unit: parser/inline/strong', () => {
9
9
  it('invalid', () => {
10
10
  assert.deepStrictEqual(inspect(parser('**')), undefined);
11
11
  assert.deepStrictEqual(inspect(parser('**a')), [['**', 'a'], '']);
12
- assert.deepStrictEqual(inspect(parser('**a **')), [['**', 'a', ' ', '**'], '']);
13
- assert.deepStrictEqual(inspect(parser('**a\n**')), [['**', 'a', '<br>', '**'], '']);
14
- assert.deepStrictEqual(inspect(parser('**a\\ **')), [['**', 'a', ' ', '**'], '']);
15
- assert.deepStrictEqual(inspect(parser('**a\\\n**')), [['**', 'a', '<span class="linebreak"> </span>', '**'], '']);
12
+ assert.deepStrictEqual(inspect(parser('**a **')), [['**', 'a'], ' **']);
13
+ assert.deepStrictEqual(inspect(parser('**a **')), [['**', 'a', ' '], ' **']);
14
+ assert.deepStrictEqual(inspect(parser('**a\n**')), [['**', 'a'], '\n**']);
15
+ assert.deepStrictEqual(inspect(parser('**a\\ **')), [['**', 'a'], '\\ **']);
16
+ assert.deepStrictEqual(inspect(parser('**a\\\n**')), [['**', 'a'], '\\\n**']);
16
17
  assert.deepStrictEqual(inspect(parser('**a*')), [['**', 'a', '*'], '']);
17
18
  assert.deepStrictEqual(inspect(parser('**a*b**')), [['**', 'a', '<em>b</em>', '*'], '']);
18
19
  assert.deepStrictEqual(inspect(parser('** **')), undefined);
@@ -1,16 +1,20 @@
1
1
  import { StrongParser } from '../inline';
2
2
  import { union, some, creator, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
+ import { emstrong } from './emstrong';
4
5
  import { str } from '../source';
5
- import { startTight, blank } from '../util';
6
+ import { startTight, blankWith } from '../util';
6
7
  import { html, defrag } from 'typed-dom/dom';
7
8
  import { unshift } from 'spica/array';
8
9
 
9
10
  export const strong: StrongParser = lazy(() => creator(surround(
10
11
  str('**'),
11
12
  startTight(some(union([
12
- some(inline, blank('', '**')),
13
- open(some(inline, '*'), inline),
13
+ some(inline, blankWith('**')),
14
+ open(some(inline, '*'), union([
15
+ emstrong,
16
+ strong,
17
+ ])),
14
18
  ])), '*'),
15
19
  str('**'), false,
16
20
  ([, bs], rest) => [[html('strong', defrag(bs))], rest],
@@ -157,6 +157,7 @@ describe('Unit: parser/inline', () => {
157
157
  assert.deepStrictEqual(inspect(parser('<http://host>')), [['<', '<a href="http://host" target="_blank">http://host</a>', '>'], '']);
158
158
  assert.deepStrictEqual(inspect(parser('<<small>a<</small>')), [['<', '<small>a&lt;</small>'], '']);
159
159
  assert.deepStrictEqual(inspect(parser('<sup><sub>a</sub>')), [['<', 'sup', '>', '<sub>a</sub>'], '']);
160
+ assert.deepStrictEqual(inspect(parser('*<small>*`</small>`')), [['<em>&lt;small&gt;</em>', '<code data-src="`</small>`">&lt;/small&gt;</code>'], '']);
160
161
  assert.deepStrictEqual(inspect(parser('[~http://host')), [['[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
161
162
  assert.deepStrictEqual(inspect(parser('[~a@b')), [['[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
162
163
  assert.deepStrictEqual(inspect(parser('[~~a~~]')), [['[', '<del>a</del>', ']'], '']);
@@ -2,7 +2,7 @@ import { Infinity, Set, Map } from 'spica/global';
2
2
  import { number as calculate, isFixed } from '../inline/extension/label';
3
3
  import { define } from 'typed-dom/dom';
4
4
  import { MultiMap } from 'spica/multimap';
5
- import { push, join } from 'spica/array';
5
+ import { push } from 'spica/array';
6
6
 
7
7
  export function* figure(
8
8
  target: ParentNode & Node,
@@ -85,7 +85,7 @@ export function* figure(
85
85
  let number = calculate(
86
86
  label,
87
87
  numbers.has(group) && !isFixed(label)
88
- ? join(numbers.get(group)!.split('.').slice(0, bases.length), '.')
88
+ ? numbers.get(group)!.split('.').slice(0, bases.length).join('.')
89
89
  : base);
90
90
  assert(def.matches('figure') || number.endsWith('.0'));
91
91
  if (number.endsWith('.0')) {
@@ -94,19 +94,18 @@ export function* figure(
94
94
  if (group !== '$' || tagName === 'FIGURE' && def.firstChild) continue;
95
95
  if (number.startsWith('0.')) {
96
96
  assert(number.endsWith('.0'));
97
- number = join(
98
- index.slice(0)
99
- .reduce((ns, _, i, xs) => {
100
- i === ns.length
101
- ? xs.length = i
102
- : ns[i] = +ns[i] > +xs[i]
103
- ? ns[i]
104
- : +ns[i] === 0
105
- ? xs[i]
106
- : `${+xs[i] + 1}`;
107
- return ns;
108
- }, number.split('.')),
109
- '.');
97
+ number = index.slice(0)
98
+ .reduce((ns, _, i, xs) => {
99
+ i === ns.length
100
+ ? xs.length = i
101
+ : ns[i] = +ns[i] > +xs[i]
102
+ ? ns[i]
103
+ : +ns[i] === 0
104
+ ? xs[i]
105
+ : `${+xs[i] + 1}`;
106
+ return ns;
107
+ }, number.split('.'))
108
+ .join('.');
110
109
  }
111
110
  base = number;
112
111
  bases = index = base.split('.');
@@ -184,8 +183,8 @@ function increment(bases: readonly string[], el: HTMLHeadingElement): string {
184
183
  const index = (+el.tagName[1] - 1 || 1) - 1;
185
184
  assert(index >= 0);
186
185
  return index + 1 < bases.length
187
- ? join(
188
- bases.slice(0, index + 2).map((v, i) => {
186
+ ? bases.slice(0, index + 2)
187
+ .map((v, i) => {
189
188
  switch (true) {
190
189
  case i < index:
191
190
  return v;
@@ -194,8 +193,8 @@ function increment(bases: readonly string[], el: HTMLHeadingElement): string {
194
193
  default:
195
194
  return 0;
196
195
  }
197
- }),
198
- '.')
196
+ })
197
+ .join('.')
199
198
  : '';
200
199
  }
201
200
 
@@ -18,10 +18,11 @@ describe('Unit: parser/source/escsource', () => {
18
18
 
19
19
  it('space', () => {
20
20
  assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
21
- assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
22
- assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
21
+ assert.deepStrictEqual(inspect(parser(' ')), [[' ', ' '], '']);
22
+ assert.deepStrictEqual(inspect(parser(' ')), [[' ', ' '], '']);
23
23
  assert.deepStrictEqual(inspect(parser(' \n')), [[' ', '\n'], '']);
24
- assert.deepStrictEqual(inspect(parser(' \n')), [[' ', '\n'], '']);
24
+ assert.deepStrictEqual(inspect(parser(' \n')), [[' ', ' ', '\n'], '']);
25
+ assert.deepStrictEqual(inspect(parser(' \n')), [[' ', ' ', '\n'], '']);
25
26
  });
26
27
 
27
28
  it('linebreak', () => {
@@ -2,11 +2,11 @@ import { EscapableSourceParser } from '../source';
2
2
  import { creator } from '../../combinator';
3
3
  import { nonWhitespace } from './text';
4
4
 
5
- const separator = /[\s\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/;
5
+ const delimiter = /[\s\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/;
6
6
 
7
7
  export const escsource: EscapableSourceParser = creator(source => {
8
8
  if (source === '') return;
9
- const i = source.search(separator);
9
+ const i = source.search(delimiter);
10
10
  switch (i) {
11
11
  case -1:
12
12
  return [[source], ''];
@@ -22,7 +22,7 @@ export const escsource: EscapableSourceParser = creator(source => {
22
22
  ? source.search(nonWhitespace)
23
23
  : 1;
24
24
  assert(i > 0);
25
- return [[source.slice(0, i)], source.slice(i)];
25
+ return [[source.slice(0, i - +b || 1)], source.slice(i - +b || 1)];
26
26
  }
27
27
  default:
28
28
  return [[source.slice(0, i)], source.slice(i)];
@@ -26,8 +26,8 @@ describe('Unit: parser/text/text', () => {
26
26
  assert.deepStrictEqual(inspect(parser(' \\\n')), [['<span class="linebreak"> </span>'], '']);
27
27
  assert.deepStrictEqual(inspect(parser(' \\\n')), [['<span class="linebreak"> </span>'], '']);
28
28
  assert.deepStrictEqual(inspect(parser(' a')), [[' ', 'a'], '']);
29
- assert.deepStrictEqual(inspect(parser(' a')), [[' ', 'a'], '']);
30
- assert.deepStrictEqual(inspect(parser(' a')), [[' ', 'a'], '']);
29
+ assert.deepStrictEqual(inspect(parser(' a')), [[' ', ' ', 'a'], '']);
30
+ assert.deepStrictEqual(inspect(parser(' a')), [[' ', ' ', 'a'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('a ')), [['a'], '']);
32
32
  assert.deepStrictEqual(inspect(parser('a ')), [['a'], '']);
33
33
  assert.deepStrictEqual(inspect(parser('a \n')), [['a', '<br>'], '']);
@@ -35,8 +35,8 @@ describe('Unit: parser/text/text', () => {
35
35
  assert.deepStrictEqual(inspect(parser('a \\\n')), [['a', '<span class="linebreak"> </span>'], '']);
36
36
  assert.deepStrictEqual(inspect(parser('a \\\n')), [['a', '<span class="linebreak"> </span>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser('a b')), [['a', ' ', 'b'], '']);
38
- assert.deepStrictEqual(inspect(parser('a b')), [['a', ' ', 'b'], '']);
39
- assert.deepStrictEqual(inspect(parser('a b')), [['a', ' ', 'b'], '']);
38
+ assert.deepStrictEqual(inspect(parser('a b')), [['a', ' ', ' ', 'b'], '']);
39
+ assert.deepStrictEqual(inspect(parser('a b')), [['a', ' ', ' ', 'b'], '']);
40
40
  });
41
41
 
42
42
  it('hardbreak', () => {
@@ -4,14 +4,14 @@ import { union, focus, creator } from '../../combinator';
4
4
  import { str } from './str';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
- export const separator = /[\s\x00-\x7F]|\S#|[、。!?][^\S\n]*(?=\\\n)/;
7
+ export const delimiter = /[\s\x00-\x7F]|\S#|[、。!?][^\S\n]*(?=\\\n)/;
8
8
  export const nonWhitespace = /[\S\n]|$/;
9
9
  export const nonAlphanumeric = /[^0-9A-Za-z]|\S#|$/;
10
10
  const repeat = str(/^(.)\1*/);
11
11
 
12
12
  export const text: TextParser = creator((source, context) => {
13
13
  if (source === '') return;
14
- const i = source.search(separator);
14
+ const i = source.search(delimiter);
15
15
  switch (i) {
16
16
  case -1:
17
17
  return [[source], ''];
@@ -70,7 +70,7 @@ export const text: TextParser = creator((source, context) => {
70
70
  || b && source[i] === '\n'
71
71
  || b && source[i] === '\\' && source[i + 1] === '\n'
72
72
  ? [[], source.slice(i)]
73
- : [[source.slice(0, i)], source.slice(i)];
73
+ : [[source.slice(0, i - +b || 1)], source.slice(i - +b || 1)];
74
74
  }
75
75
  default:
76
76
  return [[source.slice(0, i)], source.slice(i)];
@@ -18,10 +18,11 @@ describe('Unit: parser/source/unescapable', () => {
18
18
 
19
19
  it('space', () => {
20
20
  assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
21
- assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
22
- assert.deepStrictEqual(inspect(parser(' ')), [[' '], '']);
21
+ assert.deepStrictEqual(inspect(parser(' ')), [[' ', ' '], '']);
22
+ assert.deepStrictEqual(inspect(parser(' ')), [[' ', ' '], '']);
23
23
  assert.deepStrictEqual(inspect(parser(' \n')), [[' ', '\n'], '']);
24
- assert.deepStrictEqual(inspect(parser(' \n')), [[' ', '\n'], '']);
24
+ assert.deepStrictEqual(inspect(parser(' \n')), [[' ', ' ', '\n'], '']);
25
+ assert.deepStrictEqual(inspect(parser(' \n')), [[' ', ' ', '\n'], '']);
25
26
  });
26
27
 
27
28
  it('linebreak', () => {
@@ -1,11 +1,11 @@
1
1
  import { UnescapableSourceParser } from '../source';
2
2
  import { creator } from '../../combinator';
3
- import { separator, nonWhitespace, nonAlphanumeric, isAlphanumeric } from './text';
3
+ import { delimiter, nonWhitespace, nonAlphanumeric, isAlphanumeric } from './text';
4
4
 
5
5
  export const unescsource: UnescapableSourceParser = creator(source => {
6
6
  assert(source[0] !== '\x1B');
7
7
  if (source === '') return;
8
- const i = source.search(separator);
8
+ const i = source.search(delimiter);
9
9
  switch (i) {
10
10
  case -1:
11
11
  return [[source], ''];
@@ -15,7 +15,7 @@ export const unescsource: UnescapableSourceParser = creator(source => {
15
15
  ? source.search(b ? nonWhitespace : nonAlphanumeric) || 1
16
16
  : 1;
17
17
  assert(i > 0);
18
- return [[source.slice(0, i)], source.slice(i)];
18
+ return [[source.slice(0, i - +b || 1)], source.slice(i - +b || 1)];
19
19
  }
20
20
  default:
21
21
  return [[source.slice(0, i)], source.slice(i)];
@@ -1,19 +1,25 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { MarkdownParser } from '../../markdown';
3
3
  import { Parser, eval } from '../combinator/data/parser';
4
- import { union, some, verify, convert } from '../combinator';
4
+ import { union, some, verify, convert, fmap } from '../combinator';
5
5
  import { unsafehtmlentity } from './inline/htmlentity';
6
6
  import { linebreak, unescsource } from './source';
7
7
  import { invisibleHTMLEntityNames } from './api/normalize';
8
8
  import { reduce } from 'spica/memoize';
9
9
  import { push } from 'spica/array';
10
10
 
11
- export function blank(prefix: '' | RegExp, suffix: string | RegExp): RegExp {
11
+ export const regBlankInlineStart = new RegExp(String.raw
12
+ `^(?:\\?[^\S\n]|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)+`);
13
+
14
+ export function blankWith(delimiter: string | RegExp): RegExp;
15
+ export function blankWith(starting: '' | '\n', delimiter: string | RegExp): RegExp;
16
+ export function blankWith(starting: '' | '\n', delimiter?: string | RegExp): RegExp {
17
+ if (delimiter === undefined) return blankWith('', starting);
12
18
  return new RegExp(String.raw
13
- `^${
14
- prefix && prefix.source
15
- }(?:\\\s|[^\S\n]+|\n|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)?${
16
- typeof suffix === 'string' ? suffix.replace(/[*+()\[\]]/g, '\\$&') : suffix.source
19
+ `^(?:(?=${
20
+ starting
21
+ })(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)${starting && '+'})?${
22
+ typeof delimiter === 'string' ? delimiter.replace(/[*+()\[\]]/g, '\\$&') : delimiter.source
17
23
  }`);
18
24
  }
19
25
 
@@ -53,9 +59,10 @@ export function startLoose<T extends HTMLElement | string>(parser: Parser<T>, ex
53
59
  ? parser(source, context)
54
60
  : undefined;
55
61
  }
56
- export const isStartLoose = reduce((source: string, context: MarkdownParser.Context, except?: string): boolean => {
57
- return isStartTight(source.replace(/^[^\S\n]+/, ''), context, except);
62
+ const isStartLoose = reduce((source: string, context: MarkdownParser.Context, except?: string): boolean => {
63
+ return isStartTight(source.replace(regBlankInlineStart, ''), context, except);
58
64
  }, (source, _, except = '') => `${source}\x1E${except}`);
65
+
59
66
  export function startTight<P extends Parser<unknown>>(parser: P, except?: string): P;
60
67
  export function startTight<T>(parser: Parser<T>, except?: string): Parser<T> {
61
68
  return (source, context) =>
@@ -130,10 +137,16 @@ function isVisible(node: HTMLElement | string, strpos?: number): boolean {
130
137
  }
131
138
  }
132
139
 
133
- export function trimSpaceStart<P extends Parser<unknown>>(parser: P): P;
134
- export function trimSpaceStart<T>(parser: Parser<T>): Parser<T> {
140
+ export function trimBlankInline<P extends Parser<HTMLElement | string>>(parser: P): P;
141
+ export function trimBlankInline<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
142
+ return fmap(
143
+ trimBlankInlineStart(parser),
144
+ trimNodeEnd);
145
+ }
146
+ function trimBlankInlineStart<P extends Parser<unknown>>(parser: P): P;
147
+ function trimBlankInlineStart<T>(parser: Parser<T>): Parser<T> {
135
148
  return convert(
136
- reduce(source => source.replace(/^[^\S\n]+/, '')),
149
+ reduce(source => source.replace(regBlankInlineStart, '')),
137
150
  parser);
138
151
  }
139
152
  //export function trimNode(nodes: (HTMLElement | string)[]): (HTMLElement | string)[] {
@@ -153,7 +166,7 @@ export function trimSpaceStart<T>(parser: Parser<T>): Parser<T> {
153
166
  // }
154
167
  // return nodes;
155
168
  //}
156
- export function trimNodeEnd(nodes: (HTMLElement | string)[]): (HTMLElement | string)[] {
169
+ export function trimNodeEnd<T extends HTMLElement | string>(nodes: T[]): T[] {
157
170
  const skip = nodes.length > 0 &&
158
171
  typeof nodes[nodes.length - 1] === 'object' &&
159
172
  nodes[nodes.length - 1]['className'] === 'indexer'
@@ -163,7 +176,7 @@ export function trimNodeEnd(nodes: (HTMLElement | string)[]): (HTMLElement | str
163
176
  if (typeof node === 'string') {
164
177
  const pos = node.trimEnd().length;
165
178
  if (pos > 0) {
166
- nodes[nodes.length - 1] = node.slice(0, pos);
179
+ nodes[nodes.length - 1] = node.slice(0, pos) as T;
167
180
  break;
168
181
  }
169
182
  }
@@ -1,11 +1,11 @@
1
- import { ObjectFromEntries } from 'spica/alias';
1
+ import { Object } from 'spica/global';
2
2
  import { Collection } from 'spica/collection';
3
3
  import { define } from 'typed-dom/dom';
4
4
 
5
5
  export function image(source: HTMLImageElement, url: URL, cache?: Collection<string, HTMLElement>): HTMLImageElement {
6
6
  if (cache?.has(url.href)) return define(
7
7
  cache.get(url.href)!.cloneNode(true) as HTMLImageElement,
8
- ObjectFromEntries([...source.attributes]
8
+ Object.fromEntries([...source.attributes]
9
9
  .map(attr => [attr.name, attr.value])));
10
10
  define(source, {
11
11
  'data-type': 'image',
@@ -14,7 +14,7 @@ export function image(source: HTMLImageElement, url: URL, cache?: Collection<str
14
14
  });
15
15
  cache?.set(url.href, define(
16
16
  source.cloneNode(true),
17
- ObjectFromEntries([...source.attributes]
17
+ Object.fromEntries([...source.attributes]
18
18
  .filter(attr => !['class', 'data-type', 'data-src', 'src', 'loading'].includes(attr.name))
19
19
  .map(attr => [attr.name, null]))));
20
20
  return source;
@@ -1,4 +1,4 @@
1
- import { ObjectFromEntries } from 'spica/alias';
1
+ import { Object } from 'spica/global';
2
2
  import { html } from 'typed-dom/dom';
3
3
 
4
4
  const extensions = [
@@ -11,7 +11,7 @@ export function video(source: HTMLImageElement, url: URL): HTMLVideoElement | un
11
11
  return html('video', {
12
12
  src: source.getAttribute('data-src'),
13
13
  'data-type': 'video',
14
- ...ObjectFromEntries([...source.attributes]
14
+ ...Object.fromEntries([...source.attributes]
15
15
  .map(attr => [attr.name, attr.value])),
16
16
  muted: '',
17
17
  controls: '',