securemark 0.267.0 → 0.268.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.267.0",
3
+ "version": "0.268.1",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -34,13 +34,13 @@
34
34
  "@types/mocha": "10.0.1",
35
35
  "@types/power-assert": "1.5.8",
36
36
  "@types/prismjs": "1.26.0",
37
- "@typescript-eslint/parser": "^5.49.0",
37
+ "@typescript-eslint/parser": "^5.53.0",
38
38
  "babel-loader": "^9.1.2",
39
39
  "babel-plugin-unassert": "^3.2.0",
40
40
  "concurrently": "^7.6.0",
41
- "eslint": "^8.32.0",
42
- "eslint-plugin-redos": "^4.4.3",
43
- "eslint-webpack-plugin": "^3.2.0",
41
+ "eslint": "^8.35.0",
42
+ "eslint-plugin-redos": "^4.4.5",
43
+ "eslint-webpack-plugin": "^4.0.0",
44
44
  "glob": "^8.1.0",
45
45
  "karma": "^6.4.1",
46
46
  "karma-chrome-launcher": "^3.1.1",
@@ -49,12 +49,12 @@
49
49
  "karma-mocha": "^2.0.1",
50
50
  "karma-power-assert": "^1.0.0",
51
51
  "mocha": "^10.2.0",
52
- "npm-check-updates": "^16.6.3",
52
+ "npm-check-updates": "^16.7.9",
53
53
  "semver": "^7.3.8",
54
54
  "spica": "0.0.719",
55
55
  "ts-loader": "^9.4.2",
56
- "typed-dom": "^0.0.315",
57
- "typescript": "4.9.4",
56
+ "typed-dom": "^0.0.316",
57
+ "typescript": "4.9.5",
58
58
  "webpack": "^5.75.0",
59
59
  "webpack-cli": "^5.0.1",
60
60
  "webpack-merge": "^5.8.0"
@@ -1,6 +1,6 @@
1
1
  import { Parser, exec } from '../../data/parser';
2
2
  import { Memo } from '../../data/parser/context/memo';
3
- import { firstline, isEmpty } from './line';
3
+ import { firstline, isBlank } from './line';
4
4
 
5
5
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
6
6
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
@@ -11,7 +11,7 @@ export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
11
11
  const result = parser({ source, context });
12
12
  if (result === undefined) return;
13
13
  const rest = exec(result);
14
- if (separation && !isEmpty(firstline(rest))) return;
14
+ if (separation && !isBlank(firstline(rest))) return;
15
15
  assert(rest === '' || source[source.length - rest.length - 1] === '\n');
16
16
  return rest === '' || source[source.length - rest.length - 1] === '\n'
17
17
  ? result
@@ -14,7 +14,7 @@ export function line<T>(parser: Parser<T>): Parser<T> {
14
14
  assert(check(line, result));
15
15
  context.offset -= source.length - line.length;
16
16
  if (result === undefined) return;
17
- return isEmpty(exec(result))
17
+ return isBlank(exec(result))
18
18
  ? [eval(result), source.slice(line.length)]
19
19
  : undefined;
20
20
  };
@@ -32,7 +32,7 @@ export function firstline(source: string): string {
32
32
  }
33
33
  }
34
34
 
