securemark 0.231.0 → 0.232.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.231.0",
3
+ "version": "0.232.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,7 +31,7 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/dompurify": "2.3.3",
34
- "@types/jquery": "3.5.13",
34
+ "@types/jquery": "3.5.14",
35
35
  "@types/mathjax": "0.0.37",
36
36
  "@types/mocha": "9.1.0",
37
37
  "@types/power-assert": "1.5.8",
@@ -40,15 +40,17 @@
40
40
  "browserify-shim": "^3.8.14",
41
41
  "concurrently": "^7.0.0",
42
42
  "del": "^6.0.0",
43
+ "eslint-plugin-redos": "^4.3.0",
43
44
  "gulp": "^4.0.2",
44
45
  "gulp-derequire": "^3.0.0",
46
+ "gulp-eslint": "^6.0.0",
45
47
  "gulp-footer": "^2.1.0",
46
48
  "gulp-header": "^2.0.9",
47
49
  "gulp-load-plugins": "^2.0.7",
48
50
  "gulp-mocha": "^8.0.0",
49
51
  "gulp-rename": "^2.0.0",
50
52
  "gulp-unassert": "^2.0.0",
51
- "karma": "^6.3.16",
53
+ "karma": "^6.3.17",
52
54
  "karma-chrome-launcher": "^3.1.0",
53
55
  "karma-coverage-istanbul-instrumenter": "^1.0.4",
54
56
  "karma-coverage-istanbul-reporter": "^3.0.3",
@@ -56,13 +58,13 @@
56
58
  "karma-firefox-launcher": "^2.1.2",
57
59
  "karma-mocha": "^2.0.1",
58
60
  "mocha": "^9.2.1",
59
- "npm-check-updates": "^12.4.0",
61
+ "npm-check-updates": "^12.5.2",
60
62
  "power-assert": "^1.6.1",
61
63
  "semver": "^7.3.5",
62
- "spica": "0.0.510",
64
+ "spica": "0.0.511",
63
65
  "tsify": "^5.0.4",
64
- "typed-dom": "0.0.248",
65
- "typescript": "4.5.5",
66
+ "typed-dom": "0.0.249",
67
+ "typescript": "4.6.2",
66
68
  "vinyl-buffer": "^1.0.1",
67
69
  "vinyl-source-stream": "^2.0.0"
68
70
  },
