securemark 0.288.0 → 0.288.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +7261 -7317
  3. package/markdown.d.ts +1 -7
  4. package/package.json +1 -1
  5. package/src/combinator/control/manipulation/surround.ts +5 -0
  6. package/src/combinator/data/parser/context/delimiter.ts +2 -2
  7. package/src/combinator/data/parser.ts +1 -0
  8. package/src/parser/api/parse.test.ts +2 -2
  9. package/src/parser/block/codeblock.ts +9 -6
  10. package/src/parser/block/extension/aside.ts +7 -8
  11. package/src/parser/block/extension/example.ts +7 -8
  12. package/src/parser/block/extension/figure.ts +77 -74
  13. package/src/parser/block/extension/message.ts +7 -8
  14. package/src/parser/block/extension/placeholder.ts +6 -3
  15. package/src/parser/block/extension/table.ts +13 -24
  16. package/src/parser/block/heading.test.ts +1 -1
  17. package/src/parser/block/heading.ts +2 -3
  18. package/src/parser/block/ilist.ts +17 -7
  19. package/src/parser/block/mathblock.ts +8 -6
  20. package/src/parser/block/mediablock.ts +5 -8
  21. package/src/parser/block/olist.ts +5 -7
  22. package/src/parser/block/reply/cite.ts +2 -6
  23. package/src/parser/block/reply/quote.ts +3 -2
  24. package/src/parser/block/reply.ts +1 -1
  25. package/src/parser/block/sidefence.ts +2 -3
  26. package/src/parser/block/table.ts +2 -3
  27. package/src/parser/block/ulist.ts +3 -17
  28. package/src/parser/context.ts +1 -2
  29. package/src/parser/header.ts +2 -3
  30. package/src/parser/inline/annotation.test.ts +1 -0
  31. package/src/parser/inline/bracket.ts +18 -24
  32. package/src/parser/inline/extension/index.test.ts +1 -1
  33. package/src/parser/inline/extension/index.ts +17 -7
  34. package/src/parser/inline/extension/indexee.ts +2 -3
  35. package/src/parser/inline/extension/indexer.test.ts +2 -1
  36. package/src/parser/inline/extension/placeholder.ts +2 -3
  37. package/src/parser/inline/html.ts +21 -23
  38. package/src/parser/inline/htmlentity.ts +6 -8
  39. package/src/parser/inline/link.test.ts +4 -0
  40. package/src/parser/inline/link.ts +3 -5
  41. package/src/parser/inline/math.ts +3 -3
  42. package/src/parser/inline/media.test.ts +17 -13
  43. package/src/parser/inline/media.ts +15 -9
  44. package/src/parser/inline/reference.ts +3 -7
  45. package/src/parser/inline/ruby.ts +4 -5
  46. package/src/parser/inline.test.ts +5 -3
  47. package/src/parser/source/escapable.test.ts +5 -5
  48. package/src/parser/source/escapable.ts +7 -1
  49. package/src/parser/source/str.ts +4 -6
  50. package/src/parser/source/text.ts +1 -0
  51. package/src/parser/source/unescapable.test.ts +5 -5
  52. package/src/parser/source/unescapable.ts +13 -8
  53. package/src/parser/util.ts +12 -0
  54. package/src/parser/visibility.ts +2 -3
