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
@@ -1,7 +1,8 @@
1
1
  import { undefined, location, encodeURI, decodeURI, Location } from 'spica/global';
2
- import { LinkParser, TextLinkParser } from '../inline';
3
- import { eval, exec } from '../../combinator/data/parser';
4
- import { union, inits, tails, subsequence, some, constraint, syntax, creation, precedence, state, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
2
+ import { MarkdownParser } from '../../../markdown';
3
+ import { LinkParser } from '../inline';
4
+ import { Result, eval, exec } from '../../combinator/data/parser';
5
+ import { union, inits, tails, sequence, some, constraint, syntax, creation, precedence, state, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
6
  import { inline, media, shortmedia } from '../inline';
6
7
  import { attributes } from './html';
7
8
  import { autolink } from '../autolink';
@@ -17,82 +18,62 @@ const optspec = {
17
18
  } as const;
18
19
  Object.setPrototypeOf(optspec, null);
19
20
 
20
- export const link: LinkParser = lazy(() => validate(['[', '{'], bind(
21
+ export const link: LinkParser = lazy(() => validate(['[', '{'], union([
22
+ medialink,
23
+ textlink,
24
+ ])));
25
+
26
+ const textlink: LinkParser.TextLinkParser = lazy(() =>
21
27
  constraint(State.link, false,
22
- creation(10,
23
- fmap(subsequence([
24
- state(State.link,
25
- dup(union([
26
- surround('[', media, ']'),
27
- surround('[', shortmedia, ']'),
28
- surround(
29
- '[',
30
- state(State.annotation | State.reference | State.index | State.label | State.media | State.autolink,
31
- syntax(Syntax.link, 2, 0,
32
- some(inline, ']', [[/^\\?\n/, 9], [']', 2]]))),
33
- ']',
34
- true),
35
- ]))),
28
+ syntax(Syntax.link, 2, 10, State.linkable,
29
+ bind(reverse(tails([
30
+ dup(surround(
31
+ '[',
32
+ some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]]),
33
+ ']',
34
+ true)),
36
35
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
37
- ], nodes => nodes[0][0] !== ''),
38
- ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []]))),
39
- ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
40
- assert(content[0] !== '' || params.length === 0);
41
- if (content[0] === '') return [content, rest];
42
- if (params.length === 0) return;
43
- assert(params.every(p => typeof p === 'string'));
36
+ ])),
37
+ ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
38
+ assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
44
39
  if (content.length !== 0 && trimNode(content).length === 0) return;
45
40
  for (let source = stringify(content); source;) {
46
- const result = autolink(source, context);
41
+ const result = state(State.autolink, false, autolink)(source, context);
47
42
  if (typeof eval(result!)[0] === 'object') return;
48
43
  source = exec(result!);
49
44
  }
50
- assert(!html('div', content).querySelector('a, .media, .annotation, .reference') || (content[0] as HTMLElement).matches('.media'));
51
- const INSECURE_URI = params.shift()!;
52
- assert(INSECURE_URI === INSECURE_URI.trim());
53
- assert(!INSECURE_URI.match(/\s/));
54
- const el = elem(
55
- INSECURE_URI,
56
- defrag(content),
57
- new ReadonlyURL(
58
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
59
- context.host?.href || location.href),
60
- context.host?.origin || location.origin);
61
- if (el.className === 'invalid') return [[el], rest];
62
- assert(el.classList.length === 0);
63
- return [[define(el, attributes('link', [], optspec, params))], rest];
64
- })));
45
+ return parse(content, params, rest, context);
46
+ }))));
47
+
48
+ const medialink: LinkParser.MediaLinkParser = lazy(() =>
49
+ constraint(State.link | State.media, false,
50
+ syntax(Syntax.link, 2, 10, State.linkable ^ State.media,
51
+ bind(reverse(sequence([
52
+ dup(surround(
53
+ '[',
54
+ union([media, shortmedia]),
55
+ ']')),
56
+ dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
57
+ ])),
58
+ ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) =>
59
+ parse(content, params, rest, context)))));
65
60
 