package/src/debug.test.ts CHANGED
@@ -21,6 +21,7 @@ export function inspect(result: Result<HTMLElement | string>, until: number | st
21
21
  el.innerHTML = node.outerHTML.slice(0, until);
22
22
  if (node.outerHTML.length <= until) {
23
23
  assert(node.outerHTML === el.innerHTML);
24
+ // eslint-disable-next-line redos/no-vulnerable
24
25
  assert(node.childNodes.length === el.firstChild?.childNodes.length || />[^<]{65537}/.test(node.outerHTML));
25
26
  }
26
27
  else {
@@ -8,7 +8,7 @@ export function header(source: string): string {
8
8
 
9
9
  export function headers(source: string): string[] {
10
10
  const [el] = parse(source);
11
- return el?.textContent!.trimEnd().slice(el.firstChild!.textContent!.length).split(/[^\S\n]*\n/) ?? [];
11
+ return el?.textContent!.trimEnd().slice(el.firstChild!.textContent!.length).split('\n') ?? [];
12
12
  }
13
13
 
14
14
  function parse(source: string): [HTMLDetailsElement, string] | [] {
@@ -127,7 +127,7 @@ function attributes(source: string) {
127
127
  ? { 'data-highlight-level': +highlight! > 1 ? highlight : undefined }
128
128
  : {
129
129
  'data-invalid-syntax': 'table',
130
- 'data-invalid-type': 'highlight',
130
+ 'data-invalid-type': 'syntax',
131
131
  'data-invalid-description': 'Too much highlight level.',
132
132
  },
133
133
  };
@@ -28,28 +28,20 @@ describe('Unit: parser/block/paragraph', () => {
28
28
  assert.deepStrictEqual(inspect(parser(' a')), [['<p>a</p>'], '']);
29
29
  });
30
30
 
31
- it('mention', () => {
32
- assert.deepStrictEqual(inspect(parser('>>1')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span></p>'], '']);
33
- assert.deepStrictEqual(inspect(parser('>>1\na')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br>a</p>'], '']);
34
- assert.deepStrictEqual(inspect(parser('>>1\na\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br>a<br><a href="?comment=2" class="anchor">&gt;&gt;2</a></p>'], '']);
35
- assert.deepStrictEqual(inspect(parser('>>1\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="cite">&gt;<a href="?comment=2" class="anchor" data-depth="1">&gt;2</a></span></p>'], '']);
36
- assert.deepStrictEqual(inspect(parser('>>1\n> a')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span></p>'], '']);
37
- assert.deepStrictEqual(inspect(parser('>>1\n> a\nb')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span><br>b</p>'], '']);
38
- assert.deepStrictEqual(inspect(parser('>>1\n> a\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span><br><span class="cite">&gt;<a href="?comment=2" class="anchor" data-depth="1">&gt;2</a></span></p>'], '']);
39
- assert.deepStrictEqual(inspect(parser('>>1\n> a\n>> b')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a<br>&gt;&gt; b</span></p>'], '']);
31
+ it('anchor', () => {
40
32
  assert.deepStrictEqual(inspect(parser('>>1 a\nb')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a> a<br>b</p>'], '']);
41
33
  assert.deepStrictEqual(inspect(parser('>>1 a\n>>2')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a> a<br><a href="?comment=2" class="anchor">&gt;&gt;2</a></p>'], '']);
42
34
  assert.deepStrictEqual(inspect(parser('>>1 a\n>>b')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a> a<br><a href="?comment=b" class="anchor">&gt;&gt;b</a></p>'], '']);
43
- assert.deepStrictEqual(inspect(parser('>>1 a\n>> b')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a> a<br><span class="quote">&gt;&gt; b</span></p>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('>>1 a\n>> b')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a> a<br>&gt;&gt; b</p>'], '']);
44
36
  assert.deepStrictEqual(inspect(parser('>>11.')), [['<p><a href="?comment=11" class="anchor">&gt;&gt;11</a>.</p>'], '']);
45
37
  assert.deepStrictEqual(inspect(parser('>>11 a')), [['<p><a href="?comment=11" class="anchor">&gt;&gt;11</a> a</p>'], '']);
46
38
  assert.deepStrictEqual(inspect(parser('>>>11 a')), [['<p>&gt;<a href="?comment=11" class="anchor">&gt;&gt;11</a> a</p>'], '']);
47
- assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p><span class="quote">&gt;&gt; a</span><br><a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
39
+ assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p>&gt;&gt; a<br><a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
48
40
  assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a<a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
49
41
  assert.deepStrictEqual(inspect(parser('a >>1')), [['<p>a <a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
50
42
  assert.deepStrictEqual(inspect(parser('a\n>>1')), [['<p>a<br><a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
51
43
  assert.deepStrictEqual(inspect(parser('a\n>>1\nb')), [['<p>a<br><a href="?comment=1" class="anchor">&gt;&gt;1</a><br>b</p>'], '']);
52
- assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br><span class="quote">&gt;&gt; b</span><br>c</p>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br>&gt;&gt; b<br>c</p>'], '']);
53
45
  assert.deepStrictEqual(inspect(parser(' >>1')), [['<p><a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
54
46
  assert.deepStrictEqual(inspect(parser(' >>>1')), [['<p>&gt;<a href="?comment=1" class="anchor">&gt;&gt;1</a></p>'], '']);
55
47
  });
@@ -1,24 +1,10 @@
1
1
  import { ParagraphParser } from '../block';
2
- import { union, subsequence, some, block, rewrite, trim, fmap } from '../../combinator';
3
- import { mention } from './paragraph/mention';
4
- import { quote, syntax as delimiter } from './paragraph/mention/quote';
2
+ import { union, some, block, trim, fmap } from '../../combinator';
5
3
  import { inline } from '../inline';
6
- import { anyline } from '../source';
7
4
  import { localize } from '../locale';
8
5
  import { visualize } from '../util';
9
6
  import { html, defrag } from 'typed-dom';
10
- import { push, pop } from 'spica/array';
11
7
 
12
8
  export const paragraph: ParagraphParser = block(localize(fmap(
13
- subsequence([
14
- some(mention),
15
- some(union([
16
- quote,
17
- fmap(
18
- rewrite(
19
- some(anyline, delimiter),
20
- trim(visualize(some(inline)))),
21
- ns => push(ns, [html('br')])),
22
- ])),
23
- ]),
24
- ns => [html('p', defrag(pop(ns)[0]))])));
9
+ trim(visualize(some(union([inline])))),
10
+ ns => [html('p', defrag(ns))])));
@@ -1,8 +1,8 @@
1
1
  import { cite } from './cite';
2
- import { some } from '../../../../combinator';
3
- import { inspect } from '../../../../debug.test';
2
+ import { some } from '../../../combinator';
3
+ import { inspect } from '../../../debug.test';
4
4
 
5
- describe('Unit: parser/block/paragraph/mention/cite', () => {
5
+ describe('Unit: parser/block/reply/cite', () => {
6
6
  describe('cite', () => {
7
7
  const parser = (source: string) => some(cite)(source, {});
8
8
 
@@ -1,10 +1,10 @@
1
- import { ParagraphParser } from '../../../block';
2
- import { tails, line, validate, creator, reverse, fmap } from '../../../../combinator';
3
- import { anchor } from '../../../inline/autolink/anchor';
4
- import { str } from '../../../source';
1
+ import { ReplyParser } from '../../block';
2
+ import { tails, line, validate, creator, reverse, fmap } from '../../../combinator';
3
+ import { anchor } from '../../inline/autolink/anchor';
4
+ import { str } from '../../source';
5
5
  import { html, define, defrag } from 'typed-dom';
6
6
 
7
- export const cite: ParagraphParser.MentionParser.CiteParser = creator(line(fmap(validate(
7
+ export const cite: ReplyParser.CiteParser = creator(line(fmap(validate(
8
8
  '>>',
9
9
  reverse(tails([
10
10
  str(/^>*(?=>>)/),
@@ -1,8 +1,8 @@
1
1
  import { quote } from './quote';
2
- import { some } from '../../../../combinator';
3
- import { inspect } from '../../../../debug.test';
2
+ import { some } from '../../../combinator';
3
+ import { inspect } from '../../../debug.test';
4
4
 
5
- describe('Unit: parser/block/paragraph/mention/quote', () => {
5
+ describe('Unit: parser/block/reply/quote', () => {
6
6
  describe('quote', () => {
7
7
  const parser = (source: string) => some(quote)(source, {});
8
8
 
@@ -1,14 +1,14 @@
1
- import { ParagraphParser } from '../../../block';
2
- import { eval } from '../../../../combinator/data/parser';
3
- import { union, some, block, line, validate, rewrite, creator, lazy, fmap } from '../../../../combinator';
4
- import { math } from '../../../inline/math';
5
- import { str, anyline } from '../../../source';
6
- import { autolink } from '../../../autolink';
1
+ import { ReplyParser } from '../../block';
2
+ import { eval } from '../../../combinator/data/parser';
3
+ import { union, some, block, line, validate, rewrite, creator, lazy, fmap } from '../../../combinator';
4
+ import { math } from '../../inline/math';
5
+ import { str, anyline } from '../../source';
6
+ import { autolink } from '../../autolink';
7
7
  import { html, defrag } from 'typed-dom';
8
8
 
9
9
  export const syntax = /^>+(?=[^\S\n])|^>(?=[^\s>])|^>+(?=[^\s>])(?![0-9a-z]+(?:-[0-9a-z]+)*(?![0-9A-Za-z@#:]))/;
10
10
 
11
- export const quote: ParagraphParser.MentionParser.QuoteParser = lazy(() => creator(block(fmap(validate(
11
+ export const quote: ReplyParser.QuoteParser = lazy(() => creator(block(fmap(validate(
12
12
  '>',
13
13
  union([
14
14
  rewrite(
@@ -33,7 +33,7 @@ export const quote: ParagraphParser.MentionParser.QuoteParser = lazy(() => creat
33
33
  ]),
34
34
  false)));
35
35
 
36
- const qblock: ParagraphParser.MentionParser.QuoteParser.BlockParser = (source, context) => {
36
+ const qblock: ReplyParser.QuoteParser.BlockParser = (source, context) => {
37
37
  source = source.replace(/\n$/, '');
38
38
  const lines = source.match(/^.*\n?/mg)!;
39
39
  assert(lines);
@@ -69,7 +69,7 @@ const qblock: ParagraphParser.MentionParser.QuoteParser.BlockParser = (source, c
69
69
  return [defrag(nodes), ''];
70
70
  };
71
71
 
72
- const text: ParagraphParser.MentionParser.QuoteParser.TextParser = union([
72
+ const text: ReplyParser.QuoteParser.TextParser = union([
73
73
  math,
74
74
  autolink,
75
75
  ]);
@@ -0,0 +1,22 @@
1
+ import { reply } from './reply';
2
+ import { some } from '../../combinator';
3
+ import { inspect } from '../../debug.test';
4
+
5
+ describe('Unit: parser/block/reply', () => {
6
+ describe('reply', () => {
7
+ const parser = (source: string) => some(reply)(source, {});
8
+
9
+ it('basic', () => {
10
+ assert.deepStrictEqual(inspect(parser('>>1')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span></p>'], '']);
11
+ assert.deepStrictEqual(inspect(parser('>>1\na')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br>a</p>'], '']);
12
+ assert.deepStrictEqual(inspect(parser('>>1\na\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br>a<br><a href="?comment=2" class="anchor">&gt;&gt;2</a></p>'], '']);
13
+ assert.deepStrictEqual(inspect(parser('>>1\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="cite">&gt;<a href="?comment=2" class="anchor" data-depth="1">&gt;2</a></span></p>'], '']);
14
+ assert.deepStrictEqual(inspect(parser('>>1\n> a')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span></p>'], '']);
15
+ assert.deepStrictEqual(inspect(parser('>>1\n> a\nb')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span><br>b</p>'], '']);
16
+ assert.deepStrictEqual(inspect(parser('>>1\n> a\n>>2')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a</span><br><span class="cite">&gt;<a href="?comment=2" class="anchor" data-depth="1">&gt;2</a></span></p>'], '']);
17
+ assert.deepStrictEqual(inspect(parser('>>1\n> a\n>> b')), [['<p><span class="cite">&gt;<a href="?comment=1" class="anchor" data-depth="1">&gt;1</a></span><br><span class="quote">&gt; a<br>&gt;&gt; b</span></p>'], '']);
18
+ });
19
+
20
+ });
21
+
22
+ });
@@ -0,0 +1,27 @@
1
+ import { ReplyParser } from '../block';
2
+ import { inits, subsequence, some, block, validate, rewrite, trim, fmap } from '../../combinator';
3
+ import { cite } from './reply/cite';
4
+ import { quote, syntax as delimiter } from './reply/quote';
5
+ import { inline } from '../inline';
6
+ import { anyline } from '../source';
7
+ import { localize } from '../locale';
8
+ import { visualize } from '../util';
9
+ import { html, defrag } from 'typed-dom';
10
+ import { push, pop } from 'spica/array';
11
+
12
+ export const reply: ReplyParser = block(validate('>', localize(fmap(
13
+ some(inits([
14
+ some(inits([
15
+ cite,
16
+ quote,
17
+ ])),
18
+ subsequence([
19
+ some(quote),
20
+ fmap(
21
+ rewrite(
22
+ some(anyline, delimiter),
23
+ trim(visualize(some(inline)))),
24
+ ns => push(ns, [html('br')])),
25
+ ]),
26
+ ])),
27
+ ns => [html('p', defrag(pop(ns)[0]))]))));
@@ -13,6 +13,7 @@ import { blockquote } from './block/blockquote';
13
13
  import { codeblock } from './block/codeblock';
14
14
  import { mathblock } from './block/mathblock';
15
15
  import { extension } from './block/extension';
16
+ import { reply } from './block/reply';
16
17
  import { paragraph } from './block/paragraph';
17
18
  import { html } from 'typed-dom';
18
19
  import { rnd0Z } from 'spica/random';
@@ -29,6 +30,7 @@ export import CodeBlockParser = BlockParser.CodeBlockParser;
29
30
  export import MathBlockParser = BlockParser.MathBlockParser;
30
31
  export import ExtensionParser = BlockParser.ExtensionParser;
31
32
  export import BlockquoteParser = BlockParser.BlockquoteParser;
33
+ export import ReplyParser = BlockParser.ReplyParser;
32
34
  export import ParagraphParser = BlockParser.ParagraphParser;
33
35
 
34
36
  export const block: BlockParser = creator(error(
@@ -46,6 +48,7 @@ export const block: BlockParser = creator(error(
46
48
  mathblock,
47
49
  extension,
48
50
  blockquote,
51
+ reply,
49
52
  paragraph
50
53
  ]))));
51
54
 
@@ -6,11 +6,8 @@ import { str } from '../source';
6
6
  import { html, defrag } from 'typed-dom';
7
7
  import { unshift, push } from 'spica/array';
8
8
 
9
- const index = new RegExp(`^(?:${[
10
- /(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))+/,
11
- /[0-9]{1,4}|[A-Za-z]/,
12
- ].map(r => r.source).join('|')})`);
13
- const indexFW = new RegExp(index.source.replace(/[019AZaz](?!,)/g, c => String.fromCharCode(c.charCodeAt(0) + 0xfee0)));
9
+ const index = /^(?:[0-9]+(?:\.[0-9]+)*|[A-Za-z])/;
10
+ const indexFW = new RegExp(index.source.replace(/[019AZaz](?!,)/g, c => String.fromCharCode(c.charCodeAt(0) + 0xFEE0)));
14
11
 
15
12
  export const bracket: BracketParser = lazy(() => union([
16
13
  surround(str('('), str(index), str(')'), false,
@@ -17,8 +17,10 @@ describe('Unit: parser/inline/comment', () => {
17
17
  assert.deepStrictEqual(inspect(parser('[# #] #]')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('[# #] #]')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('[# [#')), undefined);
20
+ assert.deepStrictEqual(inspect(parser('[#[#')), undefined);
20
21
  assert.deepStrictEqual(inspect(parser('[# [# ')), undefined);
21
22
  assert.deepStrictEqual(inspect(parser('[# [# a')), undefined);
23
+ assert.deepStrictEqual(inspect(parser('[# a[#')), [['<sup class="comment invalid">[# a</sup>'], '[#']);
22
24
  assert.deepStrictEqual(inspect(parser('[# a [#')), [['<sup class="comment invalid">[# a </sup>'], '[#']);
23
25
  assert.deepStrictEqual(inspect(parser('[# a [# ')), [['<sup class="comment invalid">[# a </sup>'], '[# ']);
24
26
  assert.deepStrictEqual(inspect(parser('[# a [#\n')), [['<sup class="comment invalid">[# a </sup>'], '[#\n']);
@@ -6,9 +6,9 @@ import { unescsource } from '../source';
6
6
  import { html } from 'typed-dom';
7
7
 
8
8
  export const comment: CommentParser = creator(validate('[#', match(
9
- /^\[(#+)\s+(?!\s|\1\]|\[\1\s)((?:\S+\s+)+?)(\1\]|(?=\[\1(?:$|\s)))/,
9
+ /^\[(#+)(?!\S|\s+\1\]|\s*\[\1(?:$|\s))((?:\s+\S+)+?)(?:\s+(\1\])|\s*(?=\[\1(?:$|\s)))/,
10
10
  ([whole, , body, closer]) => (rest, context) => {
11
- [whole, body] = `${whole}\0${body.trimEnd()}`.replace(/\x1B/g, '').split('\0', 2);
11
+ [whole, body] = `${whole}\0${body.trimStart()}`.replace(/\x1B/g, '').split('\0', 2);
12
12
  if (!closer) return [[html('sup', {
13
13
  class: 'comment invalid',
14
14
  'data-invalid-syntax': 'comment',
@@ -88,7 +88,7 @@ export const html: HTMLParser = lazy(() => creator(validate('<', validate(/^<[a-
88
88
  ])))));
89
89
 
90
90
  export const attribute: HTMLParser.TagParser.AttributeParser = union([
91
- str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\n"])*")?(?=[^\S\n]|>)/),
91
+ str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/),
92
92
  ]);
93
93
 
94
94
  function elem(tag: string, as: (HTMLElement | string)[], bs: (HTMLElement | string)[], cs: (HTMLElement | string)[], context: MarkdownParser.Context): HTMLElement {
@@ -52,7 +52,7 @@ export const link: LinkParser = lazy(() => creator(10, bind(reverse(
52
52
  const INSECURE_URI = params.shift()!;
53
53
  assert(INSECURE_URI === INSECURE_URI.trim());
54
54
  assert(!INSECURE_URI.match(/\s/));
55
- const el = create(
55
+ const el = elem(
56
56
  INSECURE_URI,
57
57
  trimNode(defrag(content)),
58
58
  new ReadonlyURL(
@@ -71,7 +71,7 @@ export const uri: LinkParser.ParameterParser.UriParser = union([
71
71
 
72
72
  export const option: LinkParser.ParameterParser.OptionParser = union([
73
73
  fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
74
- str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\n"])*")?(?=[^\S\n]|})/),
74
+ str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|})/),
75
75
  fmap(str(/^[^\S\n]+(?=})/), () => []),
76
76
  fmap(str(/^[^\S\n]+[^\n{}]+/), opt => [` \\${opt.slice(1)}`]),
77
77
  ]);
@@ -98,7 +98,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
98
98
  }
99
99
  }
100
100
 
101
- function create(
101
+ function elem(
102
102
  INSECURE_URI: string,
103
103
  content: readonly (string | HTMLElement)[],
104
104
  uri: ReadonlyURL,
@@ -11,8 +11,8 @@ import { unshift, push, join } from 'spica/array';
11
11
  export const ruby: RubyParser = lazy(() => creator(bind(verify(
12
12
  validate('[', ')', '\n',
13
13
  sequence([
14
- surround('[', focus(/^(?:\\[^\n]|[^\[\]\n])+(?=]\()/, text), ']'),
15
- surround('(', focus(/^(?:\\[^\n]|[^\(\)\n])+(?=\))/, text), ')'),
14
+ surround('[', focus(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'),
15
+ surround('(', focus(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')'),
16
16
  ])),
17
17
  ([texts]) => isStartTightNodes(texts)),
18
18
  ([texts, rubies], rest) => {
@@ -42,13 +42,13 @@ const invisibleHTMLEntityNames = [
42
42
  'InvisibleComma',
43
43
  'ic',
44
44
  ];
45
- const blankline = new RegExp(String.raw`^(?!$|\n)(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>|\[(#+)\s+(?!\s|\1\]|\[\1\s)(?:\S+\s+)+?(?:\1\]|(?=\[\1(?:$|\s))))*\\?(?:$|\n)`, 'gm');
45
+ const blankline = new RegExp(String.raw`^(?!$)(?:\\$|\\?[^\S\n]|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>|\[(#+)(?!\S|\s+\1\]|\s*\[\1(?:$|\s))((?:\s+\S+)+?)(?:\s+(\1\])|\s*(?=\[\1(?:$|\s))))+(?=$|(\S))`, 'gm');
46
46
 
47
47
  export function visualize<P extends Parser<HTMLElement | string>>(parser: P): P;
48
48
  export function visualize<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
49
49
  return union([
50
50
  convert(
51
- source => source.replace(blankline, line => line.replace(/[\\&<\[]/g, '\x1B$&')),
51
+ source => source.replace(blankline, (line, ...$) => !$[3] ? line.replace(/[\\&<\[]/g, '\x1B$&') : line),
52
52
  verify(parser, (ns, rest, context) => !rest && hasVisible(ns, context))),
53
53
  some(union([linebreak, unescsource])),
54
54
  ]);
@@ -229,7 +229,7 @@ export function stringify(nodes: readonly (HTMLElement | string)[]): string {
229
229
  }
230
230
  else {
231
231
  assert(!node.matches('br') && !node.querySelector('br'));
232
- // Note: Doesn't reflect line breaks.
232
+ // NOTE: Doesn't reflect line breaks.
233
233
  acc += node.innerText;
234
234
  }
235
235
  }
@@ -15,12 +15,12 @@ export function youtube(source: HTMLImageElement, url: URL): HTMLElement | undef
15
15
  function resolve(url: URL): string | undefined {
16
16
  switch (url.origin) {
17
17
  case 'https://www.youtube.com':
18
- return url.pathname === '/watch/'
19
- ? url.href.replace(/.+?=/, '').replace('&', '?')
18
+ return url.pathname.match(/^\/watch\/?$/)
19
+ ? url.searchParams.get('v')?.concat(url.search.replace(/([?&])v=[^&#]*&?/g, '$1'), url.hash)
20
20
  : undefined;
21
21
  case 'https://youtu.be':
22
- return url.pathname.match(/^\/[\w-]+$/)
23
- ? url.href.slice(url.href.indexOf('/', 9) + 1)
22
+ return url.pathname.match(/^\/[\w-]+\/?$/)
23
+ ? url.href.slice(url.origin.length)
24
24
  : undefined;
25
25
  default:
26
26
  return;
@@ -14,7 +14,7 @@ describe('Unit: util/info', () => {
14
14
  channel: [],
15
15
  hashtag: [],
16
16
  hashnum: [],
17
- mention: [],
17
+ reply: [],
18
18
  anchor: [],
19
19
  media: [],
20
20
  });
package/src/util/info.ts CHANGED
@@ -2,7 +2,7 @@ import { Info } from '../..';
2
2
  import { scope } from './scope';
3
3
 
4
4
  export function info(source: DocumentFragment | HTMLElement | ShadowRoot): Info {
5
- const match = scope(source, 'section, article, aside, blockquote, .quote, pre, .math, .media');
5
+ const match = scope(source, '.invalid');
6
6
  return {
7
7
  url: find<HTMLAnchorElement>('a:not(.email):not(.account):not(.channel):not(.hashtag):not(.hashnum):not(.anchor)')
8
8
  .filter(el => ['http:', 'https:'].includes(el.protocol)),
@@ -13,7 +13,7 @@ export function info(source: DocumentFragment | HTMLElement | ShadowRoot): Info
13
13
  channel: find('a.channel'),
14
14
  hashtag: find('a.hashtag'),
15
15
  hashnum: find('a.hashnum'),
16
- mention: find('.cite > a.anchor'),
16
+ reply: find('.cite > a.anchor'),
17
17
  anchor: find(':not(.cite) > a.anchor'),
18
18
  media: find('.media[data-src]'),
19
19
  };
package/src/util/quote.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { exec } from '../combinator/data/parser';
2
- import { cite } from '../parser/block/paragraph/mention/cite';
2
+ import { cite } from '../parser/block/reply/cite';
3
3
  import { define } from 'typed-dom';
4
4
 
5
5
  export function quote(anchor: string, range: Range): string {
package/src/util/scope.ts CHANGED
@@ -2,8 +2,10 @@ import { undefined, WeakMap } from 'spica/global';
2
2
 
3
3
  export function scope(
4
4
  base: DocumentFragment | HTMLElement | ShadowRoot,
5
- bound: string = `${'id' in base && base.id ? `#${base.id}, ` : ''}section, article, aside, blockquote`,
5
+ filter: string = '',
6
+ bound: string = `${'id' in base && base.id ? `#${base.id}, ` : ''}section, article, aside, blockquote, pre, .quote, .math, .media`,
6
7
  ): (el: Element) => boolean {
8
+ bound += filter && `, ${filter}`;
7
9
  const memory = new WeakMap<Node, boolean>();
8
10
  const context = 'id' in base && base.closest(bound) || null;
9
11
  return el => {
@@ -1,9 +0,0 @@
1
- import { ParagraphParser } from '../../block';
2
- import { inits, some, validate } from '../../../combinator';
3
- import { cite } from './mention/cite';
4
- import { quote } from './mention/quote';
5
-
6
- export const mention: ParagraphParser.MentionParser = validate('>', inits([
7
- some(cite),
8
- quote,
9
- ]));