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/CHANGELOG.md +14 -0
- package/dist/securemark.js +133 -103
- package/gulpfile.js +21 -1
- package/index.d.ts +2 -2
- package/markdown.d.ts +44 -42
- package/package-lock.json +934 -205
- package/package.json +9 -7
- package/src/debug.test.ts +1 -0
- package/src/parser/api/header.ts +1 -1
- package/src/parser/block/extension/table.ts +1 -1
- package/src/parser/block/paragraph.test.ts +4 -12
- package/src/parser/block/paragraph.ts +3 -17
- package/src/parser/block/{paragraph/mention → reply}/cite.test.ts +3 -3
- package/src/parser/block/{paragraph/mention → reply}/cite.ts +5 -5
- package/src/parser/block/{paragraph/mention → reply}/quote.test.ts +3 -3
- package/src/parser/block/{paragraph/mention → reply}/quote.ts +9 -9
- package/src/parser/block/reply.test.ts +22 -0
- package/src/parser/block/reply.ts +27 -0
- package/src/parser/block.ts +3 -0
- package/src/parser/inline/bracket.ts +2 -5
- package/src/parser/inline/comment.test.ts +2 -0
- package/src/parser/inline/comment.ts +2 -2
- package/src/parser/inline/html.ts +1 -1
- package/src/parser/inline/link.ts +3 -3
- package/src/parser/inline/ruby.ts +2 -2
- package/src/parser/util.ts +3 -3
- package/src/renderer/render/media/youtube.ts +4 -4
- package/src/util/info.test.ts +1 -1
- package/src/util/info.ts +2 -2
- package/src/util/quote.ts +1 -1
- package/src/util/scope.ts +3 -1
- package/src/parser/block/paragraph/mention.ts +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securemark",
|
|
3
|
-
"version": "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.
|
|
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.
|
|
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.
|
|
61
|
+
"npm-check-updates": "^12.5.2",
|
|
60
62
|
"power-assert": "^1.6.1",
|
|
61
63
|
"semver": "^7.3.5",
|
|
62
|
-
"spica": "0.0.
|
|
64
|
+
"spica": "0.0.511",
|
|
63
65
|
"tsify": "^5.0.4",
|
|
64
|
-
"typed-dom": "0.0.
|
|
65
|
-
"typescript": "4.
|
|
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 {
|
package/src/parser/api/header.ts
CHANGED
|
@@ -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(
|
|
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': '
|
|
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('
|
|
32
|
-
assert.deepStrictEqual(inspect(parser('>>1')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span></p>'], '']);
|
|
33
|
-
assert.deepStrictEqual(inspect(parser('>>1\na')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br>a</p>'], '']);
|
|
34
|
-
assert.deepStrictEqual(inspect(parser('>>1\na\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br>a<br><a href="?comment=2" class="anchor">>>2</a></p>'], '']);
|
|
35
|
-
assert.deepStrictEqual(inspect(parser('>>1\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="cite">><a href="?comment=2" class="anchor" data-depth="1">>2</a></span></p>'], '']);
|
|
36
|
-
assert.deepStrictEqual(inspect(parser('>>1\n> a')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span></p>'], '']);
|
|
37
|
-
assert.deepStrictEqual(inspect(parser('>>1\n> a\nb')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span><br>b</p>'], '']);
|
|
38
|
-
assert.deepStrictEqual(inspect(parser('>>1\n> a\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span><br><span class="cite">><a href="?comment=2" class="anchor" data-depth="1">>2</a></span></p>'], '']);
|
|
39
|
-
assert.deepStrictEqual(inspect(parser('>>1\n> a\n>> b')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a<br>>> b</span></p>'], '']);
|
|
31
|
+
it('anchor', () => {
|
|
40
32
|
assert.deepStrictEqual(inspect(parser('>>1 a\nb')), [['<p><a href="?comment=1" class="anchor">>>1</a> a<br>b</p>'], '']);
|
|
41
33
|
assert.deepStrictEqual(inspect(parser('>>1 a\n>>2')), [['<p><a href="?comment=1" class="anchor">>>1</a> a<br><a href="?comment=2" class="anchor">>>2</a></p>'], '']);
|
|
42
34
|
assert.deepStrictEqual(inspect(parser('>>1 a\n>>b')), [['<p><a href="?comment=1" class="anchor">>>1</a> a<br><a href="?comment=b" class="anchor">>>b</a></p>'], '']);
|
|
43
|
-
assert.deepStrictEqual(inspect(parser('>>1 a\n>> b')), [['<p><a href="?comment=1" class="anchor">>>1</a> a<br
|
|
35
|
+
assert.deepStrictEqual(inspect(parser('>>1 a\n>> b')), [['<p><a href="?comment=1" class="anchor">>>1</a> a<br>>> b</p>'], '']);
|
|
44
36
|
assert.deepStrictEqual(inspect(parser('>>11.')), [['<p><a href="?comment=11" class="anchor">>>11</a>.</p>'], '']);
|
|
45
37
|
assert.deepStrictEqual(inspect(parser('>>11 a')), [['<p><a href="?comment=11" class="anchor">>>11</a> a</p>'], '']);
|
|
46
38
|
assert.deepStrictEqual(inspect(parser('>>>11 a')), [['<p>><a href="?comment=11" class="anchor">>>11</a> a</p>'], '']);
|
|
47
|
-
assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p
|
|
39
|
+
assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p>>> a<br><a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
48
40
|
assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a<a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
49
41
|
assert.deepStrictEqual(inspect(parser('a >>1')), [['<p>a <a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
50
42
|
assert.deepStrictEqual(inspect(parser('a\n>>1')), [['<p>a<br><a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
51
43
|
assert.deepStrictEqual(inspect(parser('a\n>>1\nb')), [['<p>a<br><a href="?comment=1" class="anchor">>>1</a><br>b</p>'], '']);
|
|
52
|
-
assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br
|
|
44
|
+
assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br>>> b<br>c</p>'], '']);
|
|
53
45
|
assert.deepStrictEqual(inspect(parser(' >>1')), [['<p><a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
54
46
|
assert.deepStrictEqual(inspect(parser(' >>>1')), [['<p>><a href="?comment=1" class="anchor">>>1</a></p>'], '']);
|
|
55
47
|
});
|
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { ParagraphParser } from '../block';
|
|
2
|
-
import { union,
|
|
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
|
-
|
|
14
|
-
|
|
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 '
|
|
3
|
-
import { inspect } from '
|
|
2
|
+
import { some } from '../../../combinator';
|
|
3
|
+
import { inspect } from '../../../debug.test';
|
|
4
4
|
|
|
5
|
-
describe('Unit: parser/block/
|
|
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 {
|
|
2
|
-
import { tails, line, validate, creator, reverse, fmap } from '
|
|
3
|
-
import { anchor } from '
|
|
4
|
-
import { str } from '
|
|
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:
|
|
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 '
|
|
3
|
-
import { inspect } from '
|
|
2
|
+
import { some } from '../../../combinator';
|
|
3
|
+
import { inspect } from '../../../debug.test';
|
|
4
4
|
|
|
5
|
-
describe('Unit: parser/block/
|
|
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 {
|
|
2
|
-
import { eval } from '
|
|
3
|
-
import { union, some, block, line, validate, rewrite, creator, lazy, fmap } from '
|
|
4
|
-
import { math } from '
|
|
5
|
-
import { str, anyline } from '
|
|
6
|
-
import { autolink } from '
|
|
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:
|
|
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:
|
|
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:
|
|
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">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span></p>'], '']);
|
|
11
|
+
assert.deepStrictEqual(inspect(parser('>>1\na')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br>a</p>'], '']);
|
|
12
|
+
assert.deepStrictEqual(inspect(parser('>>1\na\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br>a<br><a href="?comment=2" class="anchor">>>2</a></p>'], '']);
|
|
13
|
+
assert.deepStrictEqual(inspect(parser('>>1\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="cite">><a href="?comment=2" class="anchor" data-depth="1">>2</a></span></p>'], '']);
|
|
14
|
+
assert.deepStrictEqual(inspect(parser('>>1\n> a')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span></p>'], '']);
|
|
15
|
+
assert.deepStrictEqual(inspect(parser('>>1\n> a\nb')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span><br>b</p>'], '']);
|
|
16
|
+
assert.deepStrictEqual(inspect(parser('>>1\n> a\n>>2')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a</span><br><span class="cite">><a href="?comment=2" class="anchor" data-depth="1">>2</a></span></p>'], '']);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser('>>1\n> a\n>> b')), [['<p><span class="cite">><a href="?comment=1" class="anchor" data-depth="1">>1</a></span><br><span class="quote">> a<br>>> 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]))]))));
|
package/src/parser/block.ts
CHANGED
|
@@ -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 =
|
|
10
|
-
|
|
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
|
-
/^\[(#+)
|
|
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.
|
|
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]|[
|
|
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 =
|
|
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]|[
|
|
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
|
|
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]|[
|
|
15
|
-
surround('(', focus(/^(?:\\[^\n]|[
|
|
14
|
+
surround('[', focus(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'),
|
|
15
|
+
surround('(', focus(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')'),
|
|
16
16
|
])),
|
|
17
17
|
([texts]) => isStartTightNodes(texts)),
|
|
18
18
|
([texts, rubies], rest) => {
|
package/src/parser/util.ts
CHANGED
|
@@ -42,13 +42,13 @@ const invisibleHTMLEntityNames = [
|
|
|
42
42
|
'InvisibleComma',
|
|
43
43
|
'ic',
|
|
44
44
|
];
|
|
45
|
-
const blankline = new RegExp(String.raw`^(
|
|
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
|
-
//
|
|
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
|
|
19
|
-
? url.
|
|
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.
|
|
22
|
+
return url.pathname.match(/^\/[\w-]+\/?$/)
|
|
23
|
+
? url.href.slice(url.origin.length)
|
|
24
24
|
: undefined;
|
|
25
25
|
default:
|
|
26
26
|
return;
|
package/src/util/info.test.ts
CHANGED
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, '
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
]));
|