66
- export const textlink: TextLinkParser = lazy(() => validate(['[', '{'], bind(
61
+ export const unsafelink: LinkParser.UnsafeLinkParser = lazy(() =>
67
62
  creation(10, precedence(2,
68
- reverse(tails([
69
- dup(surround('[', some(union([unescsource]), ']'), ']')),
63
+ bind(reverse(tails([
64
+ dup(surround(
65
+ '[',
66
+ some(union([unescsource]), ']'),
67
+ ']')),
70
68
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
71
- ])))),
72
- ([params, content = []], rest, context) => {
73
- assert(params[0] === '\r');
74
- params.shift();
75
- assert(params.every(p => typeof p === 'string'));
76
- trimNode(content);
77
- const INSECURE_URI = params.shift()!;
78
- assert(INSECURE_URI === INSECURE_URI.trim());
79
- assert(!INSECURE_URI.match(/\s/));
80
- const el = elem(
81
- INSECURE_URI,
82
- defrag(content),
83
- new ReadonlyURL(
84
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
85
- context.host?.href || location.href),
86
- context.host?.origin || location.origin);
87
- assert(el.className !== 'invalid');
88
- assert(el.classList.length === 0);
89
- return [[define(el, attributes('link', [], optspec, params))], rest];
90
- })));
69
+ ])),
70
+ ([params, content = []], rest, context) =>
71
+ parse(content, params, rest, context)))));
91
72
 
92
- export const uri: LinkParser.ParameterParser.UriParser = fmap(union([
73
+ export const uri: LinkParser.ParameterParser.UriParser = union([
93
74
  open(/^[^\S\n]+/, str(/^\S+/)),
94
75
  str(/^[^\s{}]+/),
95
- ]), ([uri]) => ['\r', uri]);
76
+ ]);
96
77
 