package/markdown.d.ts CHANGED
@@ -1112,12 +1112,6 @@ export namespace MarkdownParser {
1112
1112
  // ""
1113
1113
  Inline<'bracket'>,
1114
1114
  Parser<HTMLElement | string, Context, [
1115
- SourceParser.StrParser,
1116
- InlineParser,
1117
- SourceParser.StrParser,
1118
- InlineParser,
1119
- InlineParser,
1120
- InlineParser,
1121
1115
  InlineParser,
1122
1116
  InlineParser,
1123
1117
  InlineParser,
@@ -1251,7 +1245,7 @@ export namespace MarkdownParser {
1251
1245
  export interface TextParser extends
1252
1246
  // abc
1253
1247
  Source<'text'>,
1254
- Parser<string | HTMLBRElement | HTMLSpanElement, Context, []> {
1248
+ Parser<string | HTMLBRElement, Context, []> {
1255
1249
  }
1256
1250
  export interface TxtParser extends
1257
1251
  // abc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.288.0",
3
+ "version": "0.288.2",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -94,6 +94,11 @@ export function surround<T>(
94
94
  backtracks[source.length + offset - 1] |= 1 << (backtrack >>> 2) + shift;
95
95
  }
96
96
  }
97
+ context.recent = [
98
+ lmr_.slice(0, lmr_.length - mr_.length),
99
+ mr_.slice(0, mr_.length - r_.length),
100
+ r_.slice(0, r_.length - rest.length),
101
+ ];
97
102
  return rr
98
103
  ? f
99
104
  ? f([rl, rm!, rr], rest, context)
@@ -1,4 +1,4 @@
1
- import { memoize, reduce } from 'spica/memoize';
1
+ import { memoize } from 'spica/memoize';
2
2
 
3
3
  interface Delimiter {
4
4
  readonly index: number;
@@ -27,7 +27,7 @@ export class Delimiters {
27
27
  case 'string':
28
28
  return source => source.slice(0, pattern.length) === pattern || undefined;
29
29
  case 'object':
30
- return reduce(source => pattern.test(source) || undefined);
30
+ return source => pattern.test(source) || undefined;
31
31
  }
32
32
  },
33
33
  this.signature);
@@ -21,6 +21,7 @@ export interface Ctx {
21
21
  state?: number;
22
22
  backtracks?: Record<number, number>;
23
23
  backtrack?: number;
24
+ recent?: string[];
24
25
  }
25
26
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
26
27
  export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, Ctx, infer D> ? D : never;
@@ -350,7 +350,7 @@ describe('Unit: parser/api/parse', () => {
350
350
 
351
351
  it('backtrack', function () {
352
352
  this.timeout(5000);
353
- const str = `${'.'.repeat(0 + 0)}((${'['.repeat(13)}{{http://[[[${'.'.repeat(8326)}`;
353
+ const str = `${'.'.repeat(7 + 0)}((${'['.repeat(13)}{{http://[[[${'.'.repeat(8328)}`;
354
354
  assert.deepStrictEqual(
355
355
  [...parse(str).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
356
356
  [`<p>${str}</p>`]);
@@ -358,7 +358,7 @@ describe('Unit: parser/api/parse', () => {
358
358
 
359
359
  it('backtrack error', function () {
360
360
  this.timeout(5000);
361
- const str = `${'.'.repeat(0 + 1)}((${'['.repeat(13)}{{http://[[[${'.'.repeat(8326)}`;
361
+ const str = `${'.'.repeat(7 + 1)}((${'['.repeat(13)}{{http://[[[${'.'.repeat(8328)}`;
362
362
  assert.deepStrictEqual(
363
363
  [...parse(str).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
364
364
  [
@@ -2,6 +2,7 @@ import { CodeBlockParser } from '../block';
2
2
  import { eval } from '../../combinator/data/parser';
3
3
  import { block, validate, fence, clear, fmap } from '../../combinator';
4
4
  import { autolink } from '../autolink';
5
+ import { invalid } from '../util';
5
6
  import { html, defrag } from 'typed-dom/dom';
6
7
 
7
8
  const opener = /^(`{3,})(?!`)([^\n]*)(?:$|\n)/;
@@ -51,12 +52,14 @@ export const codeblock: CodeBlockParser = block(validate('```', fmap(
51
52
  if (!closer || overflow || params.invalid) return [html('pre', {
52
53
  class: 'invalid',
53
54
  translate: 'no',
54
- 'data-invalid-syntax': 'codeblock',
55
- 'data-invalid-type': !closer || overflow ? 'fence' : 'argument',
56
- 'data-invalid-message':
57
- !closer ? `Missing the closing delimiter "${delim}"` :
58
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
59
- params.invalid,
55
+ ...invalid(
56
+ 'codeblock',
57
+ !closer || overflow ? 'fence' : 'argument',
58
+ !closer
59
+ ? `Missing the closing delimiter "${delim}"`
60
+ : overflow
61
+ ? `Invalid trailing line after the closing delimiter "${delim}"`
62
+ : params.invalid!),
60
63
  }, `${opener}${body}${overflow || closer}`)];
61
64
  const el = html('pre',
62
65
  {
@@ -2,6 +2,7 @@ import { ExtensionParser } from '../../block';
2
2
  import { Recursion } from '../../context';
3
3
  import { recursion, block, validate, fence, fmap } from '../../../combinator';
4
4
  import { identity } from '../../inline/extension/indexee';
5
+ import { invalid } from '../../util';
5
6
  import { parse } from '../../api/parse';
6
7
  import { html } from 'typed-dom/dom';
7
8
 
@@ -12,12 +13,12 @@ export const aside: ExtensionParser.AsideParser = recursion(Recursion.block, blo
12
13
  if (!closer || overflow || param.trimStart()) return [html('pre', {
13
14
  class: 'invalid',
14
15
  translate: 'no',
15
- 'data-invalid-syntax': 'aside',
16
- 'data-invalid-type': !closer || overflow ? 'fence' : 'argument',
17
- 'data-invalid-message':
16
+ ...invalid(
17
+ 'aside',
18
+ !closer || overflow ? 'fence' : 'argument',
18
19
  !closer ? `Missing the closing delimiter "${delim}"` :
19
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
20
- 'Invalid argument',
20
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
21
+ 'Invalid argument'),
21
22
  }, `${opener}${body}${overflow || closer}`)];
22
23
  const references = html('ol', { class: 'references' });
23
24
  const document = parse(body.slice(0, -1), {
@@ -31,9 +32,7 @@ export const aside: ExtensionParser.AsideParser = recursion(Recursion.block, blo
31
32
  if (!heading) return [html('pre', {
32
33
  class: 'invalid',
33
34
  translate: 'no',
34
- 'data-invalid-syntax': 'aside',
35
- 'data-invalid-type': 'content',
36
- 'data-invalid-message': 'Missing the title at the first line',
35
+ ...invalid('aside', 'content', 'Missing the title at the first line'),
37
36
  }, `${opener}${body}${closer}`)];
38
37
  assert(identity('index', context.id, heading));
39
38
  return [
@@ -3,6 +3,7 @@ import { Recursion } from '../../context';
3
3
  import { eval } from '../../../combinator/data/parser';
4
4
  import { recursion, block, validate, fence, fmap } from '../../../combinator';
5
5
  import { mathblock } from '../mathblock';
6
+ import { invalid } from '../../util';
6
7
  import { parse } from '../../api/parse';
7
8
  import { html } from 'typed-dom/dom';
8
9
 
@@ -15,12 +16,12 @@ export const example: ExtensionParser.ExampleParser = recursion(Recursion.block,
15
16
  if (!closer || overflow || param.trimStart()) return [html('pre', {
16
17
  class: 'invalid',
17
18
  translate: 'no',
18
- 'data-invalid-syntax': 'example',
19
- 'data-invalid-type': !closer || overflow ? 'fence' : 'argument',
20
- 'data-invalid-message':
19
+ ...invalid(
20
+ 'example',
21
+ !closer || overflow ? 'fence' : 'argument',
21
22
  !closer ? `Missing the closing delimiter "${delim}"` :
22
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
23
- 'Invalid argument',
23
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
24
+ 'Invalid argument'),
24
25
  }, `${opener}${body}${overflow || closer}`)];
25
26
  switch (type) {
26
27
  case 'markdown': {
@@ -53,9 +54,7 @@ export const example: ExtensionParser.ExampleParser = recursion(Recursion.block,
53
54
  html('pre', {
54
55
  class: 'invalid',
55
56
  translate: 'no',
56
- 'data-invalid-syntax': 'example',
57
- 'data-invalid-type': 'type',
58
- 'data-invalid-message': 'Invalid example type',
57
+ ...invalid('example', 'type', 'Invalid example type'),
59
58
  }, `${opener}${body}${closer}`),
60
59
  ];
61
60
  }
@@ -13,6 +13,7 @@ import { blockquote, segment as seg_blockquote } from '../blockquote';
13
13
  import { placeholder, segment_ as seg_placeholder } from './placeholder';
14
14
  import { inline, media, shortmedia } from '../../inline';
15
15
  import { visualize, trimBlank } from '../../visibility';
16
+ import { invalid } from '../../util';
16
17
  import { memoize } from 'spica/memoize';
17
18
  import { html, defrag } from 'typed-dom/dom';
18
19
 
@@ -79,38 +80,40 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
79
80
  ])),
80
81
  fmap(
81
82
  fence(/^(~{3,})(?:figure|\[?\$\S*)(?!\S)[^\n]*(?:$|\n)/, 300),
82
- ([body, overflow, closer, opener, delim]: string[], _, context) => [
83
- html('pre', {
84
- class: 'invalid',
85
- translate: 'no',
86
- 'data-invalid-syntax': 'figure',
87
- ...
88
- !closer && {
89
- 'data-invalid-type': 'fence',
90
- 'data-invalid-message': `Missing the closing delimiter "${delim}"`,
91
- } ||
92
- overflow && {
93
- 'data-invalid-type': 'fence',
94
- 'data-invalid-message': `Invalid trailing line after the closing delimiter "${delim}"`,
95
- } ||
96
- !seg_label({ source: opener.match(/^~+(?:figure[^\S\n]+)?(\[?\$\S+)/)?.[1] ?? '', context }) && {
97
- 'data-invalid-type': 'label',
98
- 'data-invalid-message': 'Invalid label',
99
- } ||
100
- /^~+(?:figure[^\S\n]+)?(\[?\$\S+)[^\S\n]+\S/.test(opener) && {
101
- 'data-invalid-type': 'argument',
102
- 'data-invalid-message': 'Invalid argument',
103
- } ||
104
- {
105
- 'data-invalid-type': 'content',
106
- 'data-invalid-message': 'Invalid content',
107
- },
108
- }, `${opener}${body}${overflow || closer}`),
109
- ])));
83
+ ([body, overflow, closer, opener, delim]: string[], _, context) => {
84
+ const violation =
85
+ !closer && [
86
+ 'fence',
87
+ `Missing the closing delimiter "${delim}"`,
88
+ ] ||
89
+ overflow && [
90
+ 'fence',
91
+ `Invalid trailing line after the closing delimiter "${delim}"`,
92
+ ] ||
93
+ !seg_label({ source: opener.match(/^~+(?:figure[^\S\n]+)?(\[?\$\S+)/)?.[1] ?? '', context }) && [
94
+ 'label',
95
+ 'Invalid label',
96
+ ] ||
97
+ /^~+(?:figure[^\S\n]+)?(\[?\$\S+)[^\S\n]+\S/.test(opener) && [
98
+ 'argument',
99
+ 'Invalid argument',
100
+ ] ||
101
+ [
102
+ 'content',
103
+ 'Invalid content',
104
+ ];
105
+ return [
106
+ html('pre', {
107
+ class: 'invalid',
108
+ translate: 'no',
109
+ ...invalid('figure', violation[0], violation[1]),
110
+ }, `${opener}${body}${overflow || closer}`),
111
+ ];
112
+ })));
110
113
 
111
114
  function attributes(label: string, param: string, content: HTMLElement, caption: readonly HTMLElement[]): Record<string, string | undefined> {
112
115
  const group = label.split('-', 1)[0];
113
- let type: string = content.className.split(/\s/)[0];
116
+ let type = content.className.split(/\s/)[0];
114
117
  switch (type || content.tagName) {
115
118
  case 'UL':
116
119
  case 'OL':
@@ -135,57 +138,57 @@ function attributes(label: string, param: string, content: HTMLElement, caption:
135
138
  default:
136
139
  assert(false);
137
140
  }
138
- const invalid =
139
- param.trimStart() !== '' && {
140
- 'data-invalid-type': 'argument',
141
- 'data-invalid-message': 'Invalid argument',
142
- } ||
143
- /^[^-]+-(?:[0-9]+\.)*0$/.test(label) && {
144
- 'data-invalid-type': 'label',
145
- 'data-invalid-message': 'The last part of the fixed label numbers must not be 0',
146
- } ||
147
- group === '$' && (type !== 'math' || caption.length > 0) && {
148
- 'data-invalid-type': 'label',
149
- 'data-invalid-message': '"$" label group must be used to math formulas with no caption',
150
- } ||
151
- type === 'media' && {} ||
152
- ['fig', 'figure'].includes(group) && {
153
- 'data-invalid-type': 'label',
154
- 'data-invalid-message': '"fig" and "figure" label groups must be used to media',
155
- } ||
156
- group === 'table' && type !== group && {
157
- 'data-invalid-type': 'label',
158
- 'data-invalid-message': '"table" label group must be used to tables',
159
- } ||
160
- group === 'list' && type !== group && {
161
- 'data-invalid-type': 'label',
162
- 'data-invalid-message': '"list" label group must be used to lists',
163
- } ||
164
- group === 'quote' && type !== group && {
165
- 'data-invalid-type': 'label',
166
- 'data-invalid-message': '"quote" label group must be used to blockquotes',
167
- } ||
168
- group === 'text' && type !== group && {
169
- 'data-invalid-type': 'label',
170
- 'data-invalid-message': '"text" label group must be used to codeblocks with no language',
171
- } ||
172
- group === 'code' && type !== group && {
173
- 'data-invalid-type': 'label',
174
- 'data-invalid-message': '"code" label group must be used to codeblocks with any language',
175
- } ||
176
- group === 'example' && type !== group && {
177
- 'data-invalid-type': 'label',
178
- 'data-invalid-message': '"example" label group must be used to examples',
179
- } ||
141
+ const violation =
142
+ param.trimStart() !== '' && [
143
+ 'argument',
144
+ 'Invalid argument',
145
+ ] ||
146
+ /^[^-]+-(?:[0-9]+\.)*0$/.test(label) && [
147
+ 'label',
148
+ 'The last part of the fixed label numbers must not be 0',
149
+ ] ||
150
+ group === '$' && (type !== 'math' || caption.length > 0) && [
151
+ 'label',
152
+ '"$" label group must be used to math formulas with no caption',
153
+ ] ||
154
+ type === 'media' && [
155
+ ] ||
156
+ ['fig', 'figure'].includes(group) && [
157
+ 'label',
158
+ '"fig" and "figure" label groups must be used to media',
159
+ ] ||
160
+ group === 'table' && type !== group && [
161
+ 'label',
162
+ '"table" label group must be used to tables',
163
+ ] ||
164
+ group === 'list' && type !== group && [
165
+ 'label',
166
+ '"list" label group must be used to lists',
167
+ ] ||
168
+ group === 'quote' && type !== group && [
169
+ 'label',
170
+ '"quote" label group must be used to blockquotes',
171
+ ] ||
172
+ group === 'text' && type !== group && [
173
+ 'label',
174
+ '"text" label group must be used to codeblocks with no language',
175
+ ] ||
176
+ group === 'code' && type !== group && [
177
+ 'label',
178
+ '"code" label group must be used to codeblocks with any language',
179
+ ] ||
180
+ group === 'example' && type !== group && [
181
+ 'label',
182
+ '"example" label group must be used to examples',
183
+ ] ||
180
184
  undefined;
181
185
  return {
182
186
  'data-type': type,
183
187
  'data-label': label,
184
188
  'data-group': group,
185
- ...invalid?.['data-invalid-type'] && {
189
+ ...violation?.[0] && {
186
190
  class: 'invalid',
187
- 'data-invalid-syntax': 'figure',
188
- ...invalid,
191
+ ...invalid('figure', violation[0], violation[1]),
189
192
  },
190
193
  };
191
194
  }
@@ -13,6 +13,7 @@ import { sidefence } from '../sidefence';
13
13
  import { blockquote } from '../blockquote';
14
14
  import { mediablock } from '../mediablock';
15
15
  import { paragraph } from '../paragraph';
16
+ import { invalid } from '../../util';
16
17
  import { unshift, push } from 'spica/array';
17
18
  import { html } from 'typed-dom/dom';
18
19
 
@@ -25,12 +26,12 @@ export const message: MessageParser = block(validate('~~~', fmap(
25
26
  if (!closer || overflow || param.trimStart()) return [html('pre', {
26
27
  class: 'invalid',
27
28
  translate: 'no',
28
- 'data-invalid-syntax': 'message',
29
- 'data-invalid-type': !closer || overflow ? 'fence' : 'argument',
30
- 'data-invalid-message':
29
+ ...invalid(
30
+ 'message',
31
+ !closer || overflow ? 'fence' : 'argument',
31
32
  !closer ? `Missing the closing delimiter "${delim}"` :
32
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
33
- 'Invalid argument',
33
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
34
+ 'Invalid argument'),
34
35
  }, `${opener}${body}${overflow || closer}`)];
35
36
  switch (type) {
36
37
  case 'note':
@@ -41,9 +42,7 @@ export const message: MessageParser = block(validate('~~~', fmap(
41
42
  return [html('pre', {
42
43
  class: 'invalid',
43
44
  translate: 'no',
44
- 'data-invalid-syntax': 'message',
45
- 'data-invalid-type': 'type',
46
- 'data-invalid-message': 'Invalid message type',
45
+ ...invalid('message', 'type', 'Invalid message type'),
47
46
  }, `${opener}${body}${closer}`)];
48
47
  }
49
48
  return [
@@ -1,5 +1,6 @@
1
1
  import { ExtensionParser } from '../../block';
2
2
  import { block, validate, fence, clear, fmap } from '../../../combinator';
3
+ import { invalid } from '../../util';
3
4
  import { html } from 'typed-dom/dom';
4
5
 
5
6
  const opener = /^(~{3,})(?!~)[^\n]*(?:$|\n)/;
@@ -16,9 +17,11 @@ export const placeholder: ExtensionParser.PlaceholderParser = block(validate('~~
16
17
  html('pre', {
17
18
  class: 'invalid',
18
19
  translate: 'no',
19
- 'data-invalid-message':
20
+ ...invalid(
21
+ 'extension',
22
+ 'fence',
20
23
  !closer ? `Missing the closing delimiter "${delim}"` :
21
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
22
- 'Invalid argument',
24
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
25
+ 'Invalid argument'),
23
26
  }, `${opener}${body}${overflow || closer}`),
24
27
  ])));
@@ -4,7 +4,7 @@ import { Tree, eval } from '../../../combinator/data/parser';
4
4
  import { union, subsequence, inits, some, block, line, validate, fence, rewrite, surround, open, clear, convert, dup, lazy, fmap } from '../../../combinator';
5
5
  import { inline, medialink, media, shortmedia } from '../../inline';
6
6
  import { str, anyline, emptyline, contentline } from '../../source';
7
- import { lineable } from '../../util';
7
+ import { lineable, invalid } from '../../util';
8
8
  import { visualize, trimBlank, trimBlankEnd } from '../../visibility';
9
9
  import { unshift, splice } from 'spica/array';
10
10
  import { html, define, defrag } from 'typed-dom/dom';
@@ -29,12 +29,12 @@ export const table: TableParser = block(validate('~~~', fmap(
29
29
  if (!closer || overflow || param.trimStart()) return [html('pre', {
30
30
  class: 'invalid',
31
31
  translate: 'no',
32
- 'data-invalid-syntax': 'table',
33
- 'data-invalid-type': !closer || overflow ? 'fence' : 'argument',
34
- 'data-invalid-message':
32
+ ...invalid(
33
+ 'table',
34
+ !closer || overflow ? 'fence' : 'argument',
35
35
  !closer ? `Missing the closing delimiter "${delim}"` :
36
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
37
- 'Invalid argument',
36
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
37
+ 'Invalid argument'),
38
38
  }, `${opener}${body}${overflow || closer}`)];
39
39
  switch (type) {
40
40
  case 'grid':
@@ -45,9 +45,7 @@ export const table: TableParser = block(validate('~~~', fmap(
45
45
  return [html('pre', {
46
46
  class: 'invalid',
47
47
  translate: 'no',
48
- 'data-invalid-syntax': 'table',
49
- 'data-invalid-type': 'argument',
50
- 'data-invalid-message': 'Invalid table type',
48
+ ...invalid('table', 'argument', 'Invalid table type'),
51
49
  }, `${opener}${body}${closer}`)];
52
50
  }
53
51
  })));
@@ -140,21 +138,12 @@ function attributes(source: string): Record<string, string | undefined> {
140
138
  class: valid ? highlight && 'highlight' : 'invalid',
141
139
  rowspan,
142
140
  colspan,
143
- ...
144
- !validH && {
145
- 'data-invalid-syntax': 'table',
146
- 'data-invalid-type': 'syntax',
147
- 'data-invalid-message': 'Too much highlight level',
148
- } ||
149
- !validE && {
150
- 'data-invalid-syntax': 'table',
151
- 'data-invalid-type': 'syntax',
152
- 'data-invalid-message': 'Extensible cells are only head cells',
153
- } ||
154
- {
155
- 'data-highlight-level': level > 1 ? `${level}` : undefined,
156
- 'data-highlight-extension': extension,
157
- },
141
+ ...!validH && invalid('table', 'syntax', 'Too much highlight level')
142
+ || !validE && invalid('table', 'syntax', 'Extensible cells are only head cells')
143
+ || {
144
+ 'data-highlight-level': level > 1 ? `${level}` : undefined,
145
+ 'data-highlight-extension': extension,
146
+ },
158
147
  };
159
148
  }
160
149
 
@@ -69,7 +69,7 @@ describe('Unit: parser/block/heading', () => {
69
69
  assert.deepStrictEqual(inspect(parser('# a [|b ]')), [['<h1 id="index::b">a<span class="indexer" data-index="b"></span></h1>'], '']);
70
70
  assert.deepStrictEqual(inspect(parser('# a [|b ]')), [['<h1 id="index::b">a<span class="indexer" data-index="b"></span></h1>'], '']);
71
71
  assert.deepStrictEqual(inspect(parser('# a [|b c]')), [['<h1 id="index::b_c">a<span class="indexer" data-index="b_c"></span></h1>'], '']);
72
- assert.deepStrictEqual(inspect(parser('# a [|*b*`c`${d}$]')), [['<h1 id="index::b`c`${d}$">a<span class="indexer" data-index="b`c`${d}$"></span></h1>'], '']);
72
+ assert.deepStrictEqual(inspect(parser('# a [|*b*`c`${d}$]')), [['<h1 id="index::*b*`c`${d}$">a<span class="indexer" data-index="*b*`c`${d}$"></span></h1>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('# a [|@a]')), [['<h1 id="index::@a">a<span class="indexer" data-index="@a"></span></h1>'], '']);
74
74
  assert.deepStrictEqual(inspect(parser('# a [|http://host]')), [['<h1 id="index::http://host">a<span class="indexer" data-index="http://host"></span></h1>'], '']);
75
75
  assert.deepStrictEqual(inspect(parser('# a [|!http://host]')), [['<h1 id="index::!http://host">a<span class="indexer" data-index="!http://host"></span></h1>'], '']);
@@ -4,6 +4,7 @@ import { union, some, state, block, line, validate, focus, rewrite, open, fmap }
4
4
  import { inline, indexee, indexer, dataindex } from '../inline';
5
5
  import { str } from '../source';
6
6
  import { visualize, trimBlank } from '../visibility';
7
+ import { invalid } from '../util';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
 
9
10
  export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
@@ -27,8 +28,6 @@ export const heading: HeadingParser = block(rewrite(segment,
27
28
  ? html(`h${h.length as 1}`, { 'data-index': dataindex(ns) }, defrag(ns))
28
29
  : html(`h6`, {
29
30
  class: 'invalid',
30
- 'data-invalid-syntax': 'heading',
31
- 'data-invalid-type': 'syntax',
32
- 'data-invalid-message': 'Heading level must be up to 6',
31
+ ...invalid('heading', 'syntax', 'Heading level must be up to 6'),
33
32
  }, defrag(ns))
34
33
  ]))))));
@@ -1,11 +1,13 @@
1
1
  import { IListParser } from '../block';
2
+ import { Parser } from '../../combinator/data/parser';
2
3
  import { Recursion } from '../context';
3
- import { union, inits, some, recursion, block, line, validate, indent, open, trim, fallback, lazy, fmap } from '../../combinator';
4
- import { ulist_, invalid, fillFirstLine } from './ulist';
4
+ import { union, inits, some, recursion, block, line, validate, indent, rewrite, open, trim, fallback, lazy, fmap } from '../../combinator';
5
+ import { ulist_, fillFirstLine } from './ulist';
5
6
  import { olist_ } from './olist';
6
7
  import { inline } from '../inline';
7
- import { lineable } from '../util';
8
+ import { contentline } from '../source';
8
9
  import { visualize, trimBlank } from '../visibility';
10
+ import { lineable, invalid } from '../util';
9
11
  import { html, defrag } from 'typed-dom/dom';
10
12
 
11
13
  export const ilist: IListParser = lazy(() => block(validate(
@@ -20,14 +22,22 @@ export const ilist_: IListParser = lazy(() => block(fmap(validate(
20
22
  line(open(/^[-+*](?:$|\s)/, trim(visualize(trimBlank(lineable(some(inline))))), true)),
21
23
  indent(union([ulist_, olist_, ilist_])),
22
24
  ]),
23
- invalid),
25
+ ilistitem),
24
26
  ns => [html('li', defrag(fillFirstLine(ns)))]),
25
27
  ])))),
26
28
  es => [
27
29
  html('ul', {
28
30
  class: 'invalid',
29
- 'data-invalid-syntax': 'list',
30
- 'data-invalid-type': 'syntax',
31
- 'data-invalid-message': 'Use "-" instead of "+" or "*"',
31
+ ...invalid('list', 'syntax', 'Use "-" instead of "+" or "*"'),
32
32
  }, es),
33
33
  ])));
34
+
35
+ export const ilistitem = rewrite(
36
+ inits([contentline, indent<Parser<string>>(({ source }) => [[source], ''])]),
37
+ ({ source }) => [[
38
+ '',
39
+ html('span', {
40
+ class: 'invalid',
41
+ ...invalid('list', 'syntax', 'Fix the indent or the head of the list item'),
42
+ }, source.replace('\n', ''))
43
+ ], '']);
@@ -1,5 +1,6 @@
1
1
  import { MathBlockParser } from '../block';
2
2
  import { block, validate, fence, clear, fmap } from '../../combinator';
3
+ import { invalid } from '../util';
3
4
  import { html } from 'typed-dom/dom';
4
5
 
5
6
  const opener = /^(\${2,})(?!\$)([^\n]*)(?:$|\n)/;
@@ -20,11 +21,12 @@ export const mathblock: MathBlockParser = block(validate('$$', fmap(
20
21
  : html('pre', {
21
22
  class: 'invalid',
22
23
  translate: 'no',
23
- 'data-invalid-syntax': 'mathblock',
24
- 'data-invalid-type': delim.length > 2 ? 'syntax' : !closer || overflow ? 'fence' : 'argument',
25
- 'data-invalid-message': delim.length > 2 ? 'Invalid syntax' :
26
- !closer ? `Missing the closing delimiter "${delim}"` :
27
- overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
28
- 'Invalid argument',
24
+ ...invalid(
25
+ 'mathblock',
26
+ delim.length > 2 ? 'syntax' : !closer || overflow ? 'fence' : 'argument',
27
+ delim.length > 2 ? 'Invalid syntax' :
28
+ !closer ? `Missing the closing delimiter "${delim}"` :
29
+ overflow ? `Invalid trailing line after the closing delimiter "${delim}"` :
30
+ 'Invalid argument'),
29
31
  }, `${opener}${body}${overflow || closer}`),
30
32
  ])));
@@ -1,6 +1,7 @@
1
1
  import { MediaBlockParser } from '../block';
2
2
  import { union, inits, some, block, line, validate, fallback, fmap } from '../../combinator';
3
3
  import { medialink, media, shortmedia } from '../inline';
4
+ import { invalid } from '../util';
4
5
  import { html } from 'typed-dom/dom';
5
6
 
6
7
  export const mediablock: MediaBlockParser = block(validate(['[!', '!'], fmap(
@@ -14,13 +15,9 @@ export const mediablock: MediaBlockParser = block(validate(['[!', '!'], fmap(
14
15
  medialink,
15
16
  media,
16
17
  shortmedia,
17
- ]), ({ source }) => [[html('div', [html('span', attrs, source.replace('\n', ''))])], '']))),
18
+ ]), ({ source }) => [[html('div', [html('span', {
19
+ class: 'invalid',
20
+ ...invalid('mediablock', 'syntax', 'Not media syntax'),
21
+ }, source.replace('\n', ''))])], '']))),
18
22
  ]),
19
23
  ns => [html('div', ns)])));
20
-
21
- const attrs = {
22
- class: 'invalid',
23
- 'data-invalid-syntax': 'mediablock',
24
- 'data-invalid-type': 'syntax',
25
- 'data-invalid-message': 'Not media syntax',
26
- } as const;