securemark 0.219.4 → 0.223.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 (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/securemark.js +724 -578
  3. package/package-lock.json +389 -864
  4. package/package.json +12 -12
  5. package/src/combinator/control/manipulation/fallback.ts +8 -0
  6. package/src/combinator/control/manipulation/fence.ts +1 -1
  7. package/src/combinator/control/manipulation/indent.test.ts +0 -1
  8. package/src/combinator/control/manipulation/indent.ts +1 -1
  9. package/src/combinator/control/manipulation/recovery.ts +2 -2
  10. package/src/combinator.ts +1 -0
  11. package/src/parser/api/normalize.test.ts +9 -6
  12. package/src/parser/api/normalize.ts +30 -29
  13. package/src/parser/block/ilist.ts +4 -2
  14. package/src/parser/block/olist.test.ts +9 -8
  15. package/src/parser/block/olist.ts +21 -4
  16. package/src/parser/block/paragraph/mention/cite.ts +3 -3
  17. package/src/parser/block/table.test.ts +1 -1
  18. package/src/parser/block/table.ts +19 -9
  19. package/src/parser/block/ulist.test.ts +6 -5
  20. package/src/parser/block/ulist.ts +13 -2
  21. package/src/parser/block.ts +5 -7
  22. package/src/parser/inline/annotation.test.ts +2 -2
  23. package/src/parser/inline/annotation.ts +4 -4
  24. package/src/parser/inline/emphasis.ts +2 -2
  25. package/src/parser/inline/emstrong.ts +4 -4
  26. package/src/parser/inline/extension/index.ts +2 -2
  27. package/src/parser/inline/html.test.ts +13 -13
  28. package/src/parser/inline/html.ts +4 -4
  29. package/src/parser/inline/link.test.ts +5 -5
  30. package/src/parser/inline/link.ts +4 -4
  31. package/src/parser/inline/mark.ts +2 -2
  32. package/src/parser/inline/math.ts +2 -2
  33. package/src/parser/inline/media.test.ts +3 -2
  34. package/src/parser/inline/media.ts +5 -6
  35. package/src/parser/inline/reference.test.ts +2 -2
  36. package/src/parser/inline/reference.ts +4 -4
  37. package/src/parser/inline/ruby.ts +2 -2
  38. package/src/parser/inline/strong.ts +2 -2
  39. package/src/parser/util.ts +56 -9
  40. package/src/renderer/render/math.ts +1 -1
  41. package/tsconfig.json +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.219.4",
3
+ "version": "0.223.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",
@@ -31,38 +31,38 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/dompurify": "2.3.1",
34
- "@types/jquery": "3.5.6",
34
+ "@types/jquery": "3.5.9",
35
35
  "@types/mathjax": "0.0.37",
36
36
  "@types/mocha": "9.0.0",
37
- "@types/power-assert": "1.5.4",
37
+ "@types/power-assert": "1.5.8",
38
38
  "@types/prismjs": "1.16.6",
39
39
  "browserify": "^17.0.0",
40
40
  "browserify-shim": "^3.8.14",
41
- "concurrently": "^6.3.0",
41
+ "concurrently": "^6.4.0",
42
42
  "del": "^6.0.0",
43
43
  "gulp": "^4.0.2",
44
44
  "gulp-derequire": "^3.0.0",
45
- "gulp-footer": "^2.0.2",
45
+ "gulp-footer": "^2.1.0",
46
46
  "gulp-header": "^2.0.9",
47
47
  "gulp-load-plugins": "^2.0.7",
48
48
  "gulp-mocha": "^8.0.0",
49
49
  "gulp-rename": "^2.0.0",
50
50
  "gulp-unassert": "^2.0.0",
51
- "karma": "^6.3.4",
51
+ "karma": "^6.3.9",
52
52
  "karma-chrome-launcher": "^3.1.0",
53
53
  "karma-coverage-istanbul-instrumenter": "^1.0.3",
54
54
  "karma-coverage-istanbul-reporter": "^3.0.3",
55
55
  "karma-espower-preprocessor": "^1.2.0",
56
- "karma-firefox-launcher": "^2.1.1",
56
+ "karma-firefox-launcher": "^2.1.2",
57
57
  "karma-mocha": "^2.0.1",
58
- "mocha": "^9.1.2",
59
- "npm-check-updates": "^11.8.5",
58
+ "mocha": "^9.1.3",
59
+ "npm-check-updates": "^12.0.2",
60
60
  "power-assert": "^1.6.1",
61
61
  "semver": "^7.3.5",
62
- "spica": "0.0.490",
62
+ "spica": "0.0.492",
63
63
  "tsify": "^5.0.4",
64
- "typed-dom": "0.0.244",
65
- "typescript": "4.4.3",
64
+ "typed-dom": "0.0.247",
65
+ "typescript": "4.5.2",
66
66
  "vinyl-buffer": "^1.0.1",
67
67
  "vinyl-source-stream": "^2.0.0"
68
68
  },
