securemark 0.230.1 → 0.231.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/securemark.js +94 -64
- package/gulpfile.js +21 -1
- package/package-lock.json +941 -214
- package/package.json +9 -7
- package/src/debug.test.ts +1 -0
- package/src/parser/api/header.ts +1 -1
- package/src/parser/inline/autolink/account.ts +1 -1
- package/src/parser/inline/autolink/email.test.ts +2 -1
- package/src/parser/inline/autolink/email.ts +1 -1
- package/src/parser/inline/autolink/hashnum.test.ts +1 -0
- package/src/parser/inline/autolink/hashnum.ts +2 -1
- package/src/parser/inline/autolink/hashtag.test.ts +13 -12
- package/src/parser/inline/autolink/hashtag.ts +9 -2
- package/src/parser/inline/autolink.ts +3 -3
- 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 +4 -4
- package/src/parser/inline/ruby.ts +2 -2
- package/src/parser/inline.test.ts +2 -0
- package/src/parser/util.ts +2 -2
- package/src/renderer/render/media/youtube.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securemark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.231.2",
|
|
4
4
|
"description": "Secure markdown renderer working on browsers for user input data.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/falsandtru/securemark",
|
|
@@ -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] | [] {
|
|
@@ -11,7 +11,7 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
|
|
|
11
11
|
'@',
|
|
12
12
|
tails([
|
|
13
13
|
verify(
|
|
14
|
-
str(/^[0-9A-Za-z](?:[0-9A-Za-z
|
|
14
|
+
str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
|
|
15
15
|
([source]) => source.length <= 253 + 1),
|
|
16
16
|
verify(
|
|
17
17
|
str(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
|
|
@@ -23,6 +23,7 @@ describe('Unit: parser/inline/autolink/email', () => {
|
|
|
23
23
|
assert.deepStrictEqual(inspect(parser('a+@b')), undefined);
|
|
24
24
|
assert.deepStrictEqual(inspect(parser('a..b@c')), undefined);
|
|
25
25
|
assert.deepStrictEqual(inspect(parser('a++b@c')), undefined);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser(`a@${'b'.repeat(64)}`)), [[`a@${'b'.repeat(64)}`], '']);
|
|
26
27
|
assert.deepStrictEqual(inspect(parser(' a@b')), undefined);
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -36,7 +37,7 @@ describe('Unit: parser/inline/autolink/email', () => {
|
|
|
36
37
|
assert.deepStrictEqual(inspect(parser('a@b_c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '_c']);
|
|
37
38
|
assert.deepStrictEqual(inspect(parser('a@b-')), [['<a class="email" href="mailto:a@b">a@b</a>'], '-']);
|
|
38
39
|
assert.deepStrictEqual(inspect(parser('a@b-c')), [['<a class="email" href="mailto:a@b-c">a@b-c</a>'], '']);
|
|
39
|
-
assert.deepStrictEqual(inspect(parser('a@b--c')), [['<a class="email" href="mailto:a@b
|
|
40
|
+
assert.deepStrictEqual(inspect(parser('a@b--c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '--c']);
|
|
40
41
|
assert.deepStrictEqual(inspect(parser('a@b.')), [['<a class="email" href="mailto:a@b">a@b</a>'], '.']);
|
|
41
42
|
assert.deepStrictEqual(inspect(parser('a@b.c')), [['<a class="email" href="mailto:a@b.c">a@b.c</a>'], '']);
|
|
42
43
|
assert.deepStrictEqual(inspect(parser('a@b..c')), [['<a class="email" href="mailto:a@b">a@b</a>'], '..c']);
|
|
@@ -6,6 +6,6 @@ import { html } from 'typed-dom';
|
|
|
6
6
|
// https://html.spec.whatwg.org/multipage/input.html
|
|
7
7
|
|
|
8
8
|
export const email: AutolinkParser.EmailParser = creator(rewrite(verify(
|
|
9
|
-
str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:[0-9A-Za-z
|
|
9
|
+
str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*(?![0-9A-Za-z])/),
|
|
10
10
|
([source]) => source.indexOf('@') <= 64 && source.length <= 255),
|
|
11
11
|
source => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], '']));
|
|
@@ -35,6 +35,7 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
|
|
|
35
35
|
assert.deepStrictEqual(inspect(parser('あ#1')), [['あ#1'], '']);
|
|
36
36
|
assert.deepStrictEqual(inspect(parser(' #1')), undefined);
|
|
37
37
|
assert.deepStrictEqual(inspect(parser('#12345678901234567')), [['#12345678901234567'], '']);
|
|
38
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(128)}a`)), [[`#${'1'.repeat(128)}a`], '']);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
it('valid', () => {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
2
|
import { union, rewrite, context, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
3
|
import { link } from '../link';
|
|
4
|
+
import { emoji } from './hashtag';
|
|
4
5
|
import { str } from '../../source';
|
|
5
6
|
import { define } from 'typed-dom';
|
|
6
7
|
|
|
7
8
|
export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
|
|
8
|
-
open('#', str(
|
|
9
|
+
open('#', str(new RegExp(String.raw`^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|${emoji}|['_])`, 'u'))),
|
|
9
10
|
context({ syntax: { inline: {
|
|
10
11
|
link: true,
|
|
11
12
|
autolink: false,
|
|
@@ -23,9 +23,6 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
23
23
|
assert.deepStrictEqual(inspect(parser(`#'`)), [[`#'`], '']);
|
|
24
24
|
assert.deepStrictEqual(inspect(parser(`#a''`)), [[`#a''`], '']);
|
|
25
25
|
assert.deepStrictEqual(inspect(parser('#_')), [['#_'], '']);
|
|
26
|
-
assert.deepStrictEqual(inspect(parser('#a_')), [['#a_'], '']);
|
|
27
|
-
assert.deepStrictEqual(inspect(parser('#a__b')), [['#a__b'], '']);
|
|
28
|
-
assert.deepStrictEqual(inspect(parser(`#a_'b`)), [[`#a_'b`], '']);
|
|
29
26
|
assert.deepStrictEqual(inspect(parser('#(a)')), [['#'], '(a)']);
|
|
30
27
|
assert.deepStrictEqual(inspect(parser('#{}')), [['#'], '{}']);
|
|
31
28
|
assert.deepStrictEqual(inspect(parser('#{{}')), [['#'], '{{}']);
|
|
@@ -38,6 +35,9 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
38
35
|
assert.deepStrictEqual(inspect(parser('a##1')), [['a##1'], '']);
|
|
39
36
|
assert.deepStrictEqual(inspect(parser('a##b')), [['a##b'], '']);
|
|
40
37
|
assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
|
|
38
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_`)), [[`#${'1'.repeat(127)}_`], '']);
|
|
39
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_a`)), [[`#${'1'.repeat(127)}_a`], '']);
|
|
40
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_'a`)), [[`#${'1'.repeat(127)}_'a`], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser(' #a')), undefined);
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -50,21 +50,22 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
50
50
|
assert.deepStrictEqual(inspect(parser('#a\\\n')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '\\\n']);
|
|
51
51
|
assert.deepStrictEqual(inspect(parser('#a)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], ')']);
|
|
52
52
|
assert.deepStrictEqual(inspect(parser('#a(b')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b']);
|
|
53
|
-
assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a href="/hashtags/a
|
|
54
|
-
assert.deepStrictEqual(inspect(parser('#
|
|
55
|
-
assert.deepStrictEqual(inspect(parser(`#a'`)), [[`<a href="/hashtags/a'" class="hashtag">#a'</a>`], '']);
|
|
56
|
-
assert.deepStrictEqual(inspect(parser(`#a(b')`)), [[`<a href="/hashtags/a(b')" class="hashtag">#a(b')</a>`], '']);
|
|
57
|
-
assert.deepStrictEqual(inspect(parser(`#a(b'')`)), [[`<a href="/hashtags/a" class="hashtag">#a</a>`], `(b'')`]);
|
|
58
|
-
assert.deepStrictEqual(inspect(parser(`#a('b)`)), [['<a href="/hashtags/a" class="hashtag">#a</a>'], `('b)`]);
|
|
53
|
+
assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b)']);
|
|
54
|
+
assert.deepStrictEqual(inspect(parser('#a_')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '_']);
|
|
59
55
|
assert.deepStrictEqual(inspect(parser('#a_b')), [['<a href="/hashtags/a_b" class="hashtag">#a_b</a>'], '']);
|
|
60
|
-
assert.deepStrictEqual(inspect(parser(`#
|
|
61
|
-
assert.deepStrictEqual(inspect(parser('#
|
|
62
|
-
assert.deepStrictEqual(inspect(parser('#a(b__c)')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '(b__c)']);
|
|
56
|
+
assert.deepStrictEqual(inspect(parser(`#a_'b`)), [['<a href="/hashtags/a" class="hashtag">#a</a>'], `_'b`]);
|
|
57
|
+
assert.deepStrictEqual(inspect(parser('#a__b')), [['<a href="/hashtags/a" class="hashtag">#a</a>'], '__b']);
|
|
63
58
|
assert.deepStrictEqual(inspect(parser('#あ')), [['<a href="/hashtags/あ" class="hashtag">#あ</a>'], '']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('#👩')), [['<a href="/hashtags/👩" class="hashtag">#👩</a>'], '']);
|
|
64
60
|
assert.deepStrictEqual(inspect(parser('#1a')), [['<a href="/hashtags/1a" class="hashtag">#1a</a>'], '']);
|
|
65
61
|
assert.deepStrictEqual(inspect(parser('#1あ')), [['<a href="/hashtags/1あ" class="hashtag">#1あ</a>'], '']);
|
|
62
|
+
assert.deepStrictEqual(inspect(parser('#1👩')), [['<a href="/hashtags/1👩" class="hashtag">#1👩</a>'], '']);
|
|
66
63
|
assert.deepStrictEqual(inspect(parser('#domain/a')), [['<a href="https://domain/hashtags/a" target="_blank" class="hashtag">#domain/a</a>'], '']);
|
|
67
64
|
assert.deepStrictEqual(inspect(parser('#domain.co.jp/a')), [['<a href="https://domain.co.jp/hashtags/a" target="_blank" class="hashtag">#domain.co.jp/a</a>'], '']);
|
|
65
|
+
// Reserved
|
|
66
|
+
assert.deepStrictEqual(inspect(parser(`#a'`)), [[`#a'`], '']);
|
|
67
|
+
assert.deepStrictEqual(inspect(parser(`#a'b`)), [[`#a'b`], '']);
|
|
68
|
+
assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`#a'_b`], '']);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
});
|
|
@@ -6,15 +6,22 @@ import { define } from 'typed-dom';
|
|
|
6
6
|
|
|
7
7
|
// https://example/hashtags/a must be a hashtag page or a redirect page going there.
|
|
8
8
|
|
|
9
|
+
// https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
|
|
10
|
+
export const emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
|
|
11
|
+
|
|
9
12
|
export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
|
|
10
13
|
open(
|
|
11
14
|
'#',
|
|
12
15
|
tails([
|
|
13
16
|
verify(
|
|
14
|
-
str(/^[0-9A-Za-z](?:[0-9A-Za-z
|
|
17
|
+
str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
|
|
15
18
|
([source]) => source.length <= 253 + 1),
|
|
16
19
|
verify(
|
|
17
|
-
str(
|
|
20
|
+
str(new RegExp(['^',
|
|
21
|
+
String.raw`(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|${emoji}))`,
|
|
22
|
+
String.raw`(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|_(?=[^\p{C}\p{S}\p{P}\s]|${emoji})){1,128}`,
|
|
23
|
+
String.raw`(?!_?(?:[^\p{C}\p{S}\p{P}\s]|${emoji})|')`,
|
|
24
|
+
].join(''), 'u')),
|
|
18
25
|
([source]) => source.length <= 128),
|
|
19
26
|
])),
|
|
20
27
|
context({ syntax: { inline: {
|
|
@@ -4,7 +4,7 @@ import { url } from './autolink/url';
|
|
|
4
4
|
import { email } from './autolink/email';
|
|
5
5
|
import { channel } from './autolink/channel';
|
|
6
6
|
import { account } from './autolink/account';
|
|
7
|
-
import { hashtag } from './autolink/hashtag';
|
|
7
|
+
import { hashtag, emoji } from './autolink/hashtag';
|
|
8
8
|
import { hashnum } from './autolink/hashnum';
|
|
9
9
|
import { anchor } from './autolink/anchor';
|
|
10
10
|
import { str } from '../source';
|
|
@@ -23,11 +23,11 @@ export const autolink: AutolinkParser = fmap(
|
|
|
23
23
|
// Escape unmatched account-like strings.
|
|
24
24
|
str(/^@+[0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
|
|
25
25
|
// Escape invalid leading characters.
|
|
26
|
-
str(
|
|
26
|
+
str(new RegExp(String.raw`^(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|['_])(?=#)`, 'u')),
|
|
27
27
|
hashtag,
|
|
28
28
|
hashnum,
|
|
29
29
|
// Escape unmatched hashtag-like strings.
|
|
30
|
-
str(
|
|
30
|
+
str(new RegExp(String.raw`^#+(?:[^\p{C}\p{S}\p{P}\s]|${emoji}|['_])*`, 'u')),
|
|
31
31
|
anchor,
|
|
32
32
|
])))),
|
|
33
33
|
ns => ns.length === 1 ? ns : [stringify(ns)]);
|
|
@@ -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
|
]);
|
|
@@ -83,7 +83,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
|
|
|
83
83
|
case uri.slice(0, 2) === '^/':
|
|
84
84
|
const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
|
|
85
85
|
return last.includes('.') // isFile
|
|
86
|
-
&& /^[0-9]*[
|
|
86
|
+
&& /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1))
|
|
87
87
|
? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
|
|
88
88
|
: `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
|
|
89
89
|
case host.origin === source.origin
|
|
@@ -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) => {
|
|
@@ -173,6 +173,7 @@ describe('Unit: parser/inline', () => {
|
|
|
173
173
|
assert.deepStrictEqual(inspect(parser('あい#b')), [['あ', 'い#b'], '']);
|
|
174
174
|
assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0a', 'あ#b'], '']);
|
|
175
175
|
assert.deepStrictEqual(inspect(parser('0aあい#b')), [['0a', 'あ', 'い#b'], '']);
|
|
176
|
+
assert.deepStrictEqual(inspect(parser('「#あ」')), [['「', '<a href="/hashtags/あ" class="hashtag">#あ</a>', '」'], '']);
|
|
176
177
|
assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
|
|
177
178
|
assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '<span class="linebreak"> </span>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
|
|
178
179
|
assert.deepStrictEqual(inspect(parser('*a*#b')), [['<em>a</em>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
|
|
@@ -186,6 +187,7 @@ describe('Unit: parser/inline', () => {
|
|
|
186
187
|
it('hashnum', () => {
|
|
187
188
|
assert.deepStrictEqual(inspect(parser('#1')), [['<a class="hashnum">#1</a>'], '']);
|
|
188
189
|
assert.deepStrictEqual(inspect(parser('#12345678901234567@a')), [['#12345678901234567@a'], '']);
|
|
190
|
+
assert.deepStrictEqual(inspect(parser('「#1」')), [['「', '<a class="hashnum">#1</a>', '」'], '']);
|
|
189
191
|
});
|
|
190
192
|
|
|
191
193
|
});
|
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
|
]);
|
|
@@ -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;
|