securemark 0.258.8 → 0.259.1

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 (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/design.md +4 -4
  3. package/dist/index.js +138 -131
  4. package/markdown.d.ts +37 -19
  5. package/package.json +1 -1
  6. package/src/combinator/data/parser/context.ts +16 -17
  7. package/src/parser/api/bind.ts +2 -2
  8. package/src/parser/api/parse.ts +2 -2
  9. package/src/parser/block/reply/cite.test.ts +3 -1
  10. package/src/parser/block/reply/cite.ts +1 -0
  11. package/src/parser/context.ts +15 -6
  12. package/src/parser/inline/annotation.ts +3 -4
  13. package/src/parser/inline/autolink/account.ts +2 -2
  14. package/src/parser/inline/autolink/anchor.ts +2 -2
  15. package/src/parser/inline/autolink/hashnum.ts +2 -2
  16. package/src/parser/inline/autolink/hashtag.ts +2 -2
  17. package/src/parser/inline/autolink/url.ts +2 -2
  18. package/src/parser/inline/autolink.ts +1 -1
  19. package/src/parser/inline/bracket.ts +8 -8
  20. package/src/parser/inline/comment.ts +2 -2
  21. package/src/parser/inline/deletion.ts +2 -2
  22. package/src/parser/inline/emphasis.ts +2 -2
  23. package/src/parser/inline/emstrong.ts +2 -2
  24. package/src/parser/inline/extension/index.ts +3 -4
  25. package/src/parser/inline/extension/placeholder.ts +2 -2
  26. package/src/parser/inline/html.ts +2 -2
  27. package/src/parser/inline/insertion.ts +2 -2
  28. package/src/parser/inline/link.ts +70 -66
  29. package/src/parser/inline/mark.ts +2 -2
  30. package/src/parser/inline/media.ts +11 -10
  31. package/src/parser/inline/reference.ts +3 -4
  32. package/src/parser/inline/ruby.ts +2 -2
  33. package/src/parser/inline/strong.ts +2 -2
  34. package/src/parser/inline/template.ts +2 -2
  35. package/src/parser/inline.ts +0 -1
  36. package/src/parser/source/text.ts +2 -2
package/markdown.d.ts CHANGED
@@ -577,6 +577,7 @@ export namespace MarkdownParser {
577
577
  InlineParser.AutolinkParser.AnchorParser,
578
578
  Parser<HTMLAnchorElement, Context, []>,
579
579
  Parser<HTMLAnchorElement, Context, []>,
580
+ Parser<HTMLAnchorElement, Context, []>,
580
581
  ]>,
581
582
  ]> {
582
583
  }
