securemark 0.260.4 → 0.261.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 +12 -0
- package/README.md +12 -12
- package/design.md +0 -4
- package/dist/index.js +268 -243
- package/markdown.d.ts +7 -23
- package/package.json +6 -6
- package/src/combinator/data/parser/context/memo.ts +4 -0
- package/src/combinator/data/parser/context.ts +25 -16
- package/src/combinator/data/parser.ts +0 -1
- package/src/parser/api/bind.ts +2 -1
- package/src/parser/api/parse.test.ts +1 -1
- package/src/parser/api/parse.ts +2 -1
- package/src/parser/block/blockquote.test.ts +2 -2
- package/src/parser/block/dlist.test.ts +1 -1
- package/src/parser/block/extension/example.test.ts +1 -1
- package/src/parser/block/extension/fig.test.ts +1 -1
- package/src/parser/block/heading.test.ts +2 -2
- package/src/parser/block/paragraph.test.ts +1 -4
- package/src/parser/block/reply/cite.test.ts +5 -0
- package/src/parser/block/reply/cite.ts +5 -4
- package/src/parser/context.ts +8 -7
- package/src/parser/inline/autolink/anchor.test.ts +1 -0
- package/src/parser/inline/autolink/email.test.ts +3 -0
- package/src/parser/inline/autolink/email.ts +1 -1
- package/src/parser/inline/autolink/hashnum.test.ts +1 -2
- package/src/parser/inline/autolink/hashnum.ts +1 -1
- package/src/parser/inline/autolink/hashtag.test.ts +15 -12
- package/src/parser/inline/autolink/hashtag.ts +3 -3
- package/src/parser/inline/autolink/url.test.ts +1 -1
- package/src/parser/inline/autolink/url.ts +1 -1
- package/src/parser/inline/autolink.ts +14 -5
- package/src/parser/inline/deletion.test.ts +2 -2
- package/src/parser/inline/emphasis.test.ts +26 -35
- package/src/parser/inline/emphasis.ts +5 -12
- package/src/parser/inline/escape.ts +1 -10
- package/src/parser/inline/extension/index.test.ts +2 -2
- package/src/parser/inline/extension/index.ts +1 -1
- package/src/parser/inline/insertion.test.ts +2 -2
- package/src/parser/inline/link.test.ts +1 -1
- package/src/parser/inline/link.ts +2 -2
- package/src/parser/inline/mark.test.ts +1 -1
- package/src/parser/inline/media.ts +2 -2
- package/src/parser/inline/ruby.ts +10 -6
- package/src/parser/inline/strong.test.ts +25 -32
- package/src/parser/inline/strong.ts +4 -8
- package/src/parser/inline/template.ts +1 -1
- package/src/parser/inline.test.ts +18 -91
- package/src/parser/inline.ts +0 -3
- package/src/parser/locale/ja.ts +1 -9
- package/src/parser/locale.test.ts +1 -1
- package/src/parser/source/text.test.ts +9 -4
- package/src/parser/source/text.ts +9 -16
- package/src/parser/inline/emstrong.ts +0 -62
package/markdown.d.ts
CHANGED
|
@@ -635,7 +635,6 @@ export namespace MarkdownParser {
|
|
|
635
635
|
InlineParser.InsertionParser,
|
|
636
636
|
InlineParser.DeletionParser,
|
|
637
637
|
InlineParser.MarkParser,
|
|
638
|
-
InlineParser.EmStrongParser,
|
|
639
638
|
InlineParser.StrongParser,
|
|
640
639
|
InlineParser.EmphasisParser,
|
|
641
640
|
InlineParser.CodeParser,
|
|
@@ -829,8 +828,8 @@ export namespace MarkdownParser {
|
|
|
829
828
|
// [AB](a b)
|
|
830
829
|
Inline<'ruby'>,
|
|
831
830
|
Parser<HTMLElement, Context, [
|
|
832
|
-
|
|
833
|
-
|
|
831
|
+
SourceParser.StrParser,
|
|
832
|
+
SourceParser.StrParser,
|
|
834
833
|
]> {
|
|
835
834
|
}
|
|
836
835
|
export namespace RubyParser {
|
|
@@ -1032,36 +1031,20 @@ export namespace MarkdownParser {
|
|
|
1032
1031
|
MarkParser,
|
|
1033
1032
|
]> {
|
|
1034
1033
|
}
|
|
1035
|
-
export interface EmStrongParser extends
|
|
1036
|
-
// *abc*
|
|
1037
|
-
Inline<'emstrong'>,
|
|
1038
|
-
Parser<HTMLElement | string, Context, [
|
|
1039
|
-
InlineParser,
|
|
1040
|
-
InlineParser,
|
|
1041
|
-
]> {
|
|
1042
|
-
}
|
|
1043
1034
|
export interface StrongParser extends
|
|
1044
|
-
//
|
|
1035
|
+
// *abc*
|
|
1045
1036
|
Inline<'strong'>,
|
|
1046
1037
|
Parser<HTMLElement | string, Context, [
|
|
1047
1038
|
InlineParser,
|
|
1048
|
-
|
|
1049
|
-
EmStrongParser,
|
|
1050
|
-
StrongParser,
|
|
1051
|
-
]>,
|
|
1039
|
+
StrongParser,
|
|
1052
1040
|
]> {
|
|
1053
1041
|
}
|
|
1054
1042
|
export interface EmphasisParser extends
|
|
1055
|
-
//
|
|
1043
|
+
// _abc_
|
|
1056
1044
|
Inline<'emphasis'>,
|
|
1057
1045
|
Parser<HTMLElement | string, Context, [
|
|
1058
|
-
StrongParser,
|
|
1059
1046
|
InlineParser,
|
|
1060
|
-
|
|
1061
|
-
EmStrongParser,
|
|
1062
|
-
StrongParser,
|
|
1063
|
-
EmphasisParser,
|
|
1064
|
-
]>,
|
|
1047
|
+
EmphasisParser,
|
|
1065
1048
|
]> {
|
|
1066
1049
|
}
|
|
1067
1050
|
export interface CodeParser extends
|
|
@@ -1103,6 +1086,7 @@ export namespace MarkdownParser {
|
|
|
1103
1086
|
AutolinkParser.HashtagParser,
|
|
1104
1087
|
AutolinkParser.HashnumParser,
|
|
1105
1088
|
SourceParser.StrParser,
|
|
1089
|
+
SourceParser.StrParser,
|
|
1106
1090
|
AutolinkParser.AnchorParser,
|
|
1107
1091
|
]> {
|
|
1108
1092
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securemark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.261.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",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"@types/mocha": "9.1.1",
|
|
35
35
|
"@types/power-assert": "1.5.8",
|
|
36
36
|
"@types/prismjs": "1.26.0",
|
|
37
|
-
"@typescript-eslint/parser": "^5.
|
|
37
|
+
"@typescript-eslint/parser": "^5.33.0",
|
|
38
38
|
"babel-loader": "^8.2.5",
|
|
39
39
|
"babel-plugin-unassert": "^3.2.0",
|
|
40
40
|
"concurrently": "^7.3.0",
|
|
41
|
-
"eslint": "^8.
|
|
41
|
+
"eslint": "^8.21.0",
|
|
42
42
|
"eslint-plugin-redos": "^4.4.1",
|
|
43
43
|
"eslint-webpack-plugin": "^3.2.0",
|
|
44
44
|
"glob": "^8.0.3",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"karma-mocha": "^2.0.1",
|
|
50
50
|
"karma-power-assert": "^1.0.0",
|
|
51
51
|
"mocha": "^10.0.0",
|
|
52
|
-
"npm-check-updates": "^
|
|
52
|
+
"npm-check-updates": "^16.0.5",
|
|
53
53
|
"semver": "^7.3.7",
|
|
54
|
-
"spica": "0.0.
|
|
54
|
+
"spica": "0.0.591",
|
|
55
55
|
"ts-loader": "^9.3.1",
|
|
56
56
|
"typed-dom": "^0.0.301",
|
|
57
57
|
"typescript": "4.7.4",
|
|
58
|
-
"webpack": "^5.
|
|
58
|
+
"webpack": "^5.74.0",
|
|
59
59
|
"webpack-cli": "^4.10.0",
|
|
60
60
|
"webpack-merge": "^5.8.0"
|
|
61
61
|
},
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export class Memo {
|
|
2
|
+
constructor({ targets = ~0 } = {}) {
|
|
3
|
+
this.targets = targets;
|
|
4
|
+
}
|
|
5
|
+
public readonly targets: number;
|
|
2
6
|
private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
|
|
3
7
|
public get length(): number {
|
|
4
8
|
return this.memory.length;
|
|
@@ -5,12 +5,15 @@ import { Memo } from './context/memo';
|
|
|
5
5
|
|
|
6
6
|
export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
7
7
|
export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
8
|
+
if (!('memo' in base)) {
|
|
9
|
+
base.memo = undefined;
|
|
10
|
+
}
|
|
8
11
|
assert(Object.getPrototypeOf(base) === Object.prototype);
|
|
9
12
|
assert(Object.freeze(base));
|
|
10
13
|
const changes = Object.entries(base);
|
|
11
14
|
const values = Array(changes.length);
|
|
12
15
|
return ({ source, context }) =>
|
|
13
|
-
apply(parser, source, ObjectCreate(context), changes, values);
|
|
16
|
+
apply(parser, source, ObjectCreate(context), changes, values, true);
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export function context<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
@@ -23,18 +26,22 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
|
23
26
|
apply(parser, source, context, changes, values);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[]): Result<Tree<P>>;
|
|
27
|
-
function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[]): Result<T> {
|
|
29
|
+
function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[], reset?: boolean): Result<Tree<P>>;
|
|
30
|
+
function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[], reset = false): Result<T> {
|
|
28
31
|
if (context) for (let i = 0; i < changes.length; ++i) {
|
|
29
32
|
const change = changes[i];
|
|
30
33
|
const prop = change[0];
|
|
31
34
|
switch (prop) {
|
|
32
35
|
case 'resources':
|
|
36
|
+
if (!reset) break;
|
|
33
37
|
assert(typeof change[1] === 'object');
|
|
34
38
|
assert(context[prop] || !(prop in context));
|
|
35
39
|
if (prop in context && !hasOwnProperty(context, prop)) break;
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
context[prop as string] = ObjectCreate(change[1]);
|
|
41
|
+
break;
|
|
42
|
+
case 'memo':
|
|
43
|
+
if (!reset) break;
|
|
44
|
+
context.memo = new Memo({ targets: context.memo?.targets });
|
|
38
45
|
break;
|
|
39
46
|
default:
|
|
40
47
|
values[i] = context[prop];
|
|
@@ -47,7 +54,10 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
|
|
|
47
54
|
const prop = change[0];
|
|
48
55
|
switch (prop) {
|
|
49
56
|
case 'resources':
|
|
50
|
-
|
|
57
|
+
// @ts-expect-error
|
|
58
|
+
case 'memo':
|
|
59
|
+
if (!reset) break;
|
|
60
|
+
// fallthrough
|
|
51
61
|
default:
|
|
52
62
|
context[prop] = values[i];
|
|
53
63
|
values[i] = undefined;
|
|
@@ -61,26 +71,25 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
|
|
|
61
71
|
return creation(cost, precedence(prec, ({ source, context }) => {
|
|
62
72
|
if (source === '') return;
|
|
63
73
|
const memo = context.memo ??= new Memo();
|
|
64
|
-
context.memorable ??= ~0;
|
|
65
74
|
context.offset ??= 0;
|
|
66
75
|
const position = source.length + context.offset!;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const cache = syntax && memo.get(position, syntax,
|
|
76
|
+
const stateOuter = context.state ?? 0;
|
|
77
|
+
const stateInner = context.state = stateOuter | state;
|
|
78
|
+
const cache = syntax && stateInner & memo.targets && memo.get(position, syntax, stateInner);
|
|
70
79
|
const result: Result<T> = cache
|
|
71
80
|
? cache.length === 0
|
|
72
81
|
? undefined
|
|
73
82
|
: [cache[0], source.slice(cache[1])]
|
|
74
83
|
: parser!({ source, context });
|
|
75
|
-
if (syntax &&
|
|
76
|
-
cache ?? memo.set(position, syntax,
|
|
77
|
-
assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax,
|
|
84
|
+
if (syntax && stateOuter & memo.targets) {
|
|
85
|
+
cache ?? memo.set(position, syntax, stateInner, eval(result), source.length - exec(result, '').length);
|
|
86
|
+
assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, stateInner));
|
|
78
87
|
}
|
|
79
|
-
if (result && !
|
|
80
|
-
assert(!(
|
|
88
|
+
if (result && !stateOuter && memo.length! >= position + 2) {
|
|
89
|
+
assert(!(stateOuter & memo.targets));
|
|
81
90
|
memo.clear(position + 2);
|
|
82
91
|
}
|
|
83
|
-
context.state =
|
|
92
|
+
context.state = stateOuter;
|
|
84
93
|
return result;
|
|
85
94
|
}));
|
|
86
95
|
}
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { undefined, location } from 'spica/global';
|
|
|
2
2
|
import { ParserSettings, Progress } from '../../..';
|
|
3
3
|
import { MarkdownParser } from '../../../markdown';
|
|
4
4
|
import { eval } from '../../combinator/data/parser';
|
|
5
|
+
import { Memo } from '../../combinator/data/parser/context/memo';
|
|
5
6
|
import { segment, validate, MAX_INPUT_SIZE } from '../segment';
|
|
6
7
|
import { header } from '../header';
|
|
7
8
|
import { block } from '../block';
|
|
@@ -24,7 +25,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
24
25
|
let context: MarkdownParser.Context = {
|
|
25
26
|
...settings,
|
|
26
27
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
27
|
-
|
|
28
|
+
memo: new Memo({ targets: State.backtrackers }),
|
|
28
29
|
};
|
|
29
30
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
30
31
|
assert(!settings.id);
|
|
@@ -206,7 +206,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
206
206
|
it('footnote', () => {
|
|
207
207
|
const footnotes = { references: html('ol') };
|
|
208
208
|
assert.deepStrictEqual(
|
|
209
|
-
[...parse('$-a\n$$\n$$\n\n(($-a[[b]][[
|
|
209
|
+
[...parse('$-a\n$$\n$$\n\n(($-a[[b]][[c_d_]]))', { footnotes }).children].map(el => el.outerHTML),
|
|
210
210
|
[
|
|
211
211
|
'<figure data-type="math" data-label="$-a" data-group="$" data-number="1" id="label:$-a"><figcaption><span class="figindex">(1)</span><span class="figtext"></span></figcaption><div><div class="math" translate="no">$$\n$$</div></div></figure>',
|
|
212
212
|
'<p><sup class="annotation" id="annotation:ref:1" title="(1)[1][2]"><span hidden=""><a class="label" data-label="$-a" href="#label:$-a">(1)</a><sup class="reference" id="reference:ref:1" title="b"><span hidden="">b</span><a href="#reference:def:1">[1]</a></sup><sup class="reference" id="reference:ref:2" title="cd"><span hidden="">c<em>d</em></span><a href="#reference:def:2">[2]</a></sup></span><a href="#annotation:def:1">*1</a></sup></p>',
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { location } from 'spica/global';
|
|
|
2
2
|
import { ParserOptions } from '../../..';
|
|
3
3
|
import { MarkdownParser } from '../../../markdown';
|
|
4
4
|
import { eval } from '../../combinator/data/parser';
|
|
5
|
+
import { Memo } from '../../combinator/data/parser/context/memo';
|
|
5
6
|
import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
|
|
6
7
|
import { header } from '../header';
|
|
7
8
|
import { block } from '../block';
|
|
@@ -30,7 +31,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
30
31
|
...context?.resources && {
|
|
31
32
|
resources: context.resources,
|
|
32
33
|
},
|
|
33
|
-
|
|
34
|
+
memo: new Memo({ targets: State.backtrackers }),
|
|
34
35
|
};
|
|
35
36
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
36
37
|
const node = frag();
|
|
@@ -70,8 +70,8 @@ describe('Unit: parser/block/blockquote', () => {
|
|
|
70
70
|
assert.deepStrictEqual(inspect(parser('!> a')), [['<blockquote><section><p>a</p><ol class="references"></ol></section></blockquote>'], '']);
|
|
71
71
|
assert.deepStrictEqual(inspect(parser('!> a\n')), [['<blockquote><section><p>a</p><ol class="references"></ol></section></blockquote>'], '']);
|
|
72
72
|
assert.deepStrictEqual(inspect(parser('!> a\\\nb')), [['<blockquote><section><p>a<span class="linebreak"> </span>b</p><ol class="references"></ol></section></blockquote>'], '']);
|
|
73
|
-
assert.deepStrictEqual(inspect(parser('!>
|
|
74
|
-
assert.deepStrictEqual(inspect(parser('!>
|
|
73
|
+
assert.deepStrictEqual(inspect(parser('!> _a\nb_')), [['<blockquote><section><p><em>a<br>b</em></p><ol class="references"></ol></section></blockquote>'], '']);
|
|
74
|
+
assert.deepStrictEqual(inspect(parser('!> _a\n> b_')), [['<blockquote><section><p><em>a<br>b</em></p><ol class="references"></ol></section></blockquote>'], '']);
|
|
75
75
|
assert.deepStrictEqual(inspect(parser('!> a \n b c ')), [['<blockquote><section><p> a<br> b c</p><ol class="references"></ol></section></blockquote>'], '']);
|
|
76
76
|
assert.deepStrictEqual(inspect(parser('!>> a')), [['<blockquote><blockquote><section><p>a</p><ol class="references"></ol></section></blockquote></blockquote>'], '']);
|
|
77
77
|
assert.deepStrictEqual(inspect(parser('!>> a\n> b')), [['<blockquote><blockquote><section><p>a</p><ol class="references"></ol></section></blockquote><section><p>b</p><ol class="references"></ol></section></blockquote>'], '']);
|
|
@@ -68,7 +68,7 @@ describe('Unit: parser/block/dlist', () => {
|
|
|
68
68
|
assert.deepStrictEqual(inspect(parser('~ a [#b]')), [['<dl><dt id="index:b">a<span class="indexer" data-index="b"></span></dt><dd></dd></dl>'], '']);
|
|
69
69
|
assert.deepStrictEqual(inspect(parser('~ a [#b]\\')), [['<dl><dt id="index:a_[#b]">a [<a class="hashtag" href="/hashtags/b">#b</a>]</dt><dd></dd></dl>'], '']);
|
|
70
70
|
assert.deepStrictEqual(inspect(parser('~ A')), [['<dl><dt id="index:A">A</dt><dd></dd></dl>'], '']);
|
|
71
|
-
assert.deepStrictEqual(inspect(parser('~
|
|
71
|
+
assert.deepStrictEqual(inspect(parser('~ _A_')), [['<dl><dt id="index:A"><em>A</em></dt><dd></dd></dl>'], '']);
|
|
72
72
|
assert.deepStrictEqual(inspect(parser('~ `A`')), [['<dl><dt id="index:`A`"><code data-src="`A`">A</code></dt><dd></dd></dl>'], '']);
|
|
73
73
|
assert.deepStrictEqual(inspect(parser('~ ${A}$')), [['<dl><dt id="index:${A}$"><span class="math" translate="no" data-src="${A}$">${A}$</span></dt><dd></dd></dl>'], '']);
|
|
74
74
|
});
|
|
@@ -18,7 +18,7 @@ describe('Unit: parser/block/extension/example', () => {
|
|
|
18
18
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no"></pre><hr><section><ol class="references"></ol></section></aside>'], '']);
|
|
19
19
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no"></pre><hr><section><ol class="references"></ol></section></aside>'], '']);
|
|
20
20
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\na\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no">a</pre><hr><section><p>a</p><ol class="references"></ol></section></aside>'], '']);
|
|
21
|
-
assert.deepStrictEqual(inspect(parser('~~~example/markdown\
|
|
21
|
+
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n_a\nb_\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no">_a\nb_</pre><hr><section><p><em>a<br>b</em></p><ol class="references"></ol></section></aside>'], '']);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n$fig-a\n!https://host\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no">$fig-a\n!https://host</pre><hr><section><figure data-type="media" data-label="fig-a" data-group="fig" data-number="1"><figcaption><span class="figindex">Fig. 1. </span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure><ol class="references"></ol></section></aside>'], '']);
|
|
23
23
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n[$fig-a]\n!https://host\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no">[$fig-a]\n!https://host</pre><hr><section><figure data-type="media" data-label="fig-a" data-group="fig" data-number="1"><figcaption><span class="figindex">Fig. 1. </span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure><ol class="references"></ol></section></aside>'], '']);
|
|
24
24
|
assert.deepStrictEqual(inspect(parser('~~~example/markdown\n## a\n~~~')), [['<aside class="example" data-type="markdown"><pre translate="no">## a</pre><hr><section><h2>a</h2><ol class="references"></ol></section></aside>'], '']);
|
|
@@ -40,7 +40,7 @@ describe('Unit: parser/block/extension/fig', () => {
|
|
|
40
40
|
assert.deepStrictEqual(inspect(parser('[$group-name]\n> ')), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser('[$group-name]\n> \n')), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
|
|
42
42
|
assert.deepStrictEqual(inspect(parser('[$group-name]\n>\n~~~')), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><pre><br>~~~</pre></blockquote></div></figure>'], '']);
|
|
43
|
-
assert.deepStrictEqual(inspect(parser('[$group-name]\n!>
|
|
43
|
+
assert.deepStrictEqual(inspect(parser('[$group-name]\n!> _a_')), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><section><p><em>a</em></p><ol class="references"></ol></section></blockquote></div></figure>'], '']);
|
|
44
44
|
assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
|
|
45
45
|
assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}\n')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
|
|
46
46
|
assert.deepStrictEqual(inspect(parser('$group-name\n!https://host')), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt=""></a></div></figure>'], '']);
|
|
@@ -36,7 +36,7 @@ describe('Unit: parser/block/heading', () => {
|
|
|
36
36
|
assert.deepStrictEqual(inspect(parser('# a ')), [['<h1 id="index:a">a</h1>'], '']);
|
|
37
37
|
assert.deepStrictEqual(inspect(parser('# a b c \n')), [['<h1 id="index:a_b_c">a b c</h1>'], '']);
|
|
38
38
|
assert.deepStrictEqual(inspect(parser('# a\n')), [['<h1 id="index:a">a</h1>'], '']);
|
|
39
|
-
assert.deepStrictEqual(inspect(parser('#
|
|
39
|
+
assert.deepStrictEqual(inspect(parser('# _a_`b`${c}$')), [['<h1 id="index:a`b`${c}$"><em>a</em><code data-src="`b`">b</code><span class="math" translate="no" data-src="${c}$">${c}$</span></h1>'], '']);
|
|
40
40
|
assert.deepStrictEqual(inspect(parser('# a\\')), [['<h1 id="index:a">a</h1>'], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser('# a\\\n')), [['<h1 id="index:a">a</h1>'], '']);
|
|
42
42
|
assert.deepStrictEqual(inspect(parser('# \\')), [['<h1 id="index:\\">\\</h1>'], '']);
|
|
@@ -69,7 +69,7 @@ describe('Unit: parser/block/heading', () => {
|
|
|
69
69
|
assert.deepStrictEqual(inspect(parser('# a [#b ]')), [['<h1 id="index:b">a<span class="indexer" data-index="b"></span></h1>'], '']);
|
|
70
70
|
assert.deepStrictEqual(inspect(parser('# a [#b ]')), [['<h1 id="index:b">a<span class="indexer" data-index="b"></span></h1>'], '']);
|
|
71
71
|
assert.deepStrictEqual(inspect(parser('# a [#b c]')), [['<h1 id="index:b_c">a<span class="indexer" data-index="b_c"></span></h1>'], '']);
|
|
72
|
-
assert.deepStrictEqual(inspect(parser('# a [
|
|
72
|
+
assert.deepStrictEqual(inspect(parser('# a [#_b_`c`${d}$]')), [['<h1 id="index:b`c`${d}$">a<span class="indexer" data-index="b`c`${d}$"></span></h1>'], '']);
|
|
73
73
|
assert.deepStrictEqual(inspect(parser('# a [#@a]')), [['<h1 id="index:@a">a<span class="indexer" data-index="@a"></span></h1>'], '']);
|
|
74
74
|
assert.deepStrictEqual(inspect(parser('# a [#http://host]')), [['<h1 id="index:http://host">a<span class="indexer" data-index="http://host"></span></h1>'], '']);
|
|
75
75
|
assert.deepStrictEqual(inspect(parser('# a [#!http://host]')), [['<h1 id="index:!http://host">a<span class="indexer" data-index="!http://host"></span></h1>'], '']);
|
|
@@ -27,11 +27,8 @@ describe('Unit: parser/block/paragraph', () => {
|
|
|
27
27
|
assert.deepStrictEqual(inspect(parser('<wbr>\na')), [['<p><wbr><br>a</p>'], '']);
|
|
28
28
|
assert.deepStrictEqual(inspect(parser('a\n<wbr>\n')), [['<p>a<br><wbr></p>'], '']);
|
|
29
29
|
assert.deepStrictEqual(inspect(parser('a\n<wbr>\nb')), [['<p>a<br><wbr><br>b</p>'], '']);
|
|
30
|
+
assert.deepStrictEqual(inspect(parser('_a\n<wbr>_\nb')), [['<p>_a<br><wbr>_<br>b</p>'], '']);
|
|
30
31
|
assert.deepStrictEqual(inspect(parser('*a\n<wbr>*\nb')), [['<p>*a<br><wbr>*<br>b</p>'], '']);
|
|
31
|
-
assert.deepStrictEqual(inspect(parser('**a\n<wbr>**\nb')), [['<p>**a<br><wbr>**<br>b</p>'], '']);
|
|
32
|
-
assert.deepStrictEqual(inspect(parser('***a\n<wbr>***\nb')), [['<p>***a<br><wbr>***<br>b</p>'], '']);
|
|
33
|
-
assert.deepStrictEqual(inspect(parser('***a*b\n<wbr>**\nc')), [['<p>**<em>a</em>b<br><wbr>**<br>c</p>'], '']);
|
|
34
|
-
assert.deepStrictEqual(inspect(parser('***a**b\n<wbr>*\nc')), [['<p>*<strong>a</strong>b<br><wbr>*<br>c</p>'], '']);
|
|
35
32
|
assert.deepStrictEqual(inspect(parser('==a\n<wbr>==\nb')), [['<p>==a<br><wbr>==<br>b</p>'], '']);
|
|
36
33
|
assert.deepStrictEqual(inspect(parser('\ta')), [['<p>\ta</p>'], '']);
|
|
37
34
|
});
|
|
@@ -39,11 +39,16 @@ describe('Unit: parser/block/reply/cite', () => {
|
|
|
39
39
|
assert.deepStrictEqual(inspect(parser('>>>0\n>>')), [['<span class="cite">>><a class="anchor" href="?at=0" data-depth="2">>0</a></span>', '<br>'], '>>']);
|
|
40
40
|
assert.deepStrictEqual(inspect(parser('>>>0\n>>1')), [['<span class="cite">>><a class="anchor" href="?at=0" data-depth="2">>0</a></span>', '<br>', '<span class="cite">><a class="anchor" href="?at=1" data-depth="1">>1</a></span>', '<br>'], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser('>>.')), [['<span class="cite">><a class="anchor" data-depth="1">>.</a></span>', '<br>'], '']);
|
|
42
|
+
assert.deepStrictEqual(inspect(parser('>>. ')), [['<span class="cite">><a class="anchor" data-depth="1">>.</a></span>', '<br>'], '']);
|
|
43
|
+
assert.deepStrictEqual(inspect(parser('>>.\n')), [['<span class="cite">><a class="anchor" data-depth="1">>.</a></span>', '<br>'], '']);
|
|
42
44
|
assert.deepStrictEqual(inspect(parser('>>#')), [['<span class="cite">><a class="anchor" data-depth="1">>#</a></span>', '<br>'], '']);
|
|
45
|
+
assert.deepStrictEqual(inspect(parser('>>#\n')), [['<span class="cite">><a class="anchor" data-depth="1">>#</a></span>', '<br>'], '']);
|
|
43
46
|
assert.deepStrictEqual(inspect(parser('>>#a')), [['<span class="cite">><a class="anchor" data-depth="1">>#a</a></span>', '<br>'], '']);
|
|
44
47
|
assert.deepStrictEqual(inspect(parser('>>#index:a')), [['<span class="cite">><a class="anchor" data-depth="1">>#index:a</a></span>', '<br>'], '']);
|
|
45
48
|
assert.deepStrictEqual(inspect(parser('>>#:~:text=a')), [['<span class="cite">><a class="anchor" data-depth="1">>#:~:text=a</a></span>', '<br>'], '']);
|
|
46
49
|
assert.deepStrictEqual(inspect(parser('>>http://host')), [['<span class="cite">><a class="anchor" href="http://host" target="_blank" data-depth="1">>http://host</a></span>', '<br>'], '']);
|
|
50
|
+
assert.deepStrictEqual(inspect(parser('>>http://host ')), [['<span class="cite">><a class="anchor" href="http://host" target="_blank" data-depth="1">>http://host</a></span>', '<br>'], '']);
|
|
51
|
+
assert.deepStrictEqual(inspect(parser('>>http://host\n')), [['<span class="cite">><a class="anchor" href="http://host" target="_blank" data-depth="1">>http://host</a></span>', '<br>'], '']);
|
|
47
52
|
assert.deepStrictEqual(inspect(parser('>>https://host')), [['<span class="cite">><a class="anchor" href="https://host" target="_blank" data-depth="1">>https://host</a></span>', '<br>'], '']);
|
|
48
53
|
});
|
|
49
54
|
|
|
@@ -7,14 +7,15 @@ import { html, define, defrag } from 'typed-dom/dom';
|
|
|
7
7
|
export const cite: ReplyParser.CiteParser = creation(1, false, line(fmap(validate(
|
|
8
8
|
'>>',
|
|
9
9
|
reverse(tails([
|
|
10
|
-
str(/^>*(?=>>[^>\s]
|
|
10
|
+
str(/^>*(?=>>[^>\s]+\s*$)/),
|
|
11
11
|
union([
|
|
12
12
|
anchor,
|
|
13
13
|
// Subject page representation.
|
|
14
14
|
// リンクの実装は後で検討
|
|
15
|
-
focus(/^>>\.
|
|
16
|
-
focus(/^>>#\S*
|
|
17
|
-
|
|
15
|
+
focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
|
|
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)], '']),
|
|
18
19
|
]),
|
|
19
20
|
]))),
|
|
20
21
|
([el, quotes = '']: [HTMLElement, string?]) => [
|
package/src/parser/context.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export const enum Syntax {
|
|
2
|
-
reference = 1 <<
|
|
3
|
-
comment = 1 <<
|
|
4
|
-
index = 1 <<
|
|
5
|
-
placeholder = 1 <<
|
|
2
|
+
reference = 1 << 13,
|
|
3
|
+
comment = 1 << 12,
|
|
4
|
+
index = 1 << 11,
|
|
5
|
+
placeholder = 1 << 10,
|
|
6
|
+
ruby = 1 << 9,
|
|
6
7
|
link = 1 << 8,
|
|
7
8
|
bracket = 1 << 7,
|
|
8
9
|
media = 1 << 6,
|
|
@@ -25,15 +26,15 @@ export const enum State {
|
|
|
25
26
|
autolink = 1 << 1,
|
|
26
27
|
shortcut = 1 << 0,
|
|
27
28
|
none = 0,
|
|
28
|
-
|
|
29
|
+
all = ~0,
|
|
30
|
+
linkers = 0
|
|
29
31
|
| State.annotation
|
|
30
32
|
| State.reference
|
|
31
33
|
| State.index
|
|
32
34
|
| State.label
|
|
33
35
|
| State.link
|
|
34
|
-
| State.media
|
|
35
36
|
| State.autolink,
|
|
36
|
-
|
|
37
|
+
backtrackers = 0
|
|
37
38
|
| State.annotation
|
|
38
39
|
| State.reference
|
|
39
40
|
| State.index
|
|
@@ -16,6 +16,7 @@ describe('Unit: parser/inline/autolink/anchor', () => {
|
|
|
16
16
|
assert.deepStrictEqual(inspect(parser('>>https://host')), undefined);
|
|
17
17
|
assert.deepStrictEqual(inspect(parser('>>tel:1234567890')), undefined);
|
|
18
18
|
assert.deepStrictEqual(inspect(parser('>>>')), undefined);
|
|
19
|
+
assert.deepStrictEqual(inspect(parser('a>>0')), [['a>>0'], '']);
|
|
19
20
|
assert.deepStrictEqual(inspect(parser(' >>0')), undefined);
|
|
20
21
|
});
|
|
21
22
|
|
|
@@ -8,6 +8,9 @@ describe('Unit: parser/inline/autolink/email', () => {
|
|
|
8
8
|
|
|
9
9
|
it('invalid', () => {
|
|
10
10
|
assert.deepStrictEqual(inspect(parser('')), undefined);
|
|
11
|
+
assert.deepStrictEqual(inspect(parser('a_b')), [['a'], '_b']);
|
|
12
|
+
assert.deepStrictEqual(inspect(parser('a_b_c')), [['a'], '_b_c']);
|
|
13
|
+
assert.deepStrictEqual(inspect(parser(`a_${'b'.repeat(255 - 2)}@`)), [[`a_${'b'.repeat(255 - 2)}@`], '']);
|
|
11
14
|
assert.deepStrictEqual(inspect(parser('a@')), [['a@'], '']);
|
|
12
15
|
assert.deepStrictEqual(inspect(parser('a@+')), [['a@'], '+']);
|
|
13
16
|
assert.deepStrictEqual(inspect(parser('a@_')), [['a@'], '_']);
|
|
@@ -6,6 +6,6 @@ import { html } from 'typed-dom/dom';
|
|
|
6
6
|
// https://html.spec.whatwg.org/multipage/input.html
|
|
7
7
|
|
|
8
8
|
export const email: AutolinkParser.EmailParser = creation(rewrite(verify(
|
|
9
|
-
str(/^[0-9a-z](?:[
|
|
9
|
+
str(/^[0-9a-z](?:[_.+-](?=[0-9a-z])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*(?![0-9a-z])/i),
|
|
10
10
|
([source]) => source.length <= 255),
|
|
11
11
|
({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], '']));
|
|
@@ -20,8 +20,6 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
|
|
|
20
20
|
assert.deepStrictEqual(inspect(parser('##')), [['##'], '']);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser('##1')), [['##1'], '']);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('###1')), [['###1'], '']);
|
|
23
|
-
assert.deepStrictEqual(inspect(parser(`#1'`)), [[`#1'`], '']);
|
|
24
|
-
assert.deepStrictEqual(inspect(parser(`#1''`)), [[`#1''`], '']);
|
|
25
23
|
assert.deepStrictEqual(inspect(parser('#{}')), [['#'], '{}']);
|
|
26
24
|
assert.deepStrictEqual(inspect(parser('#{{}')), [['#'], '{{}']);
|
|
27
25
|
assert.deepStrictEqual(inspect(parser('#{}}')), [['#'], '{}}']);
|
|
@@ -40,6 +38,7 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
|
|
|
40
38
|
|
|
41
39
|
it('valid', () => {
|
|
42
40
|
assert.deepStrictEqual(inspect(parser('#1')), [['<a class="hashnum">#1</a>'], '']);
|
|
41
|
+
assert.deepStrictEqual(inspect(parser('#1_')), [['<a class="hashnum">#1</a>'], '_']);
|
|
43
42
|
assert.deepStrictEqual(inspect(parser('#1 ')), [['<a class="hashnum">#1</a>'], ' ']);
|
|
44
43
|
assert.deepStrictEqual(inspect(parser('#1\n')), [['<a class="hashnum">#1</a>'], '\n']);
|
|
45
44
|
assert.deepStrictEqual(inspect(parser('#1\\')), [['<a class="hashnum">#1</a>'], '\\']);
|
|
@@ -8,7 +8,7 @@ import { define } from 'typed-dom/dom';
|
|
|
8
8
|
|
|
9
9
|
export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
|
|
10
10
|
constraint(State.shortcut, false,
|
|
11
|
-
open('#', str(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|
|
|
11
|
+
open('#', str(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|')/u.source.replace(/emoji/, emoji), 'u')))),
|
|
12
12
|
convert(
|
|
13
13
|
source => `[${source}]{ ${source.slice(1)} }`,
|
|
14
14
|
union([unsafelink]))),
|
|
@@ -20,9 +20,8 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
20
20
|
assert.deepStrictEqual(inspect(parser('##')), [['##'], '']);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser('##a')), [['##a'], '']);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('###a')), [['###a'], '']);
|
|
23
|
-
assert.deepStrictEqual(inspect(parser(
|
|
24
|
-
assert.deepStrictEqual(inspect(parser(
|
|
25
|
-
assert.deepStrictEqual(inspect(parser('#_')), [['#_'], '']);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('#_')), [['#'], '_']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('#_a')), [['#'], '_a']);
|
|
26
25
|
assert.deepStrictEqual(inspect(parser('#(a)')), [['#'], '(a)']);
|
|
27
26
|
assert.deepStrictEqual(inspect(parser('#{}')), [['#'], '{}']);
|
|
28
27
|
assert.deepStrictEqual(inspect(parser('#{{}')), [['#'], '{{}']);
|
|
@@ -35,9 +34,9 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
35
34
|
assert.deepStrictEqual(inspect(parser('a##1')), [['a##1'], '']);
|
|
36
35
|
assert.deepStrictEqual(inspect(parser('a##b')), [['a##b'], '']);
|
|
37
36
|
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)}
|
|
40
|
-
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_'a`)), [[`#${'1'.repeat(127)}
|
|
37
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_`)), [[`#${'1'.repeat(127)}`], '_']);
|
|
38
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_a`)), [[`#${'1'.repeat(127)}`], '_a']);
|
|
39
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(127)}_'a`)), [[`#${'1'.repeat(127)}`], `_'a`]);
|
|
41
40
|
assert.deepStrictEqual(inspect(parser(' #a')), undefined);
|
|
42
41
|
});
|
|
43
42
|
|
|
@@ -52,9 +51,8 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
52
51
|
assert.deepStrictEqual(inspect(parser('#a(b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b']);
|
|
53
52
|
assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b)']);
|
|
54
53
|
assert.deepStrictEqual(inspect(parser('#a_')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '_']);
|
|
55
|
-
assert.deepStrictEqual(inspect(parser('#a_b')), [['<a class="hashtag" href="/hashtags/a_b">#a_b</a>'], '']);
|
|
56
|
-
assert.deepStrictEqual(inspect(parser(`#a_'b`)), [['<a class="hashtag" href="/hashtags/a">#a</a>'], `_'b`]);
|
|
57
54
|
assert.deepStrictEqual(inspect(parser('#a__b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
|
|
55
|
+
assert.deepStrictEqual(inspect(parser('#a_b')), [['<a class="hashtag" href="/hashtags/a_b">#a_b</a>'], '']);
|
|
58
56
|
assert.deepStrictEqual(inspect(parser('#あ')), [['<a class="hashtag" href="/hashtags/あ">#あ</a>'], '']);
|
|
59
57
|
assert.deepStrictEqual(inspect(parser('#👩')), [['<a class="hashtag" href="/hashtags/👩">#👩</a>'], '']);
|
|
60
58
|
assert.deepStrictEqual(inspect(parser('#1a')), [['<a class="hashtag" href="/hashtags/1a">#1a</a>'], '']);
|
|
@@ -62,10 +60,15 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
62
60
|
assert.deepStrictEqual(inspect(parser('#1👩')), [['<a class="hashtag" href="/hashtags/1👩">#1👩</a>'], '']);
|
|
63
61
|
assert.deepStrictEqual(inspect(parser('#domain/a')), [['<a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a>'], '']);
|
|
64
62
|
assert.deepStrictEqual(inspect(parser('#domain.co.jp/a')), [['<a class="hashtag" href="https://domain.co.jp/hashtags/a" target="_blank">#domain.co.jp/a</a>'], '']);
|
|
65
|
-
|
|
66
|
-
assert.deepStrictEqual(inspect(parser(`#
|
|
67
|
-
assert.deepStrictEqual(inspect(parser(`#
|
|
68
|
-
assert.deepStrictEqual(inspect(parser(`#
|
|
63
|
+
assert.deepStrictEqual(inspect(parser(`#'0`)), [[`<a class="hashtag" href="/hashtags/'0">#'0</a>`], '']);
|
|
64
|
+
assert.deepStrictEqual(inspect(parser(`#'00`)), [[`<a class="hashtag" href="/hashtags/'00">#'00</a>`], '']);
|
|
65
|
+
assert.deepStrictEqual(inspect(parser(`#1'`)), [[`<a class="hashtag" href="/hashtags/1'">#1'</a>`], '']);
|
|
66
|
+
assert.deepStrictEqual(inspect(parser(`#1''`)), [[`<a class="hashtag" href="/hashtags/1'">#1'</a>`], `'`]);
|
|
67
|
+
assert.deepStrictEqual(inspect(parser(`#a'`)), [[`<a class="hashtag" href="/hashtags/a'">#a'</a>`], '']);
|
|
68
|
+
assert.deepStrictEqual(inspect(parser(`#a''`)), [[`<a class="hashtag" href="/hashtags/a'">#a'</a>`], `'`]);
|
|
69
|
+
assert.deepStrictEqual(inspect(parser(`#a'b`)), [[`<a class="hashtag" href="/hashtags/a'b">#a'b</a>`], '']);
|
|
70
|
+
assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`<a class="hashtag" href="/hashtags/a'_b">#a'_b</a>`], '']);
|
|
71
|
+
assert.deepStrictEqual(inspect(parser(`#a_'b`)), [[`<a class="hashtag" href="/hashtags/a_'b">#a_'b</a>`], '']);
|
|
69
72
|
});
|
|
70
73
|
|
|
71
74
|
});
|
|
@@ -20,9 +20,9 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
|
|
|
20
20
|
([source]) => source.length <= 253 + 1),
|
|
21
21
|
verify(
|
|
22
22
|
str(new RegExp([
|
|
23
|
-
/^(?=[0-9]{
|
|
24
|
-
/(?:[^\p{C}\p{S}\p{P}\s]|emoji|_(?=[^\p{C}\p{S}\p{P}\s]|emoji)){1,128}/u.source,
|
|
25
|
-
/(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji
|
|
23
|
+
/^(?=(?:[0-9]{1,127}_?)?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source,
|
|
24
|
+
/(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|')){1,128}/u.source,
|
|
25
|
+
/(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'))/u.source,
|
|
26
26
|
].join('').replace(/emoji/g, emoji), 'u')),
|
|
27
27
|
([source]) => source.length <= 128),
|
|
28
28
|
]))),
|
|
@@ -58,7 +58,7 @@ describe('Unit: parser/inline/autolink/url', () => {
|
|
|
58
58
|
assert.deepStrictEqual(inspect(parser('http://host`')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '`']);
|
|
59
59
|
assert.deepStrictEqual(inspect(parser('http://host|')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '|']);
|
|
60
60
|
assert.deepStrictEqual(inspect(parser('http://host&')), [['<a class="url" href="http://host&" target="_blank">http://host&</a>'], '']);
|
|
61
|
-
assert.deepStrictEqual(inspect(parser('http://host_')), [['<a class="url" href="http://
|
|
61
|
+
assert.deepStrictEqual(inspect(parser('http://host_')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '_']);
|
|
62
62
|
assert.deepStrictEqual(inspect(parser('http://host$')), [['<a class="url" href="http://host$" target="_blank">http://host$</a>'], '']);
|
|
63
63
|
assert.deepStrictEqual(inspect(parser('http://user@host')), [['<a class="url" href="http://user@host" target="_blank">http://user@host</a>'], '']);
|
|
64
64
|
assert.deepStrictEqual(inspect(parser('http://host#@')), [['<a class="url" href="http://host#@" target="_blank">http://host#@</a>'], '']);
|
|
@@ -3,7 +3,7 @@ import { union, some, creation, precedence, validate, focus, rewrite, convert, s
|
|
|
3
3
|
import { unsafelink } from '../link';
|
|
4
4
|
import { unescsource } from '../../source';
|
|
5
5
|
|
|
6
|
-
const closer = /^[
|
|
6
|
+
const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
|
|
7
7
|
|
|
8
8
|
export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'https://'], rewrite(
|
|
9
9
|
open(
|