@@ -0,0 +1,8 @@
1
+ import { Parser, Tree, Context } from '../../data/parser';
2
+ import { union } from '../../data/parser/union';
3
+
4
+ export function fallback<P extends Parser<unknown>>(parser: P, otherwise: Parser<Tree<P>, Context<P>>): P;
5
+ export function fallback<T, P extends Parser<T>>(parser: P, otherwise: Parser<T, Context<P>>): P;
6
+ export function fallback<T>(parser: Parser<T>, otherwise: Parser<T>): Parser<T> {
7
+ return union([parser, otherwise]);
8
+ }
@@ -2,7 +2,7 @@ import { Parser, Ctx } from '../../data/parser';
2
2
  import { firstline, isEmpty } from '../constraint/line';
3
3
  import { unshift } from 'spica/array';
4
4
 
5
- export function fence<D extends Parser<unknown, C>[], C extends Ctx>(opener: RegExp, limit: number, separation: boolean = true): Parser<string, C, D> {
5
+ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation: boolean = true): Parser<string, C, D> {
6
6
  return source => {
7
7
  if (source === '') return;
8
8
  const matches = source.match(opener);
@@ -16,7 +16,6 @@ describe('Unit: combinator/indent', () => {
16
16
  assert.deepStrictEqual(inspect(parser(' a\n a')), [['a'], ' a']);
17
17
  assert.deepStrictEqual(inspect(parser(' \ta')), [['\ta'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('\ta')), [['a'], '']);
19
- assert.deepStrictEqual(inspect(parser(' a')), [['a'], '']);
20
19
  });
21
20
 
22
21
  });
@@ -12,7 +12,7 @@ export function indent<P extends Parser<unknown>>(parser: P): P;
12
12
  export function indent<T>(parser: Parser<T>): Parser<T> {
13
13
  assert(parser);
14
14
  return bind(match(
15
- /^(?=(([ \t ])\2*))/,
15
+ /^(?=(([ \t])\2*))/,
16
16
  reduce<string[], Parser<string>, string>(
17
17
  ([, indent]) =>
18
18
  some(line(open(indent, source => [[unline(source)], '']))),
@@ -1,6 +1,6 @@
1
- import { Parser, Result, Ctx } from '../../data/parser';
1
+ import { Parser, Result, Ctx, Tree, Context } from '../../data/parser';
2
2
 
3
- export function recover<T, C extends Ctx, D extends Parser<unknown, C>[]>(parser: Parser<T, C, D> | Parser<T, C, [...Parser<never, C>[], Parser<T, C, D>]>, fallback: (source: string, context: C, reason: unknown) => Result<T, C, D>): Parser<T, C, D>;
3
+ export function recover<P extends Parser<unknown>>(parser: P, fallback: (source: string, context: Context<P>, reason: unknown) => Result<Tree<P>>): P;
4
4
  export function recover<T>(parser: Parser<T>, fallback: (source: string, context: Ctx, reason: unknown) => Result<T>): Parser<T> {
5
5
  return (source, context) => {
6
6
  try {
package/src/combinator.ts CHANGED
@@ -18,6 +18,7 @@ export * from './combinator/control/manipulation/convert';
18
18
  export * from './combinator/control/manipulation/trim';
19
19
  export * from './combinator/control/manipulation/duplicate';
20
20
  export * from './combinator/control/manipulation/reverse';
21
+ export * from './combinator/control/manipulation/fallback';
21
22
  export * from './combinator/control/manipulation/recovery';
22
23
  export * from './combinator/control/manipulation/lazy';
23
24
  export * from './combinator/control/monad/fmap';
@@ -1,4 +1,4 @@
1
- import { normalize } from './normalize';
1
+ import { normalize, escape } from './normalize';
2
2
 
3
3
  describe('Unit: parser/normalize', () => {
4
4
  describe('normalize', () => {
@@ -45,11 +45,6 @@ describe('Unit: parser/normalize', () => {
45
45
  assert(normalize('\x7F') === '\uFFFD');
46
46
  });
47
47
 
48
- it('escape', () => {
49
- assert(normalize('\u200B') === '&ZeroWidthSpace;');
50
- assert(normalize('\u200D') === '&zwj;');
51
- });
52
-
53
48
  it('sanitize', () => {
54
49
  assert(normalize('\u2006') === '\uFFFD');
55
50
  assert(normalize('\u202A') === '\uFFFD');
@@ -74,4 +69,12 @@ describe('Unit: parser/normalize', () => {
74
69
 
75
70
  });
76
71
 
72
+ describe('escape', () => {
73
+ it('', () => {
74
+ assert(escape('\u200B') === '&ZeroWidthSpace;');
75
+ assert(escape('\u200D') === '&zwj;');
76
+ });
77
+
78
+ });
79
+
77
80
  });
@@ -1,20 +1,41 @@
1
1
  import { htmlentity } from '../inline/htmlentity';
2
2
  import { eval } from '../../combinator/data/parser';
3
3
 
4
+ const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
5
+ assert(UNICODE_REPLACEMENT_CHARACTER.trim());
6
+
7
+ export function normalize(source: string): string {
8
+ return format(sanitize(source));
9
+ }
10
+
11
+ function format(source: string): string {
12
+ return source
13
+ .replace(/\r\n?/g, '\n');
14
+ }
15
+
16
+ function sanitize(source: string): string {
17
+ return source
18
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(^|[^\u1820\u1821])\u180E/g, `$1${UNICODE_REPLACEMENT_CHARACTER}`)
19
+ .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/g, char =>
20
+ char.length === 1
21
+ ? UNICODE_REPLACEMENT_CHARACTER
22
+ : char);
23
+ }
24
+
4
25
  // https://dev.w3.org/html5/html-author/charref
5
26
  // https://en.wikipedia.org/wiki/Whitespace_character
6
27
  const unreadableHTMLEntityNames = [
7
28
  //'Tab',
8
29
  //'NewLine',
9
- //'NonBreakingSpace',
10
- //'nbsp',
30
+ 'NonBreakingSpace',
31
+ 'nbsp',
11
32
  'shy',
12
- //'ensp',
13
- //'emsp',
33
+ 'ensp',
34
+ 'emsp',
14
35
  'emsp13',
15
36
  'emsp14',
16
- //'numsp',
17
- //'puncsp',
37
+ 'numsp',
38
+ 'puncsp',
18
39
  'ThinSpace',
19
40
  'thinsp',
20
41
  'VeryThinSpace',
@@ -28,7 +49,7 @@ const unreadableHTMLEntityNames = [
28
49
  'zwnj',
29
50
  'lrm',
30
51
  'rlm',
31
- //'MediumSpace',
52
+ 'MediumSpace',
32
53
  'NoBreak',
33
54
  'ApplyFunction',
34
55
  'af',
@@ -78,30 +99,10 @@ const unreadableSpecialCharacters = [
78
99
  // ZERO WIDTH NON-BREAKING SPACE
79
100
  '\uFEFF',
80
101
  ];
81
-
82
- const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
83
- assert(UNICODE_REPLACEMENT_CHARACTER.trim());
84
102
  assert(unreadableSpecialCharacters.every(c => sanitize(c) === UNICODE_REPLACEMENT_CHARACTER));
85
103
 
86
- export function normalize(source: string): string {
87
- return format(sanitize(escape(source)));
88
- }
89
-
90
- function format(source: string): string {
91
- return source
92
- .replace(/\r\n?/g, '\n');
93
- }
94
-
95
- function sanitize(source: string): string {
96
- return source
97
- .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(^|[^\u1820\u1821])\u180E/g, `$1${UNICODE_REPLACEMENT_CHARACTER}`)
98
- .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/g, char =>
99
- char.length === 1
100
- ? UNICODE_REPLACEMENT_CHARACTER
101
- : char);
102
- }
103
-
104
- function escape(source: string): string {
104
+ // 特殊不可視文字はエディタおよびソースビューアの等幅および強調表示により可視化する
105
+ export function escape(source: string): string {
105
106
  return source
106
107
  .replace(unreadableEscapableCharacter, char =>
107
108
  `&${unreadableHTMLEntityNames[unreadableEscapableCharacters.indexOf(char)]};`);
@@ -1,19 +1,21 @@
1
1
  import { IListParser } from '../block';
2
- import { union, inits, some, block, line, validate, indent, context, creator, open, convert, trim, lazy, fmap } from '../../combinator';
2
+ import { union, inits, some, block, line, validate, indent, rewrite, context, creator, open, convert, trim, fallback, lazy, fmap } from '../../combinator';
3
3
  import { ulist_, fillFirstLine } from './ulist';
4
4
  import { olist_ } from './olist';
5
5
  import { inline } from '../inline';
6
+ import { contentline } from '../source';
6
7
  import { html, defrag } from 'typed-dom';
7
8
 
8
9
  export const ilist: IListParser = lazy(() => block(fmap(validate(
9
10
  /^[-+*](?=[^\S\n]|\n[^\S\n]*\S)/,
10
11
  context({ syntax: { inline: { media: false } } },
11
12
  some(creator(union([
12
- fmap(
13
+ fmap(fallback(
13
14
  inits([
14
15
  line(open(/^[-+*](?:$|\s)/, trim(some(inline)), true)),
15
16
  indent(union([ulist_, olist_, ilist_])),
16
17
  ]),
18
+ rewrite(contentline, source => [[html('span', source.replace('\n', ''))], ''])),
17
19
  ns => [html('li', defrag(fillFirstLine(ns)))]),
18
20
  ]))))),
19
21
  es => [
@@ -26,15 +26,8 @@ describe('Unit: parser/block/olist', () => {
26
26
  assert.deepStrictEqual(inspect(parser('1.1.')), undefined);
27
27
  assert.deepStrictEqual(inspect(parser('1.a')), undefined);
28
28
  assert.deepStrictEqual(inspect(parser('1.\n')), undefined);
29
- assert.deepStrictEqual(inspect(parser('1. a\n 1. a\n 1. a')), undefined);
30
- assert.deepStrictEqual(inspect(parser('1. \n1 ')), undefined);
31
- assert.deepStrictEqual(inspect(parser('1. \n1--')), undefined);
32
- assert.deepStrictEqual(inspect(parser('1. \n1--. ')), undefined);
33
- assert.deepStrictEqual(inspect(parser('1. !http://host')), [['<ol><li>!<a href="http://host" target="_blank">http://host</a></li></ol>'], '']);
34
29
  assert.deepStrictEqual(inspect(parser('(1)')), undefined);
35
30
  assert.deepStrictEqual(inspect(parser('(1)\n')), undefined);
36
- assert.deepStrictEqual(inspect(parser('(1) \n(1)--')), undefined);
37
- assert.deepStrictEqual(inspect(parser('(1) \n(1)-- ')), undefined);
38
31
  assert.deepStrictEqual(inspect(parser('(I) ')), undefined);
39
32
  assert.deepStrictEqual(inspect(parser('(A) ')), undefined);
40
33
  assert.deepStrictEqual(inspect(parser(' 1.')), undefined);
@@ -69,6 +62,8 @@ describe('Unit: parser/block/olist', () => {
69
62
  assert.deepStrictEqual(inspect(parser('(1) \n(1)')), [['<ol data-format="paren"><li></li><li></li></ol>'], '']);
70
63
  // filled
71
64
  assert.deepStrictEqual(inspect(parser('(1) \n(1) ')), [['<ol data-format="paren"><li></li><li></li></ol>'], '']);
65
+ // invalid
66
+ assert.deepStrictEqual(inspect(parser('0. \n0 ')), [['<ol><li></li><li><span class="invalid">0 </span></li></ol>'], '']);
72
67
  });
73
68
 
74
69
  it('nest', () => {
@@ -78,9 +73,11 @@ describe('Unit: parser/block/olist', () => {
78
73
  assert.deepStrictEqual(inspect(parser('0. 1\n 0. ')), [['<ol><li>1<ol><li></li></ol></li></ol>'], '']);
79
74
  assert.deepStrictEqual(inspect(parser('0. 1\n 0.\n')), [['<ol><li>1<ol><li></li></ol></li></ol>'], '']);
80
75
  assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2')), [['<ol><li>1<ol><li>2</li></ol></li></ol>'], '']);
76
+ assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2\n0. 3')), [['<ol><li>1<ol><li>2</li></ol></li><li>3</li></ol>'], '']);
81
77
  assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2\n 0. 3')), [['<ol><li>1<ol><li>2</li><li>3</li></ol></li></ol>'], '']);
82
78
  assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2\n 0. 3')), [['<ol><li>1<ol><li>2<ol><li>3</li></ol></li></ol></li></ol>'], '']);
83
- assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2\n0. 3')), [['<ol><li>1<ol><li>2</li></ol></li><li>3</li></ol>'], '']);
79
+ assert.deepStrictEqual(inspect(parser('0. 1\n 0. 2\n 0. 3')), [['<ol><li>1<ol><li>2</li></ol></li><li><span class="invalid"> 0. 3</span></li></ol>'], '']);
80
+ assert.deepStrictEqual(inspect(parser('0. !http://host')), [['<ol><li>!<a href="http://host" target="_blank">http://host</a></li></ol>'], '']);
84
81
  });
85
82
 
86
83
  it('index', () => {
@@ -102,6 +99,10 @@ describe('Unit: parser/block/olist', () => {
102
99
  assert.deepStrictEqual(inspect(parser('(1)-1-1 ')), [['<ol data-format="paren"><li data-marker="(1)-1-1"></li></ol>'], '']);
103
100
  assert.deepStrictEqual(inspect(parser('(1) \n(1)-')), [['<ol data-format="paren"><li></li><li data-marker="(1)-"></li></ol>'], '']);
104
101
  assert.deepStrictEqual(inspect(parser('(1) \n(1)-1')), [['<ol data-format="paren"><li></li><li data-marker="(1)-1"></li></ol>'], '']);
102
+ assert.deepStrictEqual(inspect(parser('1. \n1--')), [['<ol><li></li><li><span class="invalid">1--</span></li></ol>'], '']);
103
+ assert.deepStrictEqual(inspect(parser('1. \n1--. ')), [['<ol><li></li><li><span class="invalid">1--. </span></li></ol>'], '']);
104
+ assert.deepStrictEqual(inspect(parser('(1) \n(1)--')), [['<ol data-format="paren"><li></li><li><span class="invalid">(1)--</span></li></ol>'], '']);
105
+ assert.deepStrictEqual(inspect(parser('(1) \n(1)-- ')), [['<ol data-format="paren"><li></li><li><span class="invalid">(1)-- </span></li></ol>'], '']);
105
106
  });
106
107
 
107
108
  it('type', () => {
@@ -1,9 +1,10 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { OListParser } from '../block';
3
- import { union, inits, subsequence, some, block, line, indent, focus, context, creator, open, match, convert, trim, trimStart, lazy, fmap } from '../../combinator';
3
+ import { union, inits, subsequence, some, block, line, indent, focus, rewrite, context, creator, open, match, convert, trim, trimStart, fallback, lazy, fmap } from '../../combinator';
4
4
  import { checkbox, ulist_, fillFirstLine } from './ulist';
5
5
  import { ilist_ } from './ilist';
6
6
  import { inline } from '../inline';
7
+ import { contentline } from '../source';
7
8
  import { html, define, defrag } from 'typed-dom';
8
9
  import { memoize } from 'spica/memoize';
9
10
  import { shift } from 'spica/array';
@@ -17,11 +18,12 @@ export const olist: OListParser = lazy(() => block(match(
17
18
  const list = (type: string, delim: string): OListParser => fmap(
18
19
  context({ syntax: { inline: { media: false } } },
19
20
  some(creator(union([
20
- fmap(
21
+ fmap(fallback(
21
22
  inits([
22
23
  line(open(items[delim], trim(subsequence([checkbox, trimStart(some(inline))])), true)),
23
24
  indent(union([ulist_, olist_, ilist_])),
24
25
  ]),
26
+ iitem),
25
27
  (ns: [string, ...(HTMLElement | string)[]]) => [html('li', { 'data-marker': ns[0] }, defrag(fillFirstLine(shift(ns)[1])))]),
26
28
  ])))),
27
29
  es => [format(html('ol', es), type, delim)]);
@@ -45,6 +47,16 @@ export const olist_: OListParser = convert(
45
47
  .replace(/^\(((?:[0-9]+|[a-z]+))\)?((?:-(?!-)[0-9]*)*(?=$|\n))/, `($1)$2 `),
46
48
  olist);
47
49
 
50
+ const iitem = rewrite(contentline, source => [[
51
+ '',
52
+ html('span', {
53
+ class: 'invalid',
54
+ 'data-invalid-syntax': 'listitem',
55
+ 'data-invalid-type': 'syntax',
56
+ 'data-invalid-description': 'Fix the indent or the head of list items.',
57
+ }, source.replace('\n', ''))
58
+ ], '']);
59
+
48
60
  function type(index: string): string {
49
61
  switch (index) {
50
62
  case 'i':
@@ -102,8 +114,13 @@ function format(el: HTMLOListElement, type: string, delim: string): HTMLOListEle
102
114
  const marker = el.firstElementChild?.getAttribute('data-marker')!.match(initial(type))?.[0] ?? '';
103
115
  for (let es = el.children, len = es.length, i = 0; i < len; ++i) {
104
116
  const el = es[i];
105
- if (el.getAttribute('data-marker') !== marker) break;
106
- el.removeAttribute('data-marker');
117
+ switch (el.getAttribute('data-marker')) {
118
+ case '':
119
+ case marker:
120
+ el.removeAttribute('data-marker');
121
+ continue;
122
+ }
123
+ break;
107
124
  }
108
125
  return el;
109
126
  }
@@ -10,10 +10,10 @@ export const cite: ParagraphParser.MentionParser.CiteParser = creator(line(fmap(
10
10
  str(/^>*(?=>>)/),
11
11
  anchor,
12
12
  ]))),
13
- ([el, str = '']: [HTMLElement, string?]) => [
13
+ ([el, quotes = '']: [HTMLElement, string?]) => [
14
14
  html('span', { class: 'cite' }, defrag([
15
- str + '>',
16
- define(el, { 'data-depth': `${str.length + 1}` }, el.innerText.slice(1)),
15
+ quotes + '>',
16
+ define(el, { 'data-depth': `${quotes.length + 1}` }, el.innerText.slice(1)),
17
17
  ])),
18
18
  html('br'),
19
19
  ])));
@@ -13,7 +13,6 @@ describe('Unit: parser/block/table', () => {
13
13
  assert.deepStrictEqual(inspect(parser('||')), undefined);
14
14
  assert.deepStrictEqual(inspect(parser('|||')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('|\n|')), undefined);
16
- assert.deepStrictEqual(inspect(parser('|\n|\n|')), undefined);
17
16
  assert.deepStrictEqual(inspect(parser('|h')), undefined);
18
17
  assert.deepStrictEqual(inspect(parser('|h')), undefined);
19
18
  assert.deepStrictEqual(inspect(parser('|h\n')), undefined);
@@ -26,6 +25,7 @@ describe('Unit: parser/block/table', () => {
26
25
  });
27
26
 
28
27
  it('valid', () => {
28
+ assert.deepStrictEqual(inspect(parser('|\n|\n|')), [['<table><thead><tr></tr></thead><tbody><tr class="invalid"><td>|</td></tr><tr></tr></tbody></table>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser('|\n|-\n|')), [['<table><thead><tr></tr></thead><tbody><tr></tr></tbody></table>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('||\n|-|\n||')), [['<table><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr></tbody></table>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('|||\n|-|-|\n|||')), [['<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr></tbody></table>'], '']);
@@ -1,6 +1,7 @@
1
1
  import { TableParser } from '../block';
2
- import { union, sequence, some, block, line, validate, focus, creator, surround, open, lazy, fmap } from '../../combinator';
2
+ import { union, sequence, some, block, line, validate, focus, rewrite, creator, surround, open, fallback, lazy, fmap } from '../../combinator';
3
3
  import { inline } from '../inline';
4
+ import { contentline } from '../source';
4
5
  import { html, defrag } from 'typed-dom';
5
6
  import { push } from 'spica/array';
6
7
 
@@ -9,7 +10,7 @@ import AlignParser = TableParser.AlignParser;
9
10
  import CellParser = TableParser.CellParser;
10
11
 
11
12
  export const table: TableParser = lazy(() => block(fmap(validate(
12
- /^\|[^\n]*(?:\n\|[^\n]*){2,}/,
13
+ /^\|[^\n]*(?:\n\|[^\n]*){2}/,
13
14
  sequence([
14
15
  row(some(head), true),
15
16
  row(some(align), false),
@@ -18,13 +19,21 @@ export const table: TableParser = lazy(() => block(fmap(validate(
18
19
  rows => [
19
20
  html('table', [
20
21
  html('thead', [rows.shift()!]),
21
- html('tbody', format(rows, push([], rows.shift()!.children).map(el => el.textContent!))),
22
+ html('tbody', format(rows)),
22
23
  ]),
23
24
  ])));
24
25
 
25
- const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => creator(fmap(
26
+ const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => creator(fallback(fmap(
26
27
  line(surround(/^(?=\|)/, some(union([parser])), /^\|?\s*$/, optional)),
27
- es => [html('tr', es)]));
28
+ es => [html('tr', es)]),
29
+ rewrite(contentline, source => [[
30
+ html('tr', {
31
+ class: 'invalid',
32
+ 'data-invalid-syntax': 'tablerow',
33
+ 'data-invalid-type': 'syntax',
34
+ 'data-invalid-description': 'Invalid table row.',
35
+ }, [html('td', source.replace('\n', ''))])
36
+ ], ''])));
28
37
 
29
38
  const align: AlignParser = creator(fmap(open(
30
39
  '|',
@@ -49,11 +58,12 @@ const data: CellParser.DataParser = creator(fmap(
49
58
  cell,
50
59
  ns => [html('td', defrag(ns))]));
51
60
 
52
- function format(rows: HTMLTableRowElement[], aligns: string[]): HTMLTableRowElement[] {
53
- assert(aligns.length > 0);
61
+ function format(rows: HTMLTableRowElement[]): HTMLTableRowElement[] {
62
+ const aligns = rows[0].classList.contains('invalid')
63
+ ? []
64
+ : push([], rows.shift()!.children).map(el => el.textContent!);
54
65
  for (let i = 0, len = rows.length; i < len; ++i) {
55
- const row = rows[i];
56
- const cols = row.children;
66
+ const cols = rows[i].children;
57
67
  for (let i = 0, len = cols.length; i < len; ++i) {
58
68
  if (i > 0 && !aligns[i]) {
59
69
  aligns[i] = aligns[i - 1];
@@ -17,11 +17,6 @@ describe('Unit: parser/block/ulist', () => {
17
17
  assert.deepStrictEqual(inspect(parser('-[ ]')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('-[x]')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('-\n')), undefined);
20
- assert.deepStrictEqual(inspect(parser('-\n+')), undefined);
21
- assert.deepStrictEqual(inspect(parser('-\n0')), undefined);
22
- assert.deepStrictEqual(inspect(parser('-\n -\n 0')), undefined);
23
- assert.deepStrictEqual(inspect(parser('- 0\n - 0\n - 0')), undefined);
24
- assert.deepStrictEqual(inspect(parser('- !http://host')), [['<ul><li>!<a href="http://host" target="_blank">http://host</a></li></ul>'], '']);
25
20
  assert.deepStrictEqual(inspect(parser(' -')), undefined);
26
21
  assert.deepStrictEqual(inspect(parser('+')), undefined);
27
22
  assert.deepStrictEqual(inspect(parser('*')), undefined);
@@ -43,6 +38,9 @@ describe('Unit: parser/block/ulist', () => {
43
38
  // filled
44
39
  assert.deepStrictEqual(inspect(parser('- 1\n- 2')), [['<ul><li>1</li><li>2</li></ul>'], '']);
45
40
  assert.deepStrictEqual(inspect(parser('- 1\n- 2\n- 3')), [['<ul><li>1</li><li>2</li><li>3</li></ul>'], '']);
41
+ // invalid
42
+ assert.deepStrictEqual(inspect(parser('-\n+')), [['<ul><li></li><li><span class="invalid">+</span></li></ul>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('-\n0')), [['<ul><li></li><li><span class="invalid">0</span></li></ul>'], '']);
46
44
  });
47
45
 
48
46
  it('nest', () => {
@@ -51,11 +49,14 @@ describe('Unit: parser/block/ulist', () => {
51
49
  assert.deepStrictEqual(inspect(parser('- 1\n - 2\n- 3')), [['<ul><li>1<ul><li>2</li></ul></li><li>3</li></ul>'], '']);
52
50
  assert.deepStrictEqual(inspect(parser('- 1\n - 2\n - 3')), [['<ul><li>1<ul><li>2</li><li>3</li></ul></li></ul>'], '']);
53
51
  assert.deepStrictEqual(inspect(parser('- 1\n - 2\n - 3')), [['<ul><li>1<ul><li>2<ul><li>3</li></ul></li></ul></li></ul>'], '']);
52
+ assert.deepStrictEqual(inspect(parser('- 1\n - 2\n - 3')), [['<ul><li>1<ul><li>2</li></ul></li><li><span class="invalid"> - 3</span></li></ul>'], '']);
53
+ assert.deepStrictEqual(inspect(parser('-\n -\n +\n -\n +\n+')), [['<ul><li><br><ul><li></li><li><span class="invalid">+</span></li><li></li><li><span class="invalid">+</span></li></ul></li><li><span class="invalid">+</span></li></ul>'], '']);
54
54
  assert.deepStrictEqual(inspect(parser('- 1\n + 2')), [['<ul><li>1<ul class="invalid"><li>2</li></ul></li></ul>'], '']);
55
55
  assert.deepStrictEqual(inspect(parser('- 1\n 0')), [['<ul><li>1<ol><li></li></ol></li></ul>'], '']);
56
56
  assert.deepStrictEqual(inspect(parser('- 1\n 0.')), [['<ul><li>1<ol><li></li></ol></li></ul>'], '']);
57
57
  assert.deepStrictEqual(inspect(parser('- 1\n 0. ')), [['<ul><li>1<ol><li></li></ol></li></ul>'], '']);
58
58
  assert.deepStrictEqual(inspect(parser('- 1\n 0. 2')), [['<ul><li>1<ol><li>2</li></ol></li></ul>'], '']);
59
+ assert.deepStrictEqual(inspect(parser('- !http://host')), [['<ul><li>!<a href="http://host" target="_blank">http://host</a></li></ul>'], '']);
59
60
  });
60
61
 
61
62
  it('checkbox', () => {
@@ -1,20 +1,22 @@
1
1
  import { UListParser } from '../block';
2
- import { union, inits, subsequence, some, block, line, validate, indent, focus, context, creator, open, convert, trim, trimStart, lazy, fmap } from '../../combinator';
2
+ import { union, inits, subsequence, some, block, line, validate, indent, focus, rewrite, context, creator, open, convert, trim, trimStart, fallback, lazy, fmap } from '../../combinator';
3
3
  import { olist_ } from './olist';
4
4
  import { ilist_ } from './ilist';
5
5
  import { inline } from '../inline';
6
6
  import { html, defrag } from 'typed-dom';
7
7
  import { unshift } from 'spica/array';
8
+ import { contentline } from '../source';
8
9
 
9
10
  export const ulist: UListParser = lazy(() => block(fmap(validate(
10
11
  /^-(?=[^\S\n]|\n[^\S\n]*\S)/,
11
12
  context({ syntax: { inline: { media: false } } },
12
13
  some(creator(union([
13
- fmap(
14
+ fmap(fallback(
14
15
  inits([
15
16
  line(open(/^-(?:$|\s)/, trim(subsequence([checkbox, trimStart(some(inline))])), true)),
16
17
  indent(union([ulist_, olist_, ilist_])),
17
18
  ]),
19
+ iitem),
18
20
  ns => [html('li', defrag(fillFirstLine(ns)))]),
19
21
  ]))))),
20
22
  es => [format(html('ul', es))])));
@@ -29,6 +31,15 @@ export const ulist_: UListParser = convert(
29
31
  source => source.replace(/^-(?=$|\n)/, `$& `),
30
32
  ulist);
31
33
 
34
+ const iitem = rewrite(contentline, source => [[
35
+ html('span', {
36
+ class: 'invalid',
37
+ 'data-invalid-syntax': 'listitem',
38
+ 'data-invalid-type': 'syntax',
39
+ 'data-invalid-description': 'Fix the indent or the head of list items.',
40
+ }, source.replace('\n', ''))
41
+ ], '']);
42
+
32
43
  export function fillFirstLine(ns: (HTMLElement | string)[]): (HTMLElement | string)[] {
33
44
  return ns.length === 1
34
45
  && typeof ns[0] === 'object'
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { MarkdownParser } from '../../markdown';
3
- import { union, reset, creator, open, recover } from '../combinator';
3
+ import { union, reset, creator, open, fallback, recover } from '../combinator';
4
4
  import { emptyline } from './source';
5
5
  import { horizontalrule } from './block/horizontalrule';
6
6
  import { heading } from './block/heading';
@@ -49,12 +49,10 @@ export const block: BlockParser = creator(error(
49
49
  paragraph
50
50
  ]))));
51
51
 
52
- function error(parser: MarkdownParser.BlockParser): MarkdownParser.BlockParser {
53
- return recover(
54
- union([
55
- open('\0', source => { throw new Error(source.split('\n', 1)[0]); }),
56
- parser,
57
- ]),
52
+ function error(parser: BlockParser): BlockParser {
53
+ return recover<BlockParser>(fallback(
54
+ open('\0', source => { throw new Error(source.split('\n', 1)[0]); }),
55
+ parser),
58
56
  (source, { id }, reason) => [[
59
57
  html('h1',
60
58
  {
@@ -14,8 +14,6 @@ describe('Unit: parser/inline/annotation', () => {
14
14
  assert.deepStrictEqual(inspect(parser('(())')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('(()))')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('(( ))')), undefined);
17
- assert.deepStrictEqual(inspect(parser('(( a))')), undefined);
18
- assert.deepStrictEqual(inspect(parser('(( a ))')), undefined);
19
17
  assert.deepStrictEqual(inspect(parser('((\n))')), undefined);
20
18
  assert.deepStrictEqual(inspect(parser('((\na))')), undefined);
21
19
  assert.deepStrictEqual(inspect(parser('((\\ a))')), undefined);
@@ -34,6 +32,8 @@ describe('Unit: parser/inline/annotation', () => {
34
32
  });
35
33
 
36
34
  it('basic', () => {
35
+ assert.deepStrictEqual(inspect(parser('(( a))')), [['<sup class="annotation">a</sup>'], '']);
36
+ assert.deepStrictEqual(inspect(parser('(( a ))')), [['<sup class="annotation">a</sup>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser('((a))')), [['<sup class="annotation">a</sup>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser('((a ))')), [['<sup class="annotation">a</sup>'], '']);
39
39
  assert.deepStrictEqual(inspect(parser('((a ))')), [['<sup class="annotation">a</sup>'], '']);
@@ -2,13 +2,13 @@ import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
3
  import { union, some, validate, guard, context, creator, surround, lazy, fmap } from '../../combinator';
4
4
  import { inline } from '../inline';
5
- import { startTight, trimEnd } from '../util';
5
+ import { startLoose, visible, trimNode } from '../util';
6
6
  import { html, defrag } from 'typed-dom';
7
7
 
8
8
  export const annotation: AnnotationParser = lazy(() => creator(validate('((', '))', '\n', fmap(surround(
9
9
  '((',
10
10
  guard(context => context.syntax?.inline?.annotation ?? true,
11
- startTight(
11
+ startLoose(visible(
12
12
  context({ syntax: { inline: {
13
13
  annotation: false,
14
14
  // Redundant
@@ -20,6 +20,6 @@ export const annotation: AnnotationParser = lazy(() => creator(validate('((', ')
20
20
  //link: true,
21
21
  //autolink: true,
22
22
  }}, state: undefined },
23
- union([some(inline, ')', /^\\?\n/)])))),
23
+ union([some(inline, ')', /^\\?\n/)]))))),
24
24
  '))'),
25
- ns => [html('sup', { class: 'annotation' }, trimEnd(defrag(ns)))]))));
25
+ ns => [html('sup', { class: 'annotation' }, trimNode(defrag(ns)))]))));
@@ -3,7 +3,7 @@ import { union, some, creator, surround, close, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { strong } from './strong';
5
5
  import { str } from '../source';
6
- import { startTight, verifyEndTight, trimEndBR } from '../util';
6
+ import { startTight, isEndTightNodes, trimEndBR } from '../util';
7
7
  import { html, defrag } from 'typed-dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
@@ -12,7 +12,7 @@ export const emphasis: EmphasisParser = lazy(() => creator(surround(close(
12
12
  startTight(some(union([strong, some(inline, '*')]))),
13
13
  str('*'), false,
14
14
  ([as, bs, cs], rest) =>
15
- verifyEndTight(bs)
15
+ isEndTightNodes(bs)
16
16
  ? [[html('em', defrag(trimEndBR(bs)))], rest]
17
17
  : [unshift(as, bs), cs[0] + rest],
18
18
  ([as, bs], rest) => [unshift(as, bs), rest])));