securemark 0.237.0 → 0.238.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 (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/securemark.js +179 -102
  3. package/markdown.d.ts +1 -1
  4. package/package-lock.json +16 -16
  5. package/package.json +1 -1
  6. package/src/debug.test.ts +1 -1
  7. package/src/parser/block/codeblock.test.ts +5 -2
  8. package/src/parser/block/codeblock.ts +43 -25
  9. package/src/parser/block/extension/aside.ts +1 -1
  10. package/src/parser/block/extension/example.ts +1 -1
  11. package/src/parser/block/extension/fig.test.ts +1 -1
  12. package/src/parser/block/extension/fig.ts +1 -1
  13. package/src/parser/block/extension/figure.test.ts +2 -0
  14. package/src/parser/block/extension/figure.ts +22 -25
  15. package/src/parser/block/extension/message.ts +1 -1
  16. package/src/parser/block/extension/placeholder.ts +1 -1
  17. package/src/parser/block/extension/table.ts +1 -1
  18. package/src/parser/block/heading.test.ts +1 -1
  19. package/src/parser/block/heading.ts +10 -4
  20. package/src/parser/block/mathblock.ts +1 -1
  21. package/src/parser/block.ts +2 -2
  22. package/src/parser/inline/comment.ts +1 -1
  23. package/src/parser/inline/extension/index.test.ts +19 -13
  24. package/src/parser/inline/extension/index.ts +4 -3
  25. package/src/parser/inline/htmlentity.ts +2 -2
  26. package/src/parser/inline/media.ts +2 -2
  27. package/src/parser/inline/ruby.ts +2 -2
  28. package/src/parser/processor/figure.test.ts +44 -18
  29. package/src/parser/processor/figure.ts +68 -24
  30. package/src/parser/processor/footnote.ts +1 -1
  31. package/src/parser/segment.test.ts +2 -2
  32. package/src/parser/segment.ts +2 -2
  33. package/src/parser/source/str.ts +23 -4
  34. package/src/parser/source/text.ts +3 -0
  35. package/src/parser/source.ts +1 -1
  36. package/src/parser/util.ts +2 -2
  37. package/src/util/quote.ts +1 -0
package/markdown.d.ts CHANGED
@@ -388,7 +388,7 @@ export namespace MarkdownParser {
388
388
  export interface SegmentParser extends
389
389
  Block<'extension/figure/segment'>,
390
390
  Parser<never, Context, [
391
- InlineParser.ExtensionParser.LabelParser.SegmentParser,
391
+ SourceParser.ContentLineParser,
392
392
  Parser<never, Context, [
393
393
  Parser<never, Context, [
394
394
  CodeBlockParser.SegmentParser,
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.237.0",
3
+ "version": "0.238.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -1923,9 +1923,9 @@
1923
1923
  "dev": true
1924
1924
  },
1925
1925
  "caniuse-lite": {
1926
- "version": "1.0.30001324",
1927
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001324.tgz",
1928
- "integrity": "sha512-/eYp1J6zYh1alySQB4uzYFkLmxxI8tk0kxldbNHXp8+v+rdMKdUBNjRLz7T7fz6Iox+1lIdYpc7rq6ZcXfTukg==",
1926
+ "version": "1.0.30001325",
1927
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz",
1928
+ "integrity": "sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==",
1929
1929
  "dev": true
1930
1930
  },
1931
1931
  "chalk": {
@@ -4915,9 +4915,9 @@
4915
4915
  }
4916
4916
  },
4917
4917
  "graceful-fs": {
4918
- "version": "4.2.9",
4919
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
4920
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
4918
+ "version": "4.2.10",
4919
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
4920
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
4921
4921
  "dev": true
4922
4922
  },
4923
4923
  "growl": {
@@ -8018,15 +8018,15 @@
8018
8018
  }
8019
8019
  },
8020
8020
  "npm-check-updates": {
8021
- "version": "12.5.5",
8022
- "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-12.5.5.tgz",
8023
- "integrity": "sha512-7LH6KN6F1fZMtY4zNYAQPpJU1ToxZ6sSCxk948vrLIz97aNqmPLSX72MrmbOWwpyBgLCPbFJWY/k3zE18pmxfw==",
8021
+ "version": "12.5.7",
8022
+ "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-12.5.7.tgz",
8023
+ "integrity": "sha512-WDkqPBevmgphV1UH3FImsDEpTAq2UFvnMZC3GdXPknE2VL701kfKylnae8IA9ZeYfm/uU0249N9gjMXqA/9y3w==",
8024
8024
  "dev": true,
8025
8025
  "requires": {
8026
8026
  "chalk": "^4.1.2",
8027
8027
  "cint": "^8.2.1",
8028
8028
  "cli-table": "^0.3.11",
8029
- "commander": "^9.0.0",
8029
+ "commander": "^9.1.0",
8030
8030
  "fast-memoize": "^2.5.2",
8031
8031
  "find-up": "5.0.0",
8032
8032
  "fp-and-or": "^0.1.3",
@@ -8039,11 +8039,11 @@
8039
8039
  "lodash": "^4.17.21",
8040
8040
  "minimatch": "^5.0.1",
8041
8041
  "p-map": "^4.0.0",
8042
- "pacote": "^13.0.3",
8042
+ "pacote": "^13.0.5",
8043
8043
  "parse-github-url": "^1.0.2",
8044
8044
  "progress": "^2.0.3",
8045
8045
  "prompts": "^2.4.2",
8046
- "rc-config-loader": "^4.0.0",
8046
+ "rc-config-loader": "^4.1.0",
8047
8047
  "remote-git-tags": "^3.0.0",
8048
8048
  "rimraf": "^3.0.2",
8049
8049
  "semver": "^7.3.5",
@@ -9124,9 +9124,9 @@
9124
9124
  }
9125
9125
  },
9126
9126
  "rc-config-loader": {
9127
- "version": "4.0.0",
9128
- "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.0.0.tgz",
9129
- "integrity": "sha512-//LRTblJEcqbmmro1GCmZ39qZXD+JqzuD8Y5/IZU3Dhp3A1Yr0Xn68ks8MQ6qKfKvYCWDveUmRDKDA40c+sCXw==",
9127
+ "version": "4.1.0",
9128
+ "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.0.tgz",
9129
+ "integrity": "sha512-aW+kX4qy0CiM9L4fG4Us3oEOpIrOrXzWykAn+xldD07Y9PXWjTH744oHbv0Kc9ZwWaylw3jMjxaf14RgStrNrA==",
9130
9130
  "dev": true,
9131
9131
  "requires": {
9132
9132
  "debug": "^4.1.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.237.0",
3
+ "version": "0.238.0",
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",
package/src/debug.test.ts CHANGED
@@ -19,7 +19,7 @@ export function inspect(result: Result<HTMLElement | string>, until: number | st
19
19
  ? until
20
20
  : ~(~node.outerHTML.indexOf(until) || -Infinity) + until.length;
21
21
  const el = html('div');
22
- assert(!node.outerHTML.match(/[\0\x7F]/));
22
+ assert(!node.outerHTML.match(/[\x00-\x08\x0B-\x1F\x7F]/));
23
23
  el.innerHTML = node.outerHTML.slice(0, until);
24
24
  if (node.outerHTML.length <= until) {
25
25
  assert(node.outerHTML === el.innerHTML);
@@ -47,7 +47,7 @@ describe('Unit: parser/block/codeblock', () => {
47
47
  it('attribute', () => {
48
48
  assert.deepStrictEqual(inspect(parser('```0\n```')), [['<pre class="code language-0" translate="no" data-lang="0"></pre>'], '']);
49
49
  assert.deepStrictEqual(inspect(parser('```a\n```')), [['<pre class="code language-a" translate="no" data-lang="a"></pre>'], '']);
50
- assert.deepStrictEqual(inspect(parser('```A\n```')), [['<pre class="text" translate="no" data-path="A"></pre>'], '']);
50
+ assert.deepStrictEqual(inspect(parser('```A\n```')), [['<pre class="code language-a" translate="no" data-lang="a"></pre>'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('```a-b\n```')), [['<pre class="code language-a-b" translate="no" data-lang="a-b"></pre>'], '']);
52
52
  assert.deepStrictEqual(inspect(parser('```a-b0\n```')), [['<pre class="code language-a-b0" translate="no" data-lang="a-b0"></pre>'], '']);
53
53
  assert.deepStrictEqual(inspect(parser('```a--b\n```')), [['<pre class="text" translate="no" data-path="a--b"></pre>'], '']);
@@ -62,7 +62,10 @@ describe('Unit: parser/block/codeblock', () => {
62
62
  assert.deepStrictEqual(inspect(parser('```.b\n```')), [['<pre class="text" translate="no" data-path=".b"></pre>'], '']);
63
63
  assert.deepStrictEqual(inspect(parser('```b.c\n```')), [['<pre class="code language-c" translate="no" data-lang="c" data-path="b.c"></pre>'], '']);
64
64
  assert.deepStrictEqual(inspect(parser('```a b.c\n```')), [['<pre class="code language-a" translate="no" data-lang="a" data-path="b.c"></pre>'], '']);
65
- assert.deepStrictEqual(inspect(parser('```A b.c\n```')), [['<pre class="invalid" translate="no">```A b.c\n```</pre>'], '']);
65
+ assert.deepStrictEqual(inspect(parser('```a 1\n```')), [['<pre class="code language-a" translate="no" data-lang="a" data-line="1"></pre>'], '']);
66
+ assert.deepStrictEqual(inspect(parser('``` 1\n```')), [['<pre class="text" translate="no" data-line="1"></pre>'], '']);
67
+ assert.deepStrictEqual(inspect(parser('``` 1,2-3\n```')), [['<pre class="text" translate="no" data-line="1,2-3"></pre>'], '']);
68
+ assert.deepStrictEqual(inspect(parser('``` 1 b.c\n```')), [['<pre class="code language-c" translate="no" data-lang="c" data-line="1" data-path="b.c"></pre>'], '']);
66
69
  });
67
70
 
68
71
  });
@@ -3,12 +3,10 @@ import { CodeBlockParser } from '../block';
3
3
  import { eval } from '../../combinator/data/parser';
4
4
  import { some, block, validate, fence, clear, fmap } from '../../combinator';
5
5
  import { autolink } from '../autolink';
6
- import { escsource } from '../source';
7
6
  import { html, defrag } from 'typed-dom';
8
- import { join } from 'spica/array';
9
7
 
10
- const opener = /^(`{3,})(?!`)(\S*)([^\n]*)(?:$|\n)/;
11
- const language = /^[0-9a-z]+(?:-[a-z][0-9a-z]*)*$/;
8
+ const opener = /^(`{3,})(?!`)([^\n]*)(?:$|\n)/;
9
+ const language = /^[0-9a-z]+(?:-[a-z][0-9a-z]*)*$/i;
12
10
 
13
11
  export const segment: CodeBlockParser.SegmentParser = block(validate('```',
14
12
  clear(fence(opener, 300))));
@@ -19,35 +17,55 @@ export const segment_: CodeBlockParser.SegmentParser = block(validate('```',
19
17
  export const codeblock: CodeBlockParser = block(validate('```', fmap(
20
18
  fence(opener, 300),
21
19
  // Bug: Type mismatch between outer and inner.
22
- ([body, closer, opener, delim, lang, param]: string[], _, context) => {
23
- [lang, param] = language.test(lang)
24
- ? [lang, param]
25
- : ['', lang + param];
26
- param = param.trim();
27
- const path = join(eval(some(escsource, /^\s/)(param, context), []));
28
- if (!closer || param !== path) return [html('pre', {
20
+ ([body, closer, opener, delim, param]: string[], _, context) => {
21
+ const params = param.match(/(?:\\.?|\S)+/g)?.reduce<{
22
+ lang?: string;
23
+ path?: string;
24
+ line?: string;
25
+ invalid?: string;
26
+ }>((params, value, i) => {
27
+ let name: string;
28
+ switch (true) {
29
+ case i === 0
30
+ && value[0] === param[0]
31
+ && language.test(value):
32
+ name = 'lang';
33
+ value = value.toLowerCase();
34
+ break;
35
+ case /^\d+(?:[,-]\d+)*$/.test(value):
36
+ name = 'line';
37
+ break;
38
+ default:
39
+ name = 'path';
40
+ if (!params.lang) {
41
+ const file = value.split('/').pop() ?? '';
42
+ params.lang = file && file.includes('.', 1)
43
+ ? file.split('.').pop()?.match(language)?.[0].toLowerCase()
44
+ : params.lang;
45
+ }
46
+ }
47
+ name in params
48
+ ? params.invalid = `Duplicate ${name} value`
49
+ : params[name] = value;
50
+ return params;
51
+ }, {}) ?? {};
52
+ if (!closer || params.invalid) return [html('pre', {
29
53
  class: 'invalid',
30
54
  translate: 'no',
31
55
  'data-invalid-syntax': 'codeblock',
32
- 'data-invalid-type': !closer ? 'closer' : 'argument',
33
- 'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
56
+ 'data-invalid-type': !closer ? 'fence' : 'argument',
57
+ 'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : params.invalid,
34
58
  }, `${opener}${body}${closer}`)];
35
- const file = path.split('/').pop() ?? '';
36
- const ext = file && file.includes('.', 1)
37
- ? file.split('.').pop()!
38
- : '';
39
- lang = language.test(lang || ext)
40
- ? lang || ext
41
- : lang && 'invalid';
42
59
  const el = html('pre',
43
60
  {
44
- class: lang ? `code language-${lang}` : 'text',
61
+ class: params.lang ? `code language-${params.lang}` : 'text',
45
62
  translate: 'no',
46
- 'data-lang': lang || undefined,
47
- 'data-path': path || undefined,
63
+ 'data-lang': params.lang || undefined,
64
+ 'data-line': params.line || undefined,
65
+ 'data-path': params.path || undefined,
48
66
  },
49
- lang
50
- ? context.caches?.code?.get(`${lang}\n${body.slice(0, -1)}`)?.cloneNode(true).childNodes ||
67
+ params.lang
68
+ ? context.caches?.code?.get(`${params.lang ?? ''}\n${body.slice(0, -1)}`)?.cloneNode(true).childNodes ||
51
69
  body.slice(0, -1) || undefined
52
70
  : defrag(eval(some(autolink)(body.slice(0, -1), context), [])));
53
71
  return [el];
@@ -12,7 +12,7 @@ export const aside: ExtensionParser.AsideParser = creator(100, block(validate('~
12
12
  class: 'invalid',
13
13
  translate: 'no',
14
14
  'data-invalid-syntax': 'aside',
15
- 'data-invalid-type': !closer ? 'closer' : 'argument',
15
+ 'data-invalid-type': !closer ? 'fence' : 'argument',
16
16
  'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
17
17
  }, `${opener}${body}${closer}`)];
18
18
  const annotations = html('ol', { class: 'annotations' });
@@ -15,7 +15,7 @@ export const example: ExtensionParser.ExampleParser = creator(100, block(validat
15
15
  class: 'invalid',
16
16
  translate: 'no',
17
17
  'data-invalid-syntax': 'example',
18
- 'data-invalid-type': !closer ? 'closer' : 'argument',
18
+ 'data-invalid-type': !closer ? 'fence' : 'argument',
19
19
  'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
20
20
  }, `${opener}${body}${closer}`)];
21
21
  switch (type) {
@@ -14,7 +14,7 @@ describe('Unit: parser/block/extension/fig', () => {
14
14
  assert.deepStrictEqual(inspect(parser('[$group-name]\n !https://host')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('[$group-name]\n\n!https://host')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('[$group-name]a\nhttps://host')), undefined);
17
- assert.deepStrictEqual(inspect(parser('[$group-name]a\n!https://host')), [['<figure data-type="media" data-label="group-name" data-group="group" class="invalid"><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div><figcaption><span class="figindex"></span></figcaption></figure>'], '']);
17
+ assert.deepStrictEqual(inspect(parser('[$group-name]a\n!https://host')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('[$group-name] a\nhttps://host')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('[$group-name] a\n!https://host')), [['<figure data-type="media" data-label="group-name" data-group="group" class="invalid"><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div><figcaption><span class="figindex"></span></figcaption></figure>'], '']);
20
20
  assert.deepStrictEqual(inspect(parser('$-a\n$-b')), undefined);
@@ -13,7 +13,7 @@ import FigParser = ExtensionParser.FigParser;
13
13
 
14
14
  export const segment: FigParser.SegmentParser = block(validate(['[$', '$'],
15
15
  sequence([
16
- line(close(seg_label, /^.*\n/)),
16
+ line(close(seg_label, /^(?=\s).*\n/)),
17
17
  union([
18
18
  seg_code,
19
19
  seg_math,
@@ -24,6 +24,8 @@ describe('Unit: parser/block/extension/figure', () => {
24
24
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name]a\n!https://host\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group" class="invalid"><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div><figcaption><span class="figindex"></span></figcaption></figure>'], '']);
25
25
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name] a\nhttps://host\n~~~')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('~~~figure [$group-name] a\n!https://host\n~~~')), [['<figure data-type="media" data-label="group-name" data-group="group" class="invalid"><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div><figcaption><span class="figindex"></span></figcaption></figure>'], '']);
27
+ assert.deepStrictEqual(inspect(parser('~~~figure a[$group-name]\n!https://host\n~~~')), undefined);
28
+ assert.deepStrictEqual(inspect(parser('~~~figure a [$group-name]\n!https://host\n~~~')), undefined);
27
29
  assert.deepStrictEqual(inspect(parser('~~~ [$group-name]\n!https://host\n~~~')), undefined);
28
30
  assert.deepStrictEqual(inspect(parser('~~~ $group-name\n!https://host\n~~~')), undefined);
29
31
  assert.deepStrictEqual(inspect(parser(' ~~~figure [$group-name]\n!https://host\n~~~')), undefined);
@@ -2,7 +2,7 @@ import { undefined } from 'spica/global';
2
2
  import { ExtensionParser } from '../../block';
3
3
  import { union, inits, sequence, some, block, line, rewrite, context, close, match, convert, trim, fmap } from '../../../combinator';
4
4
  import { str, contentline, emptyline } from '../../source';
5
- import { label, segment as seg_label } from '../../inline/extension/label';
5
+ import { label } from '../../inline/extension/label';
6
6
  import { ulist } from '../ulist';
7
7
  import { olist } from '../olist';
8
8
  import { table as styled_table } from '../table';
@@ -22,12 +22,12 @@ import { unshift } from 'spica/array';
22
22
  import FigureParser = ExtensionParser.FigureParser;
23
23
 
24
24
  export const segment: FigureParser.SegmentParser = block(match(
25
- /^(~{3,})(?:figure[^\S\n]+)?(?=\[?\$[A-Za-z-][^\n]*\n(?:[^\n]*\n)*?\1[^\S\n]*(?:$|\n))/,
25
+ /^(~{3,})(?:figure[^\S\n]+)?(?=\[?\$[A-Za-z-][^\n]*\n)/,
26
26
  memoize(
27
27
  ([, fence], closer = new RegExp(String.raw`^${fence}[^\S\n]*(?:$|\n)`)) =>
28
28
  close(
29
29
  sequence([
30
- line(close(seg_label, /^.*\n/)),
30
+ contentline,
31
31
  inits([
32
32
  // All parsers which can include closing terms.
33
33
  union([
@@ -85,10 +85,6 @@ export const figure: FigureParser = block(rewrite(segment, fmap(
85
85
 
86
86
  function attributes(label: string, param: string, content: HTMLElement, caption: readonly HTMLElement[]): Record<string, string | undefined> {
87
87
  const group = label.split('-', 1)[0];
88
- const invalidLabel = /^[^-]+-(?:[0-9]+\.)*0$/.test(label);
89
- const invalidParam = param.trimStart() !== '';
90
- const invalidContent = group === '$' && (!content.classList.contains('math') || caption.length > 0);
91
- const invalid = invalidLabel || invalidParam || invalidContent || undefined;
92
88
  let type: string = content.className.split(/\s/)[0];
93
89
  switch (type || content.tagName) {
94
90
  case 'UL':
@@ -113,27 +109,28 @@ function attributes(label: string, param: string, content: HTMLElement, caption:
113
109
  default:
114
110
  assert(false);
115
111
  }
112
+ const invalid =
113
+ /^[^-]+-(?:[0-9]+\.)*0$/.test(label) && {
114
+ 'data-invalid-type': 'label',
115
+ 'data-invalid-message': 'The last part of the fixed label numbers must not be 0',
116
+ } ||
117
+ param.trimStart() !== '' && {
118
+ 'data-invalid-type': 'argument',
119
+ 'data-invalid-message': 'Invalid argument',
120
+ } ||
121
+ group === '$' && (!content.classList.contains('math') || caption.length > 0) && {
122
+ 'data-invalid-type': 'content',
123
+ 'data-invalid-message': 'A figure labeled to define a formula number can contain only a math formula and no caption',
124
+ } ||
125
+ undefined;
116
126
  return {
117
127
  'data-type': type,
118
128
  'data-label': label,
119
129
  'data-group': group,
120
- class: invalid && 'invalid',
121
- ...
122
- invalidLabel && {
123
- 'data-invalid-syntax': 'figure',
124
- 'data-invalid-type': 'label',
125
- 'data-invalid-message': 'The last part of the fixed label numbers must not be 0',
126
- } ||
127
- invalidParam && {
128
- 'data-invalid-syntax': 'figure',
129
- 'data-invalid-type': 'argument',
130
- 'data-invalid-message': 'Invalid argument',
131
- } ||
132
- invalidContent && {
133
- 'data-invalid-syntax': 'figure',
134
- 'data-invalid-type': 'content',
135
- 'data-invalid-message': 'A figure labeled to define a formula number can contain only a math formula and no caption',
136
- } ||
137
- undefined,
130
+ ...invalid && {
131
+ class: 'invalid',
132
+ 'data-invalid-syntax': 'figure',
133
+ ...invalid,
134
+ },
138
135
  };
139
136
  }
@@ -24,7 +24,7 @@ export const message: MessageParser = block(validate('~~~', fmap(
24
24
  class: 'invalid',
25
25
  translate: 'no',
26
26
  'data-invalid-syntax': 'message',
27
- 'data-invalid-type': !closer ? 'closer' : 'argument',
27
+ 'data-invalid-type': !closer ? 'fence' : 'argument',
28
28
  'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
29
29
  }, `${opener}${body}${closer}`)];
30
30
  switch (type) {
@@ -17,7 +17,7 @@ export const placeholder: ExtensionParser.PlaceholderParser = block(validate('~~
17
17
  class: 'invalid',
18
18
  translate: 'no',
19
19
  'data-invalid-syntax': 'extension',
20
- 'data-invalid-type': !closer ? 'closer' : 'syntax',
20
+ 'data-invalid-type': !closer ? 'fence' : 'syntax',
21
21
  'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid syntax',
22
22
  }, `${opener}${body}${closer}`)
23
23
  ])));
@@ -31,7 +31,7 @@ export const table: TableParser = block(validate('~~~', recover(fmap(
31
31
  class: 'invalid',
32
32
  translate: 'no',
33
33
  'data-invalid-syntax': 'table',
34
- 'data-invalid-type': !closer ? 'closer' : 'argument',
34
+ 'data-invalid-type': !closer ? 'fence' : 'argument',
35
35
  'data-invalid-message': !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
36
36
  }, `${opener}${body}${closer}`)];
37
37
  return eval(parser(body, context)) ?? [html('table')];
@@ -27,7 +27,7 @@ describe('Unit: parser/block/heading', () => {
27
27
  assert.deepStrictEqual(inspect(parser('# a\nb')), undefined);
28
28
  assert.deepStrictEqual(inspect(parser('# *a\nb*')), undefined);
29
29
  assert.deepStrictEqual(inspect(parser('# a\n#b')), undefined);
30
- assert.deepStrictEqual(inspect(parser('####### a')), undefined);
30
+ assert.deepStrictEqual(inspect(parser('####### a')), [['<h6 class="invalid" id="index:a">a</h6>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser(' # a')), undefined);
32
32
  });
33
33
 
@@ -4,10 +4,9 @@ import { inline, indexee, indexer } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { visualize } from '../util';
6
6
  import { html, defrag } from 'typed-dom';
7
- import { shift } from 'spica/array';
8
7
 
9
8
  export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
10
- /^#{1,6}[^\S\n]+\S[^\n]*(?:\n#{1,6}(?!\S)[^\n]*)*(?:$|\n)/,
9
+ /^#+[^\S\n]+\S[^\n]*(?:\n#+(?!\S)[^\n]*)*(?:$|\n)/,
11
10
  some(line(source => [[source], ''])))));
12
11
 
13
12
  export const heading: HeadingParser = block(rewrite(segment,
@@ -30,6 +29,13 @@ export const heading: HeadingParser = block(rewrite(segment,
30
29
  }}},
31
30
  trim(visualize(some(union([indexer, inline]))))), true),
32
31
  ]),
33
- (ns: [string, ...(HTMLElement | string)[]]) => [
34
- html(`h${shift(ns)[0].length}` as 'h1', defrag(ns))
32
+ ([h, ...ns]: [string, ...(HTMLElement | string)[]]) => [
33
+ h.length <= 6
34
+ ? html(`h${h.length as 1}`, defrag(ns))
35
+ : html(`h6`, {
36
+ class: 'invalid',
37
+ 'data-invalid-syntax': 'heading',
38
+ 'data-invalid-type': 'syntax',
39
+ 'data-invalid-message': 'Heading level must be up to 6',
40
+ }, defrag(ns))
35
41
  ]))))));
@@ -22,7 +22,7 @@ export const mathblock: MathBlockParser = block(validate('$$', fmap(
22
22
  class: 'invalid',
23
23
  translate: 'no',
24
24
  'data-invalid-syntax': 'mathblock',
25
- 'data-invalid-type': delim.length > 2 ? 'syntax' : !closer ? 'closer' : 'argument',
25
+ 'data-invalid-type': delim.length > 2 ? 'syntax' : !closer ? 'fence' : 'argument',
26
26
  'data-invalid-message': delim.length > 2 ? 'Invalid syntax' : !closer ? `Missing the closing delimiter "${delim}"` : 'Invalid argument',
27
27
  }, `${opener}${body}${closer}`),
28
28
  ])));
@@ -54,7 +54,7 @@ export const block: BlockParser = creator(error(
54
54
 
55
55
  function error(parser: BlockParser): BlockParser {
56
56
  return recover<BlockParser>(fallback(
57
- open('\0', source => { throw new Error(source.split('\n', 1)[0]); }),
57
+ open('\x07', source => { throw new Error(source.split('\n', 1)[0]); }),
58
58
  parser),
59
59
  (source, { id }, reason) => [[
60
60
  html('h1',
@@ -71,7 +71,7 @@ function error(parser: BlockParser): BlockParser {
71
71
  translate: 'no',
72
72
  },
73
73
  source
74
- .replace(/^\0.*\n/, '')
74
+ .replace(/^\x07.*\n/, '')
75
75
  .slice(0, 1001)
76
76
  .replace(/^(.{997}).{4}$/s, '$1...') || undefined),
77
77
  ], '']);
@@ -21,4 +21,4 @@ export const comment: CommentParser = lazy(() => creator(validate('[#', match(
21
21
  ]),
22
22
  ], rest],
23
23
  ([as, bs = []], rest) => [unshift(as, bs), rest]),
24
- ([, fence]) => fence)))));
24
+ ([, fence]) => fence.length)))));
@@ -42,6 +42,7 @@ describe('Unit: parser/inline/extension/index', () => {
42
42
  assert.deepStrictEqual(inspect(parser('[#a ]')), [['<a class="index" href="#index:a">a</a>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser('[#a b]')), [['<a class="index" href="#index:a_b">a b</a>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('[#a b]')), [['<a class="index" href="#index:a_b">a b</a>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('[#a \\ ]')), [['<a class="index" href="#index:a">a</a>'], '']);
45
46
  assert.deepStrictEqual(inspect(parser('[#a &nbsp;]')), [['<a class="index" href="#index:a">a</a>'], '']);
46
47
  assert.deepStrictEqual(inspect(parser('[#a <wbr>]')), [['<a class="index" href="#index:a">a</a>'], '']);
47
48
  assert.deepStrictEqual(inspect(parser('[#a [# b #]]')), [['<a class="index" href="#index:a">a <span class="comment"><input type="checkbox"><span>[# b #]</span></span></a>'], '']);
@@ -70,21 +71,26 @@ describe('Unit: parser/inline/extension/index', () => {
70
71
  assert.deepStrictEqual(inspect(parser('[#|]')), [['<a class="index" href="#index:|">|</a>'], '']);
71
72
  assert.deepStrictEqual(inspect(parser('[#|#]')), [['<a class="index" href="#index:|#">|#</a>'], '']);
72
73
  assert.deepStrictEqual(inspect(parser('[#|#b]')), [['<a class="index" href="#index:|#b">|#b</a>'], '']);
73
- assert.deepStrictEqual(inspect(parser('[#a|#b]')), [['<a class="index" href="#index:a|#b">a|#b</a>'], '']);
74
- assert.deepStrictEqual(inspect(parser('[#a |]')), [['<a class="index" href="#index:a_|">a |</a>'], '']);
75
- assert.deepStrictEqual(inspect(parser('[#a |#]')), [['<a class="index" href="#index:a_|#">a |#</a>'], '']);
76
- assert.deepStrictEqual(inspect(parser('[#a |# ]')), [['<a class="index" href="#index:a_|#">a |#</a>'], '']);
77
- assert.deepStrictEqual(inspect(parser('[#a |#\\ ]')), [['<a class="index" href="#index:a_|#">a |#</a>'], '']);
74
+ assert.deepStrictEqual(inspect(parser('[#a|]')), [['<a class="index" href="#index:a|">a|</a>'], '']);
75
+ assert.deepStrictEqual(inspect(parser('[#a|#]')), [['<a class="index" href="#index:a|#">a|#</a>'], '']);
76
+ assert.deepStrictEqual(inspect(parser('[#a|# ]')), [['<a class="index" href="#index:a|#">a|#</a>'], '']);
77
+ assert.deepStrictEqual(inspect(parser('[#a|#\\ ]')), [['<a class="index" href="#index:a|#">a|#</a>'], '']);
78
+ assert.deepStrictEqual(inspect(parser('[#a|#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
79
+ assert.deepStrictEqual(inspect(parser('[#a|#b ]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
80
+ assert.deepStrictEqual(inspect(parser('[#a|#b ]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
81
+ assert.deepStrictEqual(inspect(parser('[#a|#\\b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
82
+ assert.deepStrictEqual(inspect(parser('[#a|#*b*]')), [['<a class="index" href="#index:*b*">a<span class="indexer" data-index="*b*"></span></a>'], '']);
83
+ assert.deepStrictEqual(inspect(parser('[#a|#b c]')), [['<a class="index" href="#index:b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
84
+ assert.deepStrictEqual(inspect(parser('[#a|#b c]')), [['<a class="index" href="#index:b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
85
+ assert.deepStrictEqual(inspect(parser('[#a|#[]]')), [['<a class="index" href="#index:[]">a<span class="indexer" data-index="[]"></span></a>'], '']);
86
+ assert.deepStrictEqual(inspect(parser('[#a|#&copy;]')), [['<a class="index" href="#index:&amp;copy;">a<span class="indexer" data-index="&amp;copy;"></span></a>'], '']);
78
87
  assert.deepStrictEqual(inspect(parser('[#a |#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
79
- assert.deepStrictEqual(inspect(parser('[#a |#b ]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
80
- assert.deepStrictEqual(inspect(parser('[#a |#b ]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
81
- assert.deepStrictEqual(inspect(parser('[#a |#\\b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
82
- assert.deepStrictEqual(inspect(parser('[#a |#*b*]')), [['<a class="index" href="#index:*b*">a<span class="indexer" data-index="*b*"></span></a>'], '']);
83
- assert.deepStrictEqual(inspect(parser('[#a |#b c]')), [['<a class="index" href="#index:b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
84
- assert.deepStrictEqual(inspect(parser('[#a |#b c]')), [['<a class="index" href="#index:b_c">a<span class="indexer" data-index="b_c"></span></a>'], '']);
85
- assert.deepStrictEqual(inspect(parser('[#a |#[]]')), [['<a class="index" href="#index:[]">a<span class="indexer" data-index="[]"></span></a>'], '']);
86
- assert.deepStrictEqual(inspect(parser('[#a |#&copy;]')), [['<a class="index" href="#index:&amp;copy;">a<span class="indexer" data-index="&amp;copy;"></span></a>'], '']);
87
88
  assert.deepStrictEqual(inspect(parser('[#a |#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
89
+ assert.deepStrictEqual(inspect(parser('[#a \\ |#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
90
+ assert.deepStrictEqual(inspect(parser('[#a &nbsp;|#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
91
+ assert.deepStrictEqual(inspect(parser('[#a <wbr>|#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
92
+ assert.deepStrictEqual(inspect(parser('[#a [# b #]|#c]')), [['<a class="index" href="#index:c">a <span class="comment"><input type="checkbox"><span>[# b #]</span></span><span class="indexer" data-index="c"></span></a>'], '']);
93
+ assert.deepStrictEqual(inspect(parser('[#a\\ |#b]')), [['<a class="index" href="#index:b">a<span class="indexer" data-index="b"></span></a>'], '']);
88
94
  });
89
95
 
90
96
  });
@@ -3,7 +3,7 @@ import { ExtensionParser } from '../../inline';
3
3
  import { union, some, validate, guard, context, creator, surround, open, lazy, fmap } from '../../../combinator';
4
4
  import { inline } from '../../inline';
5
5
  import { indexee, identity } from './indexee';
6
- import { txt, str } from '../../source';
6
+ import { txt, str, stropt } from '../../source';
7
7
  import { startTight, trimNodeEnd } from '../../util';
8
8
  import { html, define, defrag } from 'typed-dom';
9
9
  import { join } from 'spica/array';
@@ -23,10 +23,11 @@ export const index: IndexParser = lazy(() => creator(validate('[#', ']', '\n', f
23
23
  media: false,
24
24
  autolink: false,
25
25
  }}},
26
+ open(stropt('|'),
26
27
  some(union([
27
28
  signature,
28
29
  inline,
29
- ]), ']', /^\\?\n/)))),
30
+ ]), ']', /^\\?\n/), true)))),
30
31
  ']'),
31
32
  ns => [html('a', trimNodeEnd(defrag(ns)))])),
32
33
  ([el]: [HTMLAnchorElement]) => [
@@ -40,7 +41,7 @@ export const index: IndexParser = lazy(() => creator(validate('[#', ']', '\n', f
40
41
  ]))));
41
42
 
42
43
  const signature: IndexParser.SignatureParser = lazy(() => creator(fmap(open(
43
- /^\s+\|#/,
44
+ '|#',
44
45
  startTight(some(union([bracket, txt]), ']'))),
45
46
  ns => [
46
47
  html('span', { class: 'indexer', 'data-index': identity(join(ns)).slice(6) }),
@@ -6,12 +6,12 @@ import { reduce } from 'spica/memoize';
6
6
 
7
7
  export const unsafehtmlentity: UnsafeHTMLEntityParser = creator(validate('&', focus(
8
8
  /^&[0-9A-Za-z]+;/,
9
- entity => [[parse(entity) ?? `\0${entity}`], ''])));
9
+ entity => [[parse(entity) ?? `\x1B${entity}`], ''])));
10
10
 
11
11
  export const htmlentity: HTMLEntityParser = fmap(
12
12
  union([unsafehtmlentity]),
13
13
  ([test]) => [
14
- test[0] === '\0'
14
+ test[0] === '\x1B'
15
15
  ? html('span', {
16
16
  class: 'invalid',
17
17
  'data-invalid-syntax': 'htmlentity',
@@ -95,13 +95,13 @@ function sanitize(target: HTMLElement, uri: ReadonlyURL, alt: string): boolean {
95
95
  });
96
96
  return false;
97
97
  }
98
- if (alt.includes('\0')) {
98
+ if (alt.includes('\x1B')) {
99
99
  define(target, {
100
100
  class: void target.classList.add('invalid'),
101
101
  'data-invalid-syntax': 'media',
102
102
  'data-invalid-type': 'content',
103
103
  'data-invalid-message': `Cannot use invalid HTML entitiy "${alt.match(/&[0-9A-Za-z]+;/)![0]}"`,
104
- alt: target.getAttribute('alt')?.replace(/\0/g, ''),
104
+ alt: target.getAttribute('alt')?.replace(/\x1B/g, ''),
105
105
  });
106
106
  return false;
107
107
  }
@@ -83,8 +83,8 @@ function attributes(texts: string[], rubies: string[]): Record<string, string> {
83
83
  let attrs: Record<string, string> | undefined;
84
84
  for (const ss of [texts, rubies]) {
85
85
  for (let i = 0; i < ss.length; ++i) {
86
- if (ss[i].indexOf('\0') === -1) continue;
87
- ss[i] = ss[i].replace(/\0/g, '');
86
+ if (ss[i].indexOf('\x1B') === -1) continue;
87
+ ss[i] = ss[i].replace(/\x1B/g, '');
88
88
  attrs ??= {
89
89
  class: 'invalid',
90
90
  'data-invalid-syntax': 'ruby',