97
78
  export const option: LinkParser.ParameterParser.OptionParser = union([
98
79
  fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
@@ -100,6 +81,29 @@ export const option: LinkParser.ParameterParser.OptionParser = union([
100
81
  fmap(str(/^[^\S\n]+[^\s{}]+/), opt => [` \\${opt.slice(1)}`]),
101
82
  ]);
102
83
 
84
+ function parse(
85
+ content: (string | HTMLElement)[],
86
+ params: string[],
87
+ rest: string,
88
+ context: MarkdownParser.Context,
89
+ ): Result<HTMLAnchorElement, MarkdownParser.Context> {
90
+ assert(params.length > 0);
91
+ assert(params.every(p => typeof p === 'string'));
92
+ const INSECURE_URI = params.shift()!;
93
+ assert(INSECURE_URI === INSECURE_URI.trim());
94
+ assert(!INSECURE_URI.match(/\s/));
95
+ const el = elem(
96
+ INSECURE_URI,
97
+ defrag(content),
98
+ new ReadonlyURL(
99
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
100
+ context.host?.href || location.href),
101
+ context.host?.origin || location.origin);
102
+ if (el.className === 'invalid') return [[el], rest];
103
+ assert(el.classList.length === 0);
104
+ return [[define(el, attributes('link', [], optspec, params))], rest];
105
+ }
106
+
103
107
  export function resolve(uri: string, host: URL | Location, source: URL | Location): string {
104
108
  assert(uri);
105
109
  assert(uri === uri.trim());
@@ -3,13 +3,13 @@ import { union, some, syntax, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { startTight, blankWith } from '../visibility';
6
- import { Syntax } from '../context';
6
+ import { Syntax, State } from '../context';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
10
  export const mark: MarkParser = lazy(() => surround(
11
11
  str('=='),
12
- syntax(Syntax.none, 1, 1,
12
+ syntax(Syntax.none, 1, 1, State.none,
13
13
  startTight(some(union([
14
14
  some(inline, blankWith('==')),
15
15
  open(some(inline, '='), mark),
@@ -1,14 +1,14 @@
1
1
  import { undefined, location } from 'spica/global';
2
2
  import { MediaParser } from '../inline';
3
3
  import { union, inits, tails, some, syntax, creation, precedence, constraint, validate, verify, surround, open, dup, lazy, fmap, bind } from '../../combinator';
4
- import { textlink, uri, option as linkoption, resolve } from './link';
4
+ import { unsafelink, uri, option as linkoption, resolve } from './link';
5
5
  import { attributes } from './html';
6
6
  import { unsafehtmlentity } from './htmlentity';
7
7
  import { txt, str } from '../source';
8
8
  import { Syntax, State } from '../context';
9
9
  import { html, define } from 'typed-dom/dom';
10
10
  import { ReadonlyURL } from 'spica/url';
11
- import { unshift, shift, push } from 'spica/array';
11
+ import { unshift, push } from 'spica/array';
12
12
 
13
13
  const optspec = {
14
14
  'width': [],
@@ -18,19 +18,19 @@ const optspec = {
18
18
  } as const;
19
19
  Object.setPrototypeOf(optspec, null);
20
20
 
21
- export const media: MediaParser = lazy(() => validate(['![', '!{'], bind(verify(fmap(open(
21
+ export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
22
22
  '!',
23
23
  constraint(State.media, false,
24
- syntax(Syntax.media, 2, 10,
25
- tails([
24
+ syntax(Syntax.media, 2, 10, State.none,
25
+ bind(verify(fmap(tails([
26
26
  dup(surround(
27
27
  '[',
28
28
  some(union([unsafehtmlentity, bracket, txt]), ']', [[/^\\?\n/, 9]]),
29
29
  ']',
30
30
  true)),
31
31
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
32
- ])))),
33
- ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], shift(bs)[1]] : [[''], shift(as)[1]]),
32
+ ]),
33
+ ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]),
34
34
  ([[text]]) => text === '' || text.trim() !== ''),
35
35
  ([[text], params], rest, context) => {
36
36
  assert(text === text.trim());
@@ -54,12 +54,13 @@ export const media: MediaParser = lazy(() => validate(['![', '!{'], bind(verify(
54
54
  if (el.hasAttribute('aspect-ratio')) {
55
55
  el.style.aspectRatio = el.getAttribute('aspect-ratio')!;
56
56
  }
57
- if (context.state! & State.link || cache && cache.tagName !== 'IMG') return [[el], rest];
57
+ if (context.state! & State.link) return [[el], rest];
58
+ if (cache && cache.tagName !== 'IMG') return creation(10, (..._) => [[el!], rest])('!', context);
58
59
  return fmap(
59
- textlink as MediaParser,
60
+ unsafelink as MediaParser,
60
61
  ([link]) => [define(link, { target: '_blank' }, [el])])
61
62
  (`{ ${INSECURE_URI}${params.join('')} }${rest}`, context);
62
- })));
63
+ }))))));
63
64
 
64
65
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => creation(union([
65
66
  surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { ReferenceParser } from '../inline';
3
- import { union, subsequence, some, context, syntax, creation, constraint, state, surround, open, lazy, bind } from '../../combinator';
3
+ import { union, subsequence, some, context, syntax, creation, constraint, surround, open, lazy, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str, stropt } from '../source';
6
6
  import { Syntax, State } from '../context';
@@ -11,15 +11,14 @@ import { html, defrag } from 'typed-dom/dom';
11
11
  export const reference: ReferenceParser = lazy(() => surround(
12
12
  '[[',
13
13
  constraint(State.reference, false,
14
- state(State.annotation | State.reference | State.media,
15
- syntax(Syntax.reference, 6, 1,
14
+ syntax(Syntax.reference, 6, 1, State.annotation | State.reference | State.media,
16
15
  startLoose(
17
16
  context({ delimiters: undefined },
18
17
  subsequence([
19
18
  abbr,
20
19
  open(stropt(/^(?=\^)/), some(inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])),
21
20
  some(inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]]),
22
- ])), ']')))),
21
+ ])), ']'))),
23
22
  ']]',
24
23
  false,
25
24
  ([, ns], rest) => [[html('sup', attributes(ns), [html('span', trimNode(defrag(ns)))])], rest]));
@@ -4,12 +4,12 @@ import { eval, exec } from '../../combinator/data/parser';
4
4
  import { sequence, syntax, creation, validate, verify, focus, surround, lazy, fmap } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
6
  import { text as txt } from '../source';
7
- import { Syntax } from '../context';
7
+ import { Syntax, State } from '../context';
8
8
  import { isStartTightNodes } from '../visibility';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
  import { unshift, push } from 'spica/array';
11
11
 
12
- export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.none, 2, 1, fmap(verify(
12
+ export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.none, 2, 1, State.none, fmap(verify(
13
13
  sequence([
14
14
  surround('[', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=]\()/, text), ']'),
15
15
  surround('(', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=\))/, text), ')'),
@@ -3,14 +3,14 @@ import { union, some, syntax, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { emstrong } from './emstrong';
5
5
  import { str } from '../source';
6
- import { Syntax } from '../context';
6
+ import { Syntax, State } from '../context';
7
7
  import { startTight, blankWith } from '../visibility';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
  import { unshift } from 'spica/array';
10
10
 
11
11
  export const strong: StrongParser = lazy(() => surround(
12
12
  str('**'),
13
- syntax(Syntax.none, 1, 1,
13
+ syntax(Syntax.none, 1, 1, State.none,
14
14
  startTight(some(union([
15
15
  some(inline, blankWith('**')),
16
16
  open(some(inline, '*'), union([
@@ -2,12 +2,12 @@ import { undefined } from 'spica/global';
2
2
  import { TemplateParser } from '../inline';
3
3
  import { union, some, syntax, creation, precedence, surround, lazy } from '../../combinator';
4
4
  import { escsource, str } from '../source';
5
- import { Syntax } from '../context';
5
+ import { Syntax, State } from '../context';
6
6
  import { html } from 'typed-dom/dom';
7
7
  import { unshift } from 'spica/array';
8
8
 
9
9
  export const template: TemplateParser = lazy(() => surround(
10
- '{{', syntax(Syntax.none, 2, 1, some(union([bracket, escsource]), '}')), '}}', true,
10
+ '{{', syntax(Syntax.none, 2, 1, State.none, some(union([bracket, escsource]), '}')), '}}', true,
11
11
  ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
12
12
 
13
13
  const bracket: TemplateParser.BracketParser = lazy(() => creation(union([
@@ -34,7 +34,6 @@ export import MathParser = InlineParser.MathParser;
34
34
  export import ExtensionParser = InlineParser.ExtensionParser;
35
35
  export import RubyParser = InlineParser.RubyParser;
36
36
  export import LinkParser = InlineParser.LinkParser;
37
- export import TextLinkParser = InlineParser.TextLinkParser;
38
37
  export import HTMLParser = InlineParser.HTMLParser;
39
38
  export import InsertionParser = InlineParser.InsertionParser;
40
39
  export import DeletionParser = InlineParser.DeletionParser;
@@ -2,7 +2,7 @@ import { undefined } from 'spica/global';
2
2
  import { TextParser, TxtParser, LinebreakParser } from '../source';
3
3
  import { union, syntax, focus } from '../../combinator';
4
4
  import { str } from './str';
5
- import { Syntax } from '../context';
5
+ import { Syntax, State } from '../context';
6
6
  import { html } from 'typed-dom/dom';
7
7
 
8
8
  export const delimiter = /[\s\x00-\x7F]|\S[#>]|[()、。!?][^\S\n]*(?=\\\n)/;
@@ -10,7 +10,7 @@ export const nonWhitespace = /[\S\n]|$/;
10
10
  export const nonAlphanumeric = /[^0-9A-Za-z]|\S[#>]|$/;
11
11
  const repeat = str(/^(.)\1*/);
12
12
 
13
- export const text: TextParser = syntax(Syntax.none, 1, 1, (source, context) => {
13
+ export const text: TextParser = syntax(Syntax.none, 1, 1, State.none, (source, context) => {
14
14
  if (source === '') return;
15
15
  const i = source.search(delimiter);
16
16
  switch (i) {