35
- export function isEmpty(line: string): boolean {
35
+ export function isBlank(line: string): boolean {
36
36
  return line === ''
37
37
  || line === '\n'
38
38
  || line.trimStart() === '';
@@ -1,5 +1,5 @@
1
1
  import { Parser, Ctx } from '../../data/parser';
2
- import { firstline, isEmpty } from '../constraint/line';
2
+ import { firstline, isBlank } from '../constraint/line';
3
3
  import { unshift } from 'spica/array';
4
4
 
5
5
  export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
@@ -13,20 +13,20 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
13
13
  if (matches[0].indexOf(delim, delim.length) !== -1) return;
14
14
  let rest = source.slice(matches[0].length);
15
15
  // Prevent annoying parsing in editing.
16
- if (isEmpty(firstline(rest)) && firstline(rest.slice(firstline(rest).length)).trimEnd() !== delim) return;
16
+ if (isBlank(firstline(rest)) && firstline(rest.slice(firstline(rest).length)).trimEnd() !== delim) return;
17
17
  let block = '';
18
18
  let closer = '';
19
19
  let overflow = '';
20
20
  for (let count = 1; ; ++count) {
21
21
  if (rest === '') break;
22
22
  const line = firstline(rest);
23
- if ((closer || count > limit + 1) && isEmpty(line)) break;
23
+ if ((closer || count > limit + 1) && isBlank(line)) break;
24
24
  if(closer) {
25
25
  overflow += line;
26
26
  }
27
27
  if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
28
28
  closer = line;
29
- if (isEmpty(firstline(rest.slice(line.length)))) {
29
+ if (isBlank(firstline(rest.slice(line.length)))) {
30
30
  rest = rest.slice(line.length);
31
31
  break;
32
32
  }
@@ -26,7 +26,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
26
26
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
27
  memo: new Memo({ targets: State.backtrackers }),
28
28
  };
29
- if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
29
+ if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
30
30
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
31
31
  assert(!settings.id);
32
32
  type Block = readonly [segment: string, blocks: readonly HTMLElement[], url: string];
@@ -32,7 +32,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
32
32
  },
33
33
  memo: new Memo({ targets: State.backtrackers }),
34
34
  };
35
- if (context.id?.match(/[^0-9a-z-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
35
+ if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
36
36
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
37
37
  const node = frag();
38
38
  let index = 0;
@@ -1,10 +1,9 @@
1
1
  import { autolink } from './autolink';
2
- import { some } from '../combinator';
3
2
  import { inspect } from '../debug.test';
4
3
 
5
4
  describe('Unit: parser/autolink', () => {
6
5
  describe('autolink', () => {
7
- const parser = (source: string) => some(autolink)({ source, context: {} });
6
+ const parser = (source: string) => autolink({ source, context: {} });
8
7
 
9
8
  it('basic', () => {
10
9
  assert.deepStrictEqual(inspect(parser(' http://host')), [[' ', '<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
@@ -1,28 +1,21 @@
1
1
  import { MarkdownParser } from '../../markdown';
2
- import { union, lazy } from '../combinator';
2
+ import { union, tails, subsequence, some, line, focus, lazy } from '../combinator';
3
+ import { link } from './inline/link';
3
4
  import { autolink as autolink_ } from './inline/autolink';
4
- import { linebreak, unescsource } from './source';
5
+ import { linebreak, unescsource, str } from './source';
6
+ import { format } from './util';
5
7
 
6
8
  export import AutolinkParser = MarkdownParser.AutolinkParser;
7
9
 
8
- const delimiter = /[@#>0-9A-Za-z\n]|\S[#>]/;
10
+ export const autolink: AutolinkParser = lazy(() => some(line(subsequence([
11
+ lineurl,
12
+ some(union([
13
+ autolink_,
14
+ linebreak,
15
+ unescsource,
16
+ ])),
17
+ ]))));
9
18
 
10
- export const autolink: AutolinkParser = ({ source, context }) => {
11
- if (source === '') return;
12
- assert(source[0] !== '\x1B');
13
- const i = source.search(delimiter);
14
- switch (i) {
15
- case -1:
16
- return [[source], ''];
17
- case 0:
18
- return parser({ source, context });
19
- default:
20
- return [[source.slice(0, i)], source.slice(i)];
21
- }
22
- };
23
-
24
- const parser: AutolinkParser = lazy(() => union([
25
- autolink_,
26
- linebreak,
27
- unescsource
28
- ]));
19
+ export const lineurl: AutolinkParser.LineUrlParser = lazy(() => focus(
20
+ /^!?https?:\/\/\S+(?=[^\S\n]*(?:$|\n))/,
21
+ format(tails([str('!'), link]))));
@@ -25,7 +25,7 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
25
25
  convert(unindent, source)),
26
26
  rewrite(
27
27
  some(contentline, opener),
28
- convert(unindent, fmap(some(autolink), ns => [html('pre', defrag(ns))]))),
28
+ convert(unindent, fmap(autolink, ns => [html('pre', defrag(ns))]))),
29
29
  ]))),
30
30
  ns => [html('blockquote', ns)]));
31
31
 
@@ -1,6 +1,6 @@
1
1
  import { CodeBlockParser } from '../block';
2
2
  import { eval } from '../../combinator/data/parser';
3
- import { some, block, validate, fence, clear, fmap } from '../../combinator';
3
+ import { block, validate, fence, clear, fmap } from '../../combinator';
4
4
  import { autolink } from '../autolink';
5
5
  import { html, defrag } from 'typed-dom/dom';
6
6
 
@@ -69,6 +69,6 @@ export const codeblock: CodeBlockParser = block(validate('```', fmap(
69
69
  params.lang
70
70
  ? context.caches?.code?.get(`${params.lang ?? ''}\n${body.slice(0, -1)}`)?.cloneNode(true).childNodes ||
71
71
  body.slice(0, -1) || undefined
72
- : defrag(eval(some(autolink)({ source: body.slice(0, -1), context }), [])));
72
+ : defrag(eval(autolink({ source: body.slice(0, -1), context }), [])));
73
73
  return [el];
74
74
  })));
@@ -30,15 +30,15 @@ describe('Unit: parser/block/olist', () => {
30
30
  assert.deepStrictEqual(inspect(parser('(1)\n')), undefined);
31
31
  assert.deepStrictEqual(inspect(parser('(I) ')), undefined);
32
32
  assert.deepStrictEqual(inspect(parser('(A) ')), undefined);
33
- assert.deepStrictEqual(inspect(parser(' 1.')), undefined);
33
+ assert.deepStrictEqual(inspect(parser(' 1. ')), undefined);
34
34
  });
35
35
 
36
36
  it('single', () => {
37
37
  // pending
38
38
  assert.deepStrictEqual(inspect(parser('1. ')), [['<ol><li></li></ol>'], '']);
39
39
  // filled
40
- assert.deepStrictEqual(inspect(parser('1. \\')), [['<ol><li></li></ol>'], '']);
41
- assert.deepStrictEqual(inspect(parser('1. \\\n')), [['<ol><li></li></ol>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('1. \\')), [['<ol><li id="index::\\">\\</li></ol>'], '']);
41
+ assert.deepStrictEqual(inspect(parser('1. \\\n')), [['<ol><li id="index::\\">\\</li></ol>'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('1. -')), [['<ol><li id="index::-">-</li></ol>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser('1. -\n')), [['<ol><li id="index::-">-</li></ol>'], '']);
44
44
  // pending
@@ -91,7 +91,7 @@ describe('Unit: parser/block/olist', () => {
91
91
  assert.deepStrictEqual(inspect(parser('01. ')), [['<ol><li data-marker="01."></li></ol>'], '']);
92
92
  assert.deepStrictEqual(inspect(parser('0.\n1')), [['<ol><li></li><li data-marker="1."></li></ol>'], '']);
93
93
  assert.deepStrictEqual(inspect(parser('8.\n9')), [['<ol><li data-marker="8."></li><li data-marker="9."></li></ol>'], '']);
94
- assert.deepStrictEqual(inspect(parser('9.\n9')), [['<ol><li data-marker="9."></li><li data-marker="9."></li></ol>'], '']);
94
+ assert.deepStrictEqual(inspect(parser('9.\n9')), [['<ol><li data-marker="9."></li><li data-marker="9." class="invalid"></li></ol>'], '']);
95
95
  });
96
96
 
97
97
  it('branch', () => {
@@ -6,7 +6,7 @@ import { ilist_ } from './ilist';
6
6
  import { inline, indexee, indexer } from '../inline';
7
7
  import { contentline } from '../source';
8
8
  import { State } from '../context';
9
- import { trimBlank } from '../visibility';
9
+ import { visualize, trimBlank } from '../visibility';
10
10
  import { memoize } from 'spica/memoize';
11
11
  import { shift } from 'spica/array';
12
12
  import { html, define, defrag } from 'typed-dom/dom';
@@ -27,17 +27,17 @@ export const olist: OListParser = lazy(() => block(validate(
27
27
  export const olist_: OListParser = lazy(() => block(union([
28
28
  match(
29
29
  openers['.'],
30
- memoize(ms => list(type(ms[1]), '.'), ms => type(ms[1]).charCodeAt(0) || 0, [])),
30
+ memoize(ms => list(type(ms[1]), '.'), ms => index(ms[1]), [])),
31
31
  match(
32
32
  openers['('],
33
- memoize(ms => list(type(ms[1]), '('), ms => type(ms[1]).charCodeAt(0) || 0, [])),
33
+ memoize(ms => list(type(ms[1]), '('), ms => index(ms[1]), [])),
34
34
  ])));
35
35
 
36
36
  const list = (type: string, form: string): OListParser.ListParser => fmap(
37
37
  some(creation(1, false, union([
38
38
  indexee(fmap(fallback(
39
39
  inits([
40
- line(open(heads[form], subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
40
+ line(open(heads[form], subsequence([checkbox, trimBlank(visualize(some(union([indexer, inline]))))]), true)),
41
41
  indent(union([ulist_, olist_, ilist_])),
42
42
  ]),
43
43
  invalid),
@@ -61,14 +61,29 @@ export const invalid = rewrite(
61
61
  '',
62
62
  html('span', {
63
63
  class: 'invalid',
64
- 'data-invalid-syntax': 'listitem',
64
+ 'data-invalid-syntax': 'list',
65
65
  'data-invalid-type': 'syntax',
66
66
  'data-invalid-message': 'Fix the indent or the head of the list item',
67
67
  }, source.replace('\n', ''))
68
68
  ], '']);
69
69
 
70
- function type(index: string): string {
71
- switch (index) {
70
+ function index(value: string): number {
71
+ switch (value) {
72
+ case 'i':
73
+ return 1;
74
+ case 'a':
75
+ return 2;
76
+ case 'I':
77
+ return 3;
78
+ case 'A':
79
+ return 4;
80
+ default:
81
+ return 0;
82
+ }
83
+ }
84
+
85
+ function type(value: string): string {
86
+ switch (value) {
72
87
  case 'i':
73
88
  return 'i';
74
89
  case 'a':
@@ -97,7 +112,7 @@ function style(type: string): string {
97
112
  }
98
113
  }
99
114
 
100
- function initial(type: string): RegExp {
115
+ function pattern(type: string): RegExp {
101
116
  switch (type) {
102
117
  case 'i':
103
118
  return /^\(?i[).]?$/;
@@ -112,25 +127,37 @@ function initial(type: string): RegExp {
112
127
  }
113
128
  }
114
129
 
115
- function format(el: HTMLOListElement, type: string, form: string): HTMLOListElement {
116
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
117
- el.setAttribute('class', 'checklist');
130
+ function format(list: HTMLOListElement, type: string, form: string): HTMLOListElement {
131
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
132
+ list.classList.add('checklist');
118
133
  }
119
- define(el, {
134
+ define(list, {
120
135
  type: type || undefined,
121
136
  'data-format': form === '.' ? undefined : 'paren',
122
137
  'data-type': style(type) || undefined,
123
138
  });
124
- const marker = el.firstElementChild?.getAttribute('data-marker')!.match(initial(type))?.[0] ?? '';
125
- for (let es = el.children, len = es.length, i = 0; i < len; ++i) {
126
- const el = es[i];
127
- switch (el.getAttribute('data-marker')) {
128
- case '':
129
- case marker:
130
- el.removeAttribute('data-marker');
139
+ const marker = list.firstElementChild?.getAttribute('data-marker') ?? '';
140
+ // TODO: CSSカウンターをattr(start)でリセットできるようになればstart値からのオートインクリメントに対応させる。
141
+ const start = marker.match(pattern(type))?.[0] ?? '';
142
+ for (let es = list.children, len = es.length, i = 0; i < len; ++i) {
143
+ const item = es[i];
144
+ assert(item.getAttribute('data-marker') !== '');
145
+ switch (item.getAttribute('data-marker')) {
146
+ case null:
131
147
  continue;
148
+ case start:
149
+ item.removeAttribute('data-marker');
150
+ continue;
151
+ case marker:
152
+ if (i === 0 || item.classList.contains('invalid')) continue;
153
+ define(item, {
154
+ class: 'invalid',
155
+ 'data-invalid-syntax': 'list',
156
+ 'data-invalid-type': 'index',
157
+ 'data-invalid-message': 'Fix the duplicate index',
158
+ });
132
159
  }
133
160
  break;
134
161
  }
135
- return el;
162
+ return list;
136
163
  }
@@ -30,6 +30,9 @@ describe('Unit: parser/block/paragraph', () => {
30
30
  assert.deepStrictEqual(inspect(parser('_a\n<wbr>_\nb')), [['<p>_a<br><wbr>_<br>b</p>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('*a\n<wbr>*\nb')), [['<p>*a<br><wbr>*<br>b</p>'], '']);
32
32
  assert.deepStrictEqual(inspect(parser('==a\n<wbr>==\nb')), [['<p>==a<br><wbr>==<br>b</p>'], '']);
33
+ assert.deepStrictEqual(inspect(parser('http://host#!')), [['<p><a class="url" href="http://host#!" target="_blank">http://host#!</a></p>'], '']);
34
+ assert.deepStrictEqual(inspect(parser('a\nhttp://host#\\ \nb')), [['<p>a<br><a class="url" href="http://host#\\" target="_blank">http://host#\\</a><br>b</p>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('!http://host#!')), [['<p><a href="http://host#!" target="_blank"><img class="media" data-src="http://host#!" alt=""></a></p>'], '']);
33
36
  assert.deepStrictEqual(inspect(parser('\ta')), [['<p>\ta</p>'], '']);
34
37
  });
35
38
 
@@ -14,8 +14,7 @@ export const cite: ReplyParser.CiteParser = creation(1, false, line(fmap(validat
14
14
  // リンクの実装は後で検討
15
15
  focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
16
  focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
- // Support all domains, but don't support IP(v6) addresses.
18
- focus(/^>>https?:\/\/[^\p{C}\p{S}\p{P}\s]\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
17
+ focus(/^>>https?:\/\/(?:[[]|[^\p{C}\p{S}\p{P}\s])\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
19
18
  ]),
20
19
  ]))),
21
20
  ([el, quotes = '']: [HTMLElement, string?]) => [
@@ -55,6 +55,9 @@ describe('Unit: parser/block/reply/quote', () => {
55
55
  assert.deepStrictEqual(inspect(parser('> $-a, $-b')), [['<span class="quote">&gt; $-a, $-b</span>', '<br>'], '']);
56
56
  assert.deepStrictEqual(inspect(parser('> $a=b$')), [['<span class="quote">&gt; <span class="math" translate="no" data-src="$a=b$">$a=b$</span></span>', '<br>'], '']);
57
57
  assert.deepStrictEqual(inspect(parser('> ${a}$')), [['<span class="quote">&gt; <span class="math" translate="no" data-src="${a}$">${a}$</span></span>', '<br>'], '']);
58
+ assert.deepStrictEqual(inspect(parser('> http://host#!')), [['<span class="quote">&gt; <a class="url" href="http://host#!" target="_blank">http://host#!</a></span>', '<br>'], '']);
59
+ assert.deepStrictEqual(inspect(parser('> a\n> http://host#\\ \n> b')), [['<span class="quote">&gt; a<br>&gt; <a class="url" href="http://host#\\" target="_blank">http://host#\\</a> <br>&gt; b</span>', '<br>'], '']);
60
+ assert.deepStrictEqual(inspect(parser('> !http://host#!')), [['<span class="quote">&gt; !<a class="url" href="http://host#!" target="_blank">http://host#!</a></span>', '<br>'], '']);
58
61
  });
59
62
 
60
63
  });
@@ -1,9 +1,10 @@
1
1
  import { ReplyParser } from '../../block';
2
2
  import { eval } from '../../../combinator/data/parser';
3
- import { union, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
3
+ import { union, subsequence, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
4
4
  import { math } from '../../inline/math';
5
- import { str, anyline } from '../../source';
6
- import { autolink } from '../../autolink';
5
+ import { autolink } from '../../inline/autolink';
6
+ import { linebreak, unescsource, str, anyline } from '../../source';
7
+ import { lineurl } from '../../autolink';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
 
9
10
  export const syntax = /^>+(?=[^\S\n])|^>(?=[^\s>])|^>+(?=[^\s>])(?![0-9a-z]+(?:-[0-9a-z]+)*(?![0-9A-Za-z@#:]))/;
@@ -40,8 +41,8 @@ const qblock: ReplyParser.QuoteParser.BlockParser = ({ source, context }) => {
40
41
  const quotes = source.match(/^>+[^\S\n]/mg)!;
41
42
  assert(quotes);
42
43
  assert(quotes.length > 0);
43
- const content = lines.reduce((acc, line, row) => acc + line.slice(quotes[row].length), '');
44
- const nodes = eval(some(text)({ source: content, context }), []);
44
+ const content = lines.reduce((acc, line, i) => acc + line.slice(quotes[i].length), '');
45
+ const nodes = eval(text({ source: content, context }), []);
45
46
  nodes.unshift(quotes.shift()!);
46
47
  for (let i = 0; i < nodes.length; ++i) {
47
48
  const child = nodes[i] as string | Text | Element;
@@ -71,7 +72,12 @@ const qblock: ReplyParser.QuoteParser.BlockParser = ({ source, context }) => {
71
72
  return [nodes, ''];
72
73
  };
73
74
 
74
- const text: ReplyParser.QuoteParser.TextParser = union([
75
- math,
76
- autolink,
77
- ]);
75
+ const text: ReplyParser.QuoteParser.TextParser = some(line(subsequence([
76
+ lineurl,
77
+ some(union([
78
+ math, // quote補助関数が残した数式をパースする。他の構文で数式を残す場合はソーステキストを直接使用する。
79
+ autolink,
80
+ linebreak,
81
+ unescsource,
82
+ ])),
83
+ ])));
@@ -26,6 +26,6 @@ const source: SidefenceParser.SourceParser = lazy(() => fmap(
26
26
  convert(unindent, source)),
27
27
  rewrite(
28
28
  some(contentline, opener),
29
- convert(unindent, fmap(some(autolink), ns => [html('pre', defrag(ns))]))),
29
+ convert(unindent, fmap(autolink, ns => [html('pre', defrag(ns))]))),
30
30
  ]))),
31
31
  ns => [html('blockquote', ns)]));
@@ -17,7 +17,7 @@ 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(' -')), undefined);
20
+ assert.deepStrictEqual(inspect(parser(' - ')), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('+')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('*')), undefined);
23
23
  });
@@ -26,8 +26,8 @@ describe('Unit: parser/block/ulist', () => {
26
26
  // pending
27
27
  assert.deepStrictEqual(inspect(parser('- ')), [['<ul><li></li></ul>'], '']);
28
28
  // filled
29
- assert.deepStrictEqual(inspect(parser('- \\')), [['<ul><li></li></ul>'], '']);
30
- assert.deepStrictEqual(inspect(parser('- \\\n')), [['<ul><li></li></ul>'], '']);
29
+ assert.deepStrictEqual(inspect(parser('- \\')), [['<ul><li id="index::\\">\\</li></ul>'], '']);
30
+ assert.deepStrictEqual(inspect(parser('- \\\n')), [['<ul><li id="index::\\">\\</li></ul>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('- -')), [['<ul><li id="index::-">-</li></ul>'], '']);
32
32
  assert.deepStrictEqual(inspect(parser('- -\n')), [['<ul><li id="index::-">-</li></ul>'], '']);
33
33
  });
@@ -4,7 +4,7 @@ import { olist_, invalid } from './olist';
4
4
  import { ilist_ } from './ilist';
5
5
  import { inline, indexer, indexee } from '../inline';
6
6
  import { State } from '../context';
7
- import { trimBlank } from '../visibility';
7
+ import { visualize, trimBlank } from '../visibility';
8
8
  import { unshift } from 'spica/array';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
@@ -18,7 +18,7 @@ export const ulist_: UListParser = lazy(() => block(fmap(validate(
18
18
  some(creation(1, false, union([
19
19
  indexee(fmap(fallback(
20
20
  inits([
21
- line(open(/^-(?:$|\s)/, subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
21
+ line(open(/^-(?:$|\s)/, subsequence([checkbox, trimBlank(visualize(some(union([indexer, inline]))))]), true)),
22
22
  indent(union([ulist_, olist_, ilist_])),
23
23
  ]),
24
24
  invalid),
@@ -40,9 +40,9 @@ export function fillFirstLine(ns: (HTMLElement | string)[]): (HTMLElement | stri
40
40
  : ns;
41
41
  }
42
42
 
43
- function format(el: HTMLUListElement): HTMLUListElement {
44
- if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
45
- el.setAttribute('class', 'checklist');
43
+ function format(list: HTMLUListElement): HTMLUListElement {
44
+ if (list.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
45
+ list.classList.add('checklist');
46
46
  }
47
- return el;
47
+ return list;
48
48
  }
@@ -14,7 +14,7 @@ export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surroun
14
14
  constraint(State.index, false,
15
15
  syntax(Syntax.index, 2, 1, State.linkers | State.media,
16
16
  startTight(
17
- open(stropt(/^\|?/), trimBlankEnd(some(union([
17
+ open(stropt('|'), trimBlankEnd(some(union([
18
18
  signature,
19
19
  inline,
20
20
  ]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))),
@@ -9,7 +9,7 @@ export function indexee(parser: Parser<HTMLElement, MarkdownParser.Context>, opt
9
9
  }
10
10
 
11
11
  export function identity(id: string | undefined, text: string, name: 'index' | 'mark' = 'index'): string | undefined {
12
- assert(!id?.match(/[^0-9a-z-]/i));
12
+ assert(!id?.match(/[^0-9a-z/-]/i));
13
13
  assert(!text.includes('\n'));
14
14
  if (id === '') return undefined;
15
15
  text &&= text.trim().replace(/\s+/g, '_');
@@ -33,6 +33,7 @@ assert(identity(undefined, '0'.repeat(200) + 1, 'mark')!.slice(6) === '0'.repeat
33
33
  export function text(source: HTMLElement | DocumentFragment, optional = false): string {
34
34
  assert(source instanceof DocumentFragment || !source.matches('.indexer'));
35
35
  assert(source.querySelectorAll(':scope > .indexer').length <= 1);
36
+ if (!source.firstChild) return '';
36
37
  const indexer = source.querySelector(':scope > .indexer');
37
38
  const index = indexer?.getAttribute('data-index');
38
39
  if (index) return index;
@@ -139,8 +139,7 @@ export function* figure(
139
139
  }
140
140
  labels.add(label);
141
141
  opts.id !== '' && def.setAttribute('id', `label:${opts.id ? `${opts.id}:` : ''}${label}`);
142
- for (let rs = refs.take(label, Infinity), i = 0; i < rs.length; ++i) {
143
- const ref = rs[i];
142
+ for (const ref of refs.take(label, Infinity)) {
144
143
  if (ref.getAttribute('data-invalid-message') === messages.reference) {
145
144
  define(ref, {
146
145
  class: void ref.classList.remove('invalid'),
@@ -22,7 +22,7 @@ describe('Unit: parser/processor/footnote', () => {
22
22
  const target = parse('((a b))');
23
23
  const footnote = html('ol');
24
24
  for (let i = 0; i < 3; ++i) {
25
- assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 3 : 2);
25
+ assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 2 : 1);
26
26
  assert.deepStrictEqual(
27
27
  [...target.children].map(el => el.outerHTML),
28
28
  [
@@ -48,7 +48,7 @@ describe('Unit: parser/processor/footnote', () => {
48
48
  const target = parse('((1))((12345678901234567890))');
49
49
  const footnote = html('ol');
50
50
  for (let i = 0; i < 3; ++i) {
51
- assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 6 : 4);
51
+ assert.deepStrictEqual([...annotation(target, footnote)].length, i === 0 ? 4 : 2);
52
52
  assert.deepStrictEqual(
53
53
  [...target.children].map(el => el.outerHTML),
54
54
  [
@@ -165,7 +165,7 @@ describe('Unit: parser/processor/footnote', () => {
165
165
  const target = parse('((a b))');
166
166
  const footnote = html('ol');
167
167
  for (let i = 0; i < 3; ++i) {
168
- assert.deepStrictEqual([...annotation(target, footnote, { id: '0' })].length, i === 0 ? 3 : 2);
168
+ assert.deepStrictEqual([...annotation(target, footnote, { id: '0' })].length, i === 0 ? 2 : 1);
169
169
  assert.deepStrictEqual(
170
170
  [...target.children].map(el => el.outerHTML),
171
171
  [