@@ -660,7 +661,7 @@ export namespace MarkdownParser {
660
661
  export interface AnnotationParser extends
661
662
  // ((abc))
662
663
  Inline<'annotation'>,
663
- Parser<HTMLElement | string, Context, [
664
+ Parser<HTMLElement, Context, [
664
665
  InlineParser,
665
666
  ]> {
666
667
  }
@@ -669,7 +670,7 @@ export namespace MarkdownParser {
669
670
  // [[^abbr]]
670
671
  // [[^abbr| abc]]
671
672
  Inline<'reference'>,
672
- Parser<HTMLElement | string, Context, [
673
+ Parser<HTMLElement, Context, [
673
674
  ReferenceParser.AbbrParser,
674
675
  InlineParser,
675
676
  InlineParser,
@@ -689,7 +690,7 @@ export namespace MarkdownParser {
689
690
  export interface TemplateParser extends
690
691
  // {{abc}}
691
692
  Inline<'template'>,
692
- Parser<HTMLSpanElement | string, Context, [
693
+ Parser<HTMLSpanElement, Context, [
693
694
  TemplateParser.BracketParser,
694
695
  SourceParser.EscapableSourceParser,
695
696
  ]> {
@@ -842,21 +843,38 @@ export namespace MarkdownParser {
842
843
  // { uri }
843
844
  // [abc]{uri nofollow}
844
845
  Inline<'link'>,
845
- Parser<HTMLElement | string, Context, [
846
- LinkParser.ContentParser,
847
- LinkParser.ParameterParser,
848
- ]> {
849
- }
850
- export interface TextLinkParser extends
851
- // { uri }
852
- // [abc]{uri nofollow}
853
- Inline<'textlink'>,
854
846
  Parser<HTMLAnchorElement, Context, [
855
- LinkParser.TextParser,
856
- LinkParser.ParameterParser,
847
+ LinkParser.MediaLinkParser,
848
+ LinkParser.TextLinkParser,
857
849
  ]> {
858
850
  }
859
851
  export namespace LinkParser {
852
+ export interface TextLinkParser extends
853
+ Inline<'link/textlink'>,
854
+ Parser<HTMLAnchorElement, Context, [
855
+ Parser<(HTMLElement | string)[], Context, [
856
+ InlineParser,
857
+ ]>,
858
+ LinkParser.ParameterParser,
859
+ ]> {
860
+ }
861
+ export interface MediaLinkParser extends
862
+ Inline<'link/medialink'>,
863
+ Parser<HTMLAnchorElement, Context, [
864
+ Parser<HTMLElement[], Context, [
865
+ MediaParser,
866
+ ShortmediaParser,
867
+ ]>,
868
+ LinkParser.ParameterParser,
869
+ ]> {
870
+ }
871
+ export interface UnsafeLinkParser extends
872
+ Inline<'link/unsafelink'>,
873
+ Parser<HTMLAnchorElement, Context, [
874
+ LinkParser.TextParser,
875
+ LinkParser.ParameterParser,
876
+ ]> {
877
+ }
860
878
  export interface ContentParser extends
861
879
  Inline<'link/content'>,
862
880
  Parser<(HTMLElement | string)[], Context, [
@@ -1093,7 +1111,7 @@ export namespace MarkdownParser {
1093
1111
  // https://host
1094
1112
  Inline<'url'>,
1095
1113
  Parser<HTMLAnchorElement, Context, [
1096
- TextLinkParser,
1114
+ LinkParser.UnsafeLinkParser,
1097
1115
  ]> {
1098
1116
  }
1099
1117
  export namespace UrlParser {
@@ -1135,28 +1153,28 @@ export namespace MarkdownParser {
1135
1153
  // @user
1136
1154
  Inline<'account'>,
1137
1155
  Parser<HTMLAnchorElement, Context, [
1138
- TextLinkParser,
1156
+ LinkParser.UnsafeLinkParser,
1139
1157
  ]> {
1140
1158
  }
1141
1159
  export interface HashtagParser extends
1142
1160
  // #tag
1143
1161
  Inline<'hashtag'>,
1144
1162
  Parser<HTMLAnchorElement, Context, [
1145
- TextLinkParser,
1163
+ LinkParser.UnsafeLinkParser,
1146
1164
  ]> {
1147
1165
  }
1148
1166
  export interface HashnumParser extends
1149
1167
  // #1
1150
1168
  Inline<'hashnum'>,
1151
1169
  Parser<HTMLAnchorElement, Context, [
1152
- TextLinkParser,
1170
+ LinkParser.UnsafeLinkParser,
1153
1171
  ]> {
1154
1172
  }
1155
1173
  export interface AnchorParser extends
1156
1174
  // >>1
1157
1175
  Inline<'anchor'>,
1158
1176
  Parser<HTMLAnchorElement, Context, [
1159
- TextLinkParser,
1177
+ LinkParser.UnsafeLinkParser,
1160
1178
  ]> {
1161
1179
  }
1162
1180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.8",
3
+ "version": "0.259.1",
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",
@@ -56,41 +56,39 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
56
56
  return result;
57
57
  }
58
58
 
59
- export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
60
- export function syntax<T>(syntax: number, precedence: number, cost: number, parser?: Parser<T>): Parser<T> {
61
- return creation(cost, (source, context) => {
59
+ export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, state: number, parser: P): P;
60
+ export function syntax<T>(syntax: number, prec: number, cost: number, state: number, parser?: Parser<T>): Parser<T> {
61
+ return creation(cost, precedence(prec, (source, context) => {
62
62
  if (source === '') return;
63
63
  const memo = context.memo ??= new Memo();
64
64
  context.memorable ??= ~0;
65
- const p = context.precedence;
66
- context.precedence = precedence;
67
65
  const position = source.length;
68
- const state = context.state ?? 0;
69
- const cache = syntax && memo.get(position, syntax, state);
66
+ const st0 = context.state ?? 0;
67
+ const st1 = context.state = st0 | state;
68
+ const cache = syntax && memo.get(position, syntax, st1);
70
69
  const result: Result<T> = cache
71
70
  ? cache.length === 0
72
71
  ? undefined
73
72
  : [cache[0], source.slice(cache[1])]
74
73
  : parser!(source, context);
75
- if (syntax && state & context.memorable!) {
76
- cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
77
- assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
74
+ if (syntax && st0 & context.memorable!) {
75
+ cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
76
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
78
77
  }
79
- if (result && !state && memo.length! >= position) {
80
- assert(!(state & context.memorable!));
81
- memo.clear(position);
78
+ if (result && !st0 && memo.length! >= position + 2) {
79
+ assert(!(st0 & context.memorable!));
80
+ memo.clear(position + 2);
82
81
  }
83
- context.precedence = p;
82
+ context.state = st0;
84
83
  return result;
85
- });
84
+ }));
86
85
  }
87
86
 
88
87
  export function creation<P extends Parser<unknown>>(parser: P): P;
89
88
  export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
90
89
  export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
91
90
  if (typeof cost === 'function') return creation(1, cost);
92
- if (cost === 0) return parser!;
93
- assert(cost >= 0);
91
+ assert(cost > 0);
94
92
  return (source, context) => {
95
93
  const { resources = { clock: 1, recursion: 1 } } = context;
96
94
  if (resources.clock <= 0) throw new Error('Too many creations');
@@ -107,6 +105,7 @@ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown
107
105
 
108
106
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
109
107
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
108
+ assert(precedence > 0);
110
109
  return (source, context) => {
111
110
  const p = context.precedence;
112
111
  context.precedence = precedence;
@@ -5,7 +5,7 @@ import { eval } from '../../combinator/data/parser';
5
5
  import { segment, validate, MAX_INPUT_SIZE } from '../segment';
6
6
  import { header } from '../header';
7
7
  import { block } from '../block';
8
- import { backtrackable } from '../context';
8
+ import { State } from '../context';
9
9
  import { normalize } from './normalize';
10
10
  import { headers } from './header';
11
11
  import { figure } from '../processor/figure';
@@ -24,7 +24,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
24
24
  let context: MarkdownParser.Context = {
25
25
  ...settings,
26
26
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
- memorable: backtrackable,
27
+ memorable: State.backtrackable,
28
28
  };
29
29
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
30
30
  assert(!settings.id);
@@ -5,7 +5,7 @@ import { eval } from '../../combinator/data/parser';
5
5
  import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
6
6
  import { header } from '../header';
7
7
  import { block } from '../block';
8
- import { backtrackable } from '../context';
8
+ import { State } from '../context';
9
9
  import { normalize } from './normalize';
10
10
  import { headers } from './header';
11
11
  import { figure } from '../processor/figure';
@@ -30,7 +30,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
30
30
  ...context?.resources && {
31
31
  resources: context.resources,
32
32
  },
33
- memorable: backtrackable,
33
+ memorable: State.backtrackable,
34
34
  };
35
35
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
36
36
  const node = frag();
@@ -18,7 +18,7 @@ describe('Unit: parser/block/reply/cite', () => {
18
18
  assert.deepStrictEqual(inspect(parser('>>\\')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('>>01#')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('>>01@')), undefined);
21
- assert.deepStrictEqual(inspect(parser('>>https://host')), undefined);
21
+ assert.deepStrictEqual(inspect(parser('>>http://')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('>>tel:1234567890')), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('>>..')), undefined);
24
24
  assert.deepStrictEqual(inspect(parser('>> 0')), undefined);
@@ -43,6 +43,8 @@ describe('Unit: parser/block/reply/cite', () => {
43
43
  assert.deepStrictEqual(inspect(parser('>>#a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#a</a></span>', '<br>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('>>#index:a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#index:a</a></span>', '<br>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser('>>#:~:text=a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#:~:text=a</a></span>', '<br>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('>>http://host')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
47
+ assert.deepStrictEqual(inspect(parser('>>https://host')), [['<span class="cite">&gt;<a class="anchor" href="https://host" target="_blank" data-depth="1">&gt;https://host</a></span>', '<br>'], '']);
46
48
  });
47
49
 
48
50
  });
@@ -14,6 +14,7 @@ export const cite: ReplyParser.CiteParser = creation(line(fmap(validate(
14
14
  // リンクの実装は後で検討
15
15
  focus(/^>>\.[^\S\n]*(?:$|\n)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
16
  focus(/^>>#\S*[^\S\n]*(?:$|\n)/, source => [[html('a', { class: 'anchor' }, source)], '']),
17
+ focus(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, source => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
17
18
  ]),
18
19
  ]))),
19
20
  ([el, quotes = '']: [HTMLElement, string?]) => [
@@ -23,10 +23,19 @@ export const enum State {
23
23
  link = 1 << 2,
24
24
  media = 1 << 1,
25
25
  autolink = 1 << 0,
26
+ none = 0,
27
+ linkable = 0
28
+ | State.annotation
29
+ | State.reference
30
+ | State.index
31
+ | State.label
32
+ | State.link
33
+ | State.media
34
+ | State.autolink,
35
+ backtrackable = 0
36
+ | State.annotation
37
+ | State.reference
38
+ | State.index
39
+ | State.link
40
+ | State.media,
26
41
  }
27
- export const backtrackable = 0
28
- | State.annotation
29
- | State.reference
30
- | State.index
31
- | State.link
32
- | State.media;
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
- import { union, some, context, syntax, constraint, state, surround, lazy } from '../../combinator';
3
+ import { union, some, context, syntax, constraint, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { Syntax, State } from '../context';
6
6
  import { startLoose, trimNode } from '../visibility';
@@ -9,11 +9,10 @@ import { html, defrag } from 'typed-dom/dom';
9
9
  export const annotation: AnnotationParser = lazy(() => surround(
10
10
  '((',
11
11
  constraint(State.annotation, false,
12
- state(State.annotation | State.media,
13
- syntax(Syntax.annotation, 6, 1,
12
+ syntax(Syntax.annotation, 6, 1, State.annotation | State.media,
14
13
  startLoose(
15
14
  context({ delimiters: undefined },
16
- some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')))),
15
+ some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')'))),
17
16
  '))',
18
17
  false,
19
18
  ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { define } from 'typed-dom/dom';
6
6
 
@@ -24,5 +24,5 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
24
24
  ? `https://${source.slice(1).replace('/', '/@')}`
25
25
  : `/${source}`
26
26
  } }`,
27
- union([textlink]))),
27
+ union([unsafelink]))),
28
28
  ([el]) => [define(el, { class: 'account' })]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, validate, focus, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { define } from 'typed-dom/dom';
5
5
 
6
6
  // Timeline(pseudonym): user/tid
@@ -21,5 +21,5 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fma
21
21
  ? `/@${source.slice(2).replace('/', '/timeline/')}`
22
22
  : `?at=${source.slice(2)}`
23
23
  } }`,
24
- union([textlink]))),
24
+ union([unsafelink]))),
25
25
  ([el]) => [define(el, { class: 'anchor' })])));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { emoji } from './hashtag';
5
5
  import { str } from '../../source';
6
6
  import { define } from 'typed-dom/dom';
@@ -9,5 +9,5 @@ export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
9
9
  open('#', str(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, emoji), 'u'))),
10
10
  convert(
11
11
  source => `[${source}]{ ${source.slice(1)} }`,
12
- union([textlink]))),
12
+ union([unsafelink]))),
13
13
  ([el]) => [define(el, { class: 'hashnum', href: null })]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { str } from '../../source';
5
5
  import { define } from 'typed-dom/dom';
6
6
 
@@ -31,5 +31,5 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
31
31
  ? `https://${source.slice(1).replace('/', '/hashtags/')}`
32
32
  : `/hashtags/${source.slice(1)}`
33
33
  } }`,
34
- union([textlink]))),
34
+ union([unsafelink]))),
35
35
  ([el]) => [define(el, { class: 'hashtag' }, el.innerText)]));
@@ -1,6 +1,6 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { union, some, creation, precedence, validate, focus, rewrite, convert, surround, open, lazy } from '../../../combinator';
3
- import { textlink } from '../link';
3
+ import { unsafelink } from '../link';
4
4
  import { unescsource } from '../../source';
5
5
 
6
6
  const closer = /^[-+*=~^,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
@@ -11,7 +11,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'ht
11
11
  focus(/^[\x21-\x7E]+/, some(union([bracket, some(unescsource, closer)])))),
12
12
  convert(
13
13
  url => `{ ${url} }`,
14
- union([textlink])))));
14
+ union([unsafelink])))));
15
15
 
16
16
  const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creation(precedence(2, union([
17
17
  surround('(', some(union([bracket, unescsource]), ')'), ')', true),
@@ -14,7 +14,7 @@ import { stringify } from '../util';
14
14
  export const autolink: AutolinkParser = fmap(
15
15
  validate(/^(?:[@#>0-9A-Za-z]|\S[#>])/,
16
16
  constraint(State.autolink, false,
17
- syntax(Syntax.autolink, 1, 1,
17
+ syntax(Syntax.autolink, 1, 1, State.none,
18
18
  some(union([
19
19
  url,
20
20
  email,
@@ -3,29 +3,29 @@ import { BracketParser } from '../inline';
3
3
  import { union, some, syntax, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { Syntax } from '../context';
6
+ import { Syntax, State } from '../context';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift, push } from 'spica/array';
9
9
 
10
10
  const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
11
11
 
12
12
  export const bracket: BracketParser = lazy(() => union([
13
- surround(str('('), syntax(Syntax.none, 2, 1, str(index)), str(')')),
14
- surround(str('('), syntax(Syntax.bracket, 2, 1, some(inline, ')', [[')', 2]])), str(')'), true,
13
+ surround(str('('), syntax(Syntax.none, 2, 1, State.none, str(index)), str(')')),
14
+ surround(str('('), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ')', [[')', 2]])), str(')'), true,
15
15
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
16
16
  ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
17
- surround(str('('), syntax(Syntax.none, 2, 1, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0))))), str(')')),
18
- surround(str('('), syntax(Syntax.bracket, 2, 1, some(inline, ')', [[')', 2]])), str(')'), true,
17
+ surround(str('('), syntax(Syntax.none, 2, 1, State.none, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0))))), str(')')),
18
+ surround(str('('), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ')', [[')', 2]])), str(')'), true,
19
19
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
20
20
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
21
- surround(str('['), syntax(Syntax.bracket, 2, 1, some(inline, ']', [[']', 2]])), str(']'), true,
21
+ surround(str('['), syntax(Syntax.bracket, 2, 1, State.none, some(inline, ']', [[']', 2]])), str(']'), true,
22
22
  undefined,
23
23
  ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
24
- surround(str('{'), syntax(Syntax.bracket, 2, 1, some(inline, '}', [['}', 2]])), str('}'), true,
24
+ surround(str('{'), syntax(Syntax.bracket, 2, 1, State.none, some(inline, '}', [['}', 2]])), str('}'), true,
25
25
  undefined,
26
26
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
27
27
  // Control media blinking in editing rather than control confusion of pairs of quote marks.
28
- surround(str('"'), syntax(Syntax.quote, 8, 1, some(inline, '"', [['"', 8]])), str('"'), true,
28
+ surround(str('"'), syntax(Syntax.quote, 8, 1, State.none, some(inline, '"', [['"', 8]])), str('"'), true,
29
29
  undefined,
30
30
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
31
31
  ]));
@@ -2,12 +2,12 @@ import { CommentParser } from '../inline';
2
2
  import { union, some, syntax, validate, surround, open, close, match, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { text, str } from '../source';
5
- import { Syntax } from '../context';
5
+ import { Syntax, State } from '../context';
6
6
  import { html, defrag } from 'typed-dom/dom';
7
7
  import { memoize } from 'spica/memoize';
8
8
  import { unshift, push } from 'spica/array';
9
9
 
10
- export const comment: CommentParser = lazy(() => validate('[%', syntax(Syntax.none, 4, 1, match(
10
+ export const comment: CommentParser = lazy(() => validate('[%', syntax(Syntax.none, 4, 1, State.none, match(
11
11
  /^\[(%+)\s/,
12
12
  memoize(
13
13
  ([, fence]) =>
@@ -2,14 +2,14 @@ import { DeletionParser } from '../inline';
2
2
  import { union, some, syntax, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
- import { Syntax } from '../context';
5
+ import { Syntax, State } from '../context';
6
6
  import { blankWith } from '../visibility';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
10
  export const deletion: DeletionParser = lazy(() => surround(
11
11
  str('~~'),
12
- syntax(Syntax.none, 1, 1,
12
+ syntax(Syntax.none, 1, 1, State.none,
13
13
  some(union([
14
14
  some(inline, blankWith('\n', '~~')),
15
15
  open('\n', some(inline, '~'), true),
@@ -4,14 +4,14 @@ import { inline } from '../inline';
4
4
  import { emstrong } from './emstrong';
5
5
  import { strong } from './strong';
6
6
  import { str } from '../source';
7
- import { Syntax } from '../context';
7
+ import { Syntax, State } from '../context';
8
8
  import { startTight, blankWith } from '../visibility';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
  import { unshift } from 'spica/array';
11
11
 
12
12
  export const emphasis: EmphasisParser = lazy(() => surround(
13
13
  str('*'),
14
- syntax(Syntax.none, 1, 1,
14
+ syntax(Syntax.none, 1, 1, State.none,
15
15
  startTight(some(union([
16
16
  strong,
17
17
  some(inline, blankWith('*')),
@@ -5,7 +5,7 @@ import { inline } from '../inline';
5
5
  import { strong } from './strong';
6
6
  import { emphasis } from './emphasis';
7
7
  import { str } from '../source';
8
- import { Syntax } from '../context';
8
+ import { Syntax, State } from '../context';
9
9
  import { startTight, blankWith } from '../visibility';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
  import { unshift } from 'spica/array';
@@ -29,7 +29,7 @@ const subemphasis: IntermediateParser<EmphasisParser> = lazy(() => some(union([
29
29
 
30
30
  export const emstrong: EmStrongParser = lazy(() => surround(
31
31
  str('***'),
32
- syntax(Syntax.none, 1, 1,
32
+ syntax(Syntax.none, 1, 1, State.none,
33
33
  startTight(some(union([
34
34
  some(inline, blankWith('*')),
35
35
  open(some(inline, '*'), inline),
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { ExtensionParser } from '../../inline';
3
- import { union, some, syntax, creation, precedence, constraint, state, validate, surround, open, lazy, fmap } from '../../../combinator';
3
+ import { union, some, syntax, creation, precedence, constraint, validate, surround, open, lazy, fmap } from '../../../combinator';
4
4
  import { inline } from '../../inline';
5
5
  import { indexee, identity } from './indexee';
6
6
  import { txt, str, stropt } from '../../source';
@@ -13,13 +13,12 @@ import IndexParser = ExtensionParser.IndexParser;
13
13
  export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surround(
14
14
  '[#',
15
15
  constraint(State.index, false,
16
- state(State.annotation | State.reference | State.index | State.label | State.link | State.media | State.autolink,
17
- syntax(Syntax.index, 2, 1,
16
+ syntax(Syntax.index, 2, 1, State.linkable,
18
17
  startTight(
19
18
  open(stropt(/^\|?/), trimBlankEnd(some(union([
20
19
  signature,
21
20
  inline,
22
- ]), ']', [[/^\\?\n/, 9], [']', 2]])), true))))),
21
+ ]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))),
23
22
  ']',
24
23
  false,
25
24
  ([, ns], rest) => [[html('a', defrag(ns))], rest])),
@@ -2,7 +2,7 @@ import { ExtensionParser } from '../../inline';
2
2
  import { union, some, syntax, validate, surround, lazy } from '../../../combinator';
3
3
  import { inline } from '../../inline';
4
4
  import { str } from '../../source';
5
- import { Syntax } from '../../context';
5
+ import { Syntax, State } from '../../context';
6
6
  import { startTight } from '../../visibility';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
@@ -13,7 +13,7 @@ import { unshift } from 'spica/array';
13
13
 
14
14
  export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate(['[:', '[^'], surround(
15
15
  str(/^\[[:^]/),
16
- syntax(Syntax.none, 2, 1,
16
+ syntax(Syntax.none, 2, 1, State.none,
17
17
  startTight(some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]]))),
18
18
  str(']'), false,
19
19
  ([as, bs], rest) => [[
@@ -3,7 +3,7 @@ import { HTMLParser } from '../inline';
3
3
  import { union, subsequence, some, syntax, validate, focus, surround, open, match, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { Syntax } from '../context';
6
+ import { Syntax, State } from '../context';
7
7
  import { isStartLooseNodes, blankWith } from '../visibility';
8
8
  import { html as h, defrag } from 'typed-dom/dom';
9
9
  import { memoize } from 'spica/memoize';
@@ -19,7 +19,7 @@ const attrspecs = {
19
19
  Object.setPrototypeOf(attrspecs, null);
20
20
  Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
21
21
 
22
- export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, syntax(Syntax.none, 5, 1, union([
22
+ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, syntax(Syntax.none, 5, 1, State.none, union([
23
23
  focus(
24
24
  /^<wbr[^\S\n]*>/i,
25
25
  () => [[h('wbr')], '']),
@@ -2,14 +2,14 @@ import { InsertionParser } from '../inline';
2
2
  import { union, some, syntax, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
- import { Syntax } from '../context';
5
+ import { Syntax, State } from '../context';
6
6
  import { blankWith } from '../visibility';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
10
  export const insertion: InsertionParser = lazy(() => surround(
11
11
  str('++'),
12
- syntax(Syntax.none, 1, 1,
12
+ syntax(Syntax.none, 1, 1, State.none,
13
13
  some(union([
14
14
  some(inline, blankWith('\n', '++')),
15
15
  open('\n', some(inline, '+'), true),