securemark 0.257.0 → 0.257.3
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 +21 -8
- package/dist/index.js +94 -102
- package/markdown.d.ts +21 -22
- package/package.json +1 -1
- package/src/combinator/data/parser/inits.ts +1 -1
- package/src/combinator/data/parser/sequence.ts +1 -1
- package/src/debug.test.ts +1 -1
- package/src/parser/block/table.test.ts +5 -0
- package/src/parser/block/table.ts +6 -5
- package/src/parser/inline/annotation.test.ts +6 -5
- package/src/parser/inline/annotation.ts +5 -4
- package/src/parser/inline/autolink/account.ts +3 -7
- package/src/parser/inline/autolink/anchor.ts +3 -7
- package/src/parser/inline/autolink/hashnum.ts +3 -7
- package/src/parser/inline/autolink/hashtag.ts +3 -7
- package/src/parser/inline/autolink/url.test.ts +1 -0
- package/src/parser/inline/autolink/url.ts +4 -5
- package/src/parser/inline/bracket.test.ts +3 -1
- package/src/parser/inline/bracket.ts +6 -6
- package/src/parser/inline/comment.test.ts +1 -0
- package/src/parser/inline/deletion.ts +1 -1
- package/src/parser/inline/extension/index.ts +2 -2
- package/src/parser/inline/extension/placeholder.ts +2 -2
- package/src/parser/inline/insertion.ts +1 -1
- package/src/parser/inline/link.ts +55 -14
- package/src/parser/inline/mark.ts +1 -1
- package/src/parser/inline/math.ts +8 -9
- package/src/parser/inline/media.ts +5 -5
- package/src/parser/inline/reference.test.ts +6 -5
- package/src/parser/inline/reference.ts +7 -15
- package/src/parser/inline/template.ts +1 -1
- package/src/parser/inline.test.ts +5 -3
- package/src/parser/inline.ts +1 -0
- package/src/parser/util.ts +18 -18
package/markdown.d.ts
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { Parser, Ctx } from './src/combinator/data/parser';
|
|
2
2
|
import { Dict } from 'spica/dict';
|
|
3
3
|
|
|
4
|
-
/*
|
|
5
|
-
|
|
6
|
-
Operator precedence
|
|
7
|
-
|
|
8
|
-
9: \n, \\\n
|
|
9
|
-
8: `, "
|
|
10
|
-
7: $
|
|
11
|
-
6: (()), [[]]
|
|
12
|
-
5: <tag></tag>
|
|
13
|
-
4: [% %]
|
|
14
|
-
3: (), [], {}
|
|
15
|
-
2: ==, ++, ~~
|
|
16
|
-
1: *, **
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
4
|
declare abstract class Markdown<T> {
|
|
21
5
|
private parser?: T;
|
|
22
6
|
}
|
|
@@ -869,11 +853,20 @@ export namespace MarkdownParser {
|
|
|
869
853
|
// { uri }
|
|
870
854
|
// [abc]{uri nofollow}
|
|
871
855
|
Inline<'link'>,
|
|
872
|
-
Parser<
|
|
856
|
+
Parser<HTMLElement | string, Context, [
|
|
873
857
|
LinkParser.ContentParser,
|
|
874
858
|
LinkParser.ParameterParser,
|
|
875
859
|
]> {
|
|
876
860
|
}
|
|
861
|
+
export interface TextLinkParser extends
|
|
862
|
+
// { uri }
|
|
863
|
+
// [abc]{uri nofollow}
|
|
864
|
+
Inline<'textlink'>,
|
|
865
|
+
Parser<HTMLAnchorElement, Context, [
|
|
866
|
+
LinkParser.TextParser,
|
|
867
|
+
LinkParser.ParameterParser,
|
|
868
|
+
]> {
|
|
869
|
+
}
|
|
877
870
|
export namespace LinkParser {
|
|
878
871
|
export interface ContentParser extends
|
|
879
872
|
Inline<'link/content'>,
|
|
@@ -883,6 +876,12 @@ export namespace MarkdownParser {
|
|
|
883
876
|
InlineParser,
|
|
884
877
|
]> {
|
|
885
878
|
}
|
|
879
|
+
export interface TextParser extends
|
|
880
|
+
Inline<'link/text'>,
|
|
881
|
+
Parser<string[], Context, [
|
|
882
|
+
SourceParser.UnescapableSourceParser,
|
|
883
|
+
]> {
|
|
884
|
+
}
|
|
886
885
|
export interface ParameterParser extends
|
|
887
886
|
Inline<'link/parameter'>,
|
|
888
887
|
Parser<string[], Context, [
|
|
@@ -1107,7 +1106,7 @@ export namespace MarkdownParser {
|
|
|
1107
1106
|
// https://host
|
|
1108
1107
|
Inline<'url'>,
|
|
1109
1108
|
Parser<HTMLAnchorElement, Context, [
|
|
1110
|
-
|
|
1109
|
+
TextLinkParser,
|
|
1111
1110
|
]> {
|
|
1112
1111
|
}
|
|
1113
1112
|
export namespace UrlParser {
|
|
@@ -1149,28 +1148,28 @@ export namespace MarkdownParser {
|
|
|
1149
1148
|
// @user
|
|
1150
1149
|
Inline<'account'>,
|
|
1151
1150
|
Parser<HTMLAnchorElement, Context, [
|
|
1152
|
-
|
|
1151
|
+
TextLinkParser,
|
|
1153
1152
|
]> {
|
|
1154
1153
|
}
|
|
1155
1154
|
export interface HashtagParser extends
|
|
1156
1155
|
// #tag
|
|
1157
1156
|
Inline<'hashtag'>,
|
|
1158
1157
|
Parser<HTMLAnchorElement, Context, [
|
|
1159
|
-
|
|
1158
|
+
TextLinkParser,
|
|
1160
1159
|
]> {
|
|
1161
1160
|
}
|
|
1162
1161
|
export interface HashnumParser extends
|
|
1163
1162
|
// #1
|
|
1164
1163
|
Inline<'hashnum'>,
|
|
1165
1164
|
Parser<HTMLAnchorElement, Context, [
|
|
1166
|
-
|
|
1165
|
+
TextLinkParser,
|
|
1167
1166
|
]> {
|
|
1168
1167
|
}
|
|
1169
1168
|
export interface AnchorParser extends
|
|
1170
1169
|
// >>1
|
|
1171
1170
|
Inline<'anchor'>,
|
|
1172
1171
|
Parser<HTMLAnchorElement, Context, [
|
|
1173
|
-
|
|
1172
|
+
TextLinkParser,
|
|
1174
1173
|
]> {
|
|
1175
1174
|
}
|
|
1176
1175
|
}
|
package/package.json
CHANGED
|
@@ -11,10 +11,10 @@ export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
|
11
11
|
let nodes: T[] | undefined;
|
|
12
12
|
for (let i = 0, len = parsers.length; i < len; ++i) {
|
|
13
13
|
if (rest === '') break;
|
|
14
|
+
if (context.delimiters?.match(rest, context.precedence)) break;
|
|
14
15
|
const result = parsers[i](rest, context);
|
|
15
16
|
assert(check(rest, result));
|
|
16
17
|
if (!result) break;
|
|
17
|
-
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
|
@@ -11,10 +11,10 @@ export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D
|
|
|
11
11
|
let nodes: T[] | undefined;
|
|
12
12
|
for (let i = 0, len = parsers.length; i < len; ++i) {
|
|
13
13
|
if (rest === '') return;
|
|
14
|
+
if (context.delimiters?.match(rest, context.precedence)) break;
|
|
14
15
|
const result = parsers[i](rest, context);
|
|
15
16
|
assert(check(rest, result));
|
|
16
17
|
if (!result) return;
|
|
17
|
-
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
package/src/debug.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { querySelector, querySelectorAll } from 'typed-dom/query';
|
|
|
5
5
|
export function inspect(result: Result<HTMLElement | string>, until: number | string = Infinity): Result<string> {
|
|
6
6
|
return result && [
|
|
7
7
|
eval(result).map((node, i, nodes) => {
|
|
8
|
-
assert(node || node === '' && '([{'.includes(nodes[i + 1]
|
|
8
|
+
assert(node || node === '' && '([{'.includes(nodes[i + 1][0]));
|
|
9
9
|
if (typeof node === 'string') return node;
|
|
10
10
|
node = node.cloneNode(true);
|
|
11
11
|
assert(!querySelector(node, '.invalid[data-invalid-message$="."]'));
|
|
@@ -29,6 +29,11 @@ describe('Unit: parser/block/table', () => {
|
|
|
29
29
|
assert.deepStrictEqual(inspect(parser('|\n|-\n|')), [['<table><thead><tr></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
30
30
|
assert.deepStrictEqual(inspect(parser('||\n|-|\n||')), [['<table><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr></tbody></table>'], '']);
|
|
31
31
|
assert.deepStrictEqual(inspect(parser('|||\n|-|-|\n|||')), [['<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr></tbody></table>'], '']);
|
|
32
|
+
assert.deepStrictEqual(inspect(parser('|"|\n|-\n|')), [['<table><thead><tr><th>"</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
33
|
+
assert.deepStrictEqual(inspect(parser('|`|\n|-\n|')), [['<table><thead><tr><th>`</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
34
|
+
assert.deepStrictEqual(inspect(parser('|`|`|\n|-\n|')), [['<table><thead><tr><th><code data-src="`|`">|</code></th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
35
|
+
assert.deepStrictEqual(inspect(parser('|((|\n|-\n|')), [['<table><thead><tr><th>((</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
36
|
+
assert.deepStrictEqual(inspect(parser('|${|\n|-\n|')), [['<table><thead><tr><th>${</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
|
|
32
37
|
assert.deepStrictEqual(inspect(parser('|a|b|\n|-|-|\n|1|2|')), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
|
|
33
38
|
assert.deepStrictEqual(inspect(parser('|a|b\n|-|-\n|1|2')), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
|
|
34
39
|
assert.deepStrictEqual(inspect(parser('|a|\n|-|\n|1|')), [['<table><thead><tr><th>a</th></tr></thead><tbody><tr><td>1</td></tr></tbody></table>'], '']);
|
|
@@ -2,6 +2,7 @@ import { TableParser } from '../block';
|
|
|
2
2
|
import { union, sequence, some, block, line, validate, focus, rewrite, creator, surround, open, fallback, lazy, fmap } from '../../combinator';
|
|
3
3
|
import { inline } from '../inline';
|
|
4
4
|
import { contentline } from '../source';
|
|
5
|
+
import { trimNode } from '../util';
|
|
5
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
6
7
|
import { push } from 'spica/array';
|
|
7
8
|
|
|
@@ -24,7 +25,7 @@ export const table: TableParser = lazy(() => block(fmap(validate(
|
|
|
24
25
|
])));
|
|
25
26
|
|
|
26
27
|
const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => creator(fallback(fmap(
|
|
27
|
-
line(surround(/^(?=\|)/, some(union([parser])),
|
|
28
|
+
line(surround(/^(?=\|)/, some(union([parser])), /^[|\\]?\s*$/, optional)),
|
|
28
29
|
es => [html('tr', es)]),
|
|
29
30
|
rewrite(contentline, source => [[
|
|
30
31
|
html('tr', {
|
|
@@ -46,17 +47,17 @@ const align: AlignParser = creator(fmap(open(
|
|
|
46
47
|
ns => [html('td', defrag(ns))]));
|
|
47
48
|
|
|
48
49
|
const cell: CellParser = surround(
|
|
49
|
-
|
|
50
|
-
some(union([inline]), /^
|
|
50
|
+
/^\|\s*(?=\S)/,
|
|
51
|
+
some(union([inline]), /^\|/, [[/^[|\\]?\s*$/, 9]]),
|
|
51
52
|
/^[^|]*/, true);
|
|
52
53
|
|
|
53
54
|
const head: CellParser.HeadParser = creator(fmap(
|
|
54
55
|
cell,
|
|
55
|
-
ns => [html('th', defrag(ns))]));
|
|
56
|
+
ns => [html('th', trimNode(defrag(ns)))]));
|
|
56
57
|
|
|
57
58
|
const data: CellParser.DataParser = creator(fmap(
|
|
58
59
|
cell,
|
|
59
|
-
ns => [html('td', defrag(ns))]));
|
|
60
|
+
ns => [html('td', trimNode(defrag(ns)))]));
|
|
60
61
|
|
|
61
62
|
function format(rows: HTMLTableRowElement[]): HTMLTableRowElement[] {
|
|
62
63
|
const aligns = rows[0].classList.contains('invalid')
|
|
@@ -14,14 +14,15 @@ describe('Unit: parser/inline/annotation', () => {
|
|
|
14
14
|
assert.deepStrictEqual(inspect(parser('(())')), undefined);
|
|
15
15
|
assert.deepStrictEqual(inspect(parser('(()))')), undefined);
|
|
16
16
|
assert.deepStrictEqual(inspect(parser('(( ))')), undefined);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser('(( (a')), [['', '(('], ' (a']);
|
|
17
18
|
assert.deepStrictEqual(inspect(parser('((\n))')), undefined);
|
|
18
19
|
assert.deepStrictEqual(inspect(parser('((\na))')), undefined);
|
|
19
20
|
assert.deepStrictEqual(inspect(parser('((\\\na))')), undefined);
|
|
20
|
-
assert.deepStrictEqual(inspect(parser('((a\n))')), [['(('], 'a\n))']);
|
|
21
|
-
assert.deepStrictEqual(inspect(parser('((a\\\n))')), [['(('], 'a\\\n))']);
|
|
22
|
-
assert.deepStrictEqual(inspect(parser('((a\nb))')), [['(('], 'a\nb))']);
|
|
23
|
-
assert.deepStrictEqual(inspect(parser('((a\\\nb))')), [['(('], 'a\\\nb))']);
|
|
24
|
-
assert.deepStrictEqual(inspect(parser('((*a\nb*))')), [['(('], '*a\nb*))']);
|
|
21
|
+
assert.deepStrictEqual(inspect(parser('((a\n))')), [['', '(('], 'a\n))']);
|
|
22
|
+
assert.deepStrictEqual(inspect(parser('((a\\\n))')), [['', '(('], 'a\\\n))']);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('((a\nb))')), [['', '(('], 'a\nb))']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('((a\\\nb))')), [['', '(('], 'a\\\nb))']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser('((*a\nb*))')), [['', '(('], '*a\nb*))']);
|
|
25
26
|
assert.deepStrictEqual(inspect(parser('((\\))')), undefined);
|
|
26
27
|
assert.deepStrictEqual(inspect(parser('((a)b))')), undefined);
|
|
27
28
|
assert.deepStrictEqual(inspect(parser('(((a))')), undefined);
|
|
@@ -2,13 +2,14 @@ import { undefined } from 'spica/global';
|
|
|
2
2
|
import { AnnotationParser } from '../inline';
|
|
3
3
|
import { union, some, validate, guard, context, precedence, creator, recursion, surround, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
|
-
import { optimize } from './
|
|
6
|
-
import {
|
|
5
|
+
import { optimize } from './link';
|
|
6
|
+
import { startLoose, trimNode } from '../util';
|
|
7
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
8
8
|
|
|
9
9
|
export const annotation: AnnotationParser = lazy(() => creator(recursion(precedence(6, validate('((', surround(
|
|
10
10
|
'((',
|
|
11
11
|
guard(context => context.syntax?.inline?.annotation ?? true,
|
|
12
|
+
startLoose(
|
|
12
13
|
context({ syntax: { inline: {
|
|
13
14
|
annotation: false,
|
|
14
15
|
// Redundant
|
|
@@ -20,8 +21,8 @@ export const annotation: AnnotationParser = lazy(() => creator(recursion(precede
|
|
|
20
21
|
//link: true,
|
|
21
22
|
//autolink: true,
|
|
22
23
|
}}, delimiters: undefined },
|
|
23
|
-
|
|
24
|
+
some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')),
|
|
24
25
|
'))',
|
|
25
26
|
false,
|
|
26
|
-
([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span',
|
|
27
|
+
([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest],
|
|
27
28
|
([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest)))))));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union, tails, verify, rewrite,
|
|
3
|
-
import {
|
|
2
|
+
import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
|
+
import { textlink } from '../link';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { define } from 'typed-dom/dom';
|
|
6
6
|
|
|
@@ -17,10 +17,6 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
|
|
|
17
17
|
str(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
|
|
18
18
|
([source]) => source.length <= 64),
|
|
19
19
|
])),
|
|
20
|
-
context({ syntax: { inline: {
|
|
21
|
-
link: true,
|
|
22
|
-
autolink: false,
|
|
23
|
-
}}},
|
|
24
20
|
convert(
|
|
25
21
|
source =>
|
|
26
22
|
`[${source}]{ ${
|
|
@@ -28,5 +24,5 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
|
|
|
28
24
|
? `https://${source.slice(1).replace('/', '/@')}`
|
|
29
25
|
: `/${source}`
|
|
30
26
|
} }`,
|
|
31
|
-
union([
|
|
27
|
+
union([textlink]))),
|
|
32
28
|
([el]) => [define(el, { class: 'account' })]));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union, validate, focus,
|
|
3
|
-
import {
|
|
2
|
+
import { union, validate, focus, convert, fmap, lazy } from '../../../combinator';
|
|
3
|
+
import { textlink } from '../link';
|
|
4
4
|
import { define } from 'typed-dom/dom';
|
|
5
5
|
|
|
6
6
|
// Timeline(pseudonym): user/tid
|
|
@@ -14,10 +14,6 @@ import { define } from 'typed-dom/dom';
|
|
|
14
14
|
|
|
15
15
|
export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fmap(focus(
|
|
16
16
|
/^>>(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*\/)?[0-9A-Za-z]+(?:-[0-9A-Za-z]+)*(?![0-9A-Za-z@#:])/,
|
|
17
|
-
context({ syntax: { inline: {
|
|
18
|
-
link: true,
|
|
19
|
-
autolink: false,
|
|
20
|
-
}}},
|
|
21
17
|
convert(
|
|
22
18
|
source =>
|
|
23
19
|
`[${source}]{ ${
|
|
@@ -25,5 +21,5 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fma
|
|
|
25
21
|
? `/@${source.slice(2).replace('/', '/timeline/')}`
|
|
26
22
|
: `?at=${source.slice(2)}`
|
|
27
23
|
} }`,
|
|
28
|
-
union([
|
|
24
|
+
union([textlink]))),
|
|
29
25
|
([el]) => [define(el, { class: 'anchor' })])));
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union, rewrite,
|
|
3
|
-
import {
|
|
2
|
+
import { union, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
|
+
import { textlink } from '../link';
|
|
4
4
|
import { emoji } from './hashtag';
|
|
5
5
|
import { str } from '../../source';
|
|
6
6
|
import { define } from 'typed-dom/dom';
|
|
7
7
|
|
|
8
8
|
export const hashnum: AutolinkParser.HashnumParser = lazy(() => fmap(rewrite(
|
|
9
9
|
open('#', str(new RegExp(/^[0-9]{1,16}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, emoji), 'u'))),
|
|
10
|
-
context({ syntax: { inline: {
|
|
11
|
-
link: true,
|
|
12
|
-
autolink: false,
|
|
13
|
-
}}},
|
|
14
10
|
convert(
|
|
15
11
|
source => `[${source}]{ ${source.slice(1)} }`,
|
|
16
|
-
union([
|
|
12
|
+
union([textlink]))),
|
|
17
13
|
([el]) => [define(el, { class: 'hashnum', href: null })]));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union, tails, verify, rewrite,
|
|
3
|
-
import {
|
|
2
|
+
import { union, tails, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
|
+
import { textlink } from '../link';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { define } from 'typed-dom/dom';
|
|
6
6
|
|
|
@@ -24,10 +24,6 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
|
|
|
24
24
|
].join('').replace(/emoji/g, emoji), 'u')),
|
|
25
25
|
([source]) => source.length <= 128),
|
|
26
26
|
])),
|
|
27
|
-
context({ syntax: { inline: {
|
|
28
|
-
link: true,
|
|
29
|
-
autolink: false,
|
|
30
|
-
}}},
|
|
31
27
|
convert(
|
|
32
28
|
source =>
|
|
33
29
|
`[${source}]{ ${
|
|
@@ -35,5 +31,5 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
|
|
|
35
31
|
? `https://${source.slice(1).replace('/', '/hashtags/')}`
|
|
36
32
|
: `/hashtags/${source.slice(1)}`
|
|
37
33
|
} }`,
|
|
38
|
-
union([
|
|
34
|
+
union([textlink]))),
|
|
39
35
|
([el]) => [define(el, { class: 'hashtag' }, el.innerText)]));
|
|
@@ -72,6 +72,7 @@ describe('Unit: parser/inline/autolink/url', () => {
|
|
|
72
72
|
assert.deepStrictEqual(inspect(parser('http://host>')), [['<a href="http://host" target="_blank">http://host</a>'], '>']);
|
|
73
73
|
assert.deepStrictEqual(inspect(parser('http://host(')), [['<a href="http://host" target="_blank">http://host</a>'], '(']);
|
|
74
74
|
assert.deepStrictEqual(inspect(parser('http://host)')), [['<a href="http://host" target="_blank">http://host</a>'], ')']);
|
|
75
|
+
assert.deepStrictEqual(inspect(parser('http://host\\"')), [['<a href="http://host" target="_blank">http://host</a>'], '\\"']);
|
|
75
76
|
assert.deepStrictEqual(inspect(parser('http://host!?**.*--++==~~^^')), [['<a href="http://host" target="_blank">http://host</a>'], '!?**.*--++==~~^^']);
|
|
76
77
|
});
|
|
77
78
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
2
|
import { union, some, validate, focus, rewrite, precedence, creator, convert, surround, open, lazy } from '../../../combinator';
|
|
3
|
-
import {
|
|
3
|
+
import { textlink } from '../link';
|
|
4
4
|
import { unescsource } from '../../source';
|
|
5
|
-
import { clean } from '../../util';
|
|
6
5
|
|
|
7
|
-
const closer = /^[-+*=~^,.;:!?]*(?=["`|\[\](){}<>]
|
|
6
|
+
const closer = /^[-+*=~^,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
|
|
8
7
|
|
|
9
8
|
export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'https://'], rewrite(
|
|
10
9
|
open(
|
|
@@ -12,9 +11,9 @@ export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'ht
|
|
|
12
11
|
focus(/^[\x21-\x7E]+/, some(union([bracket, some(unescsource, closer)])))),
|
|
13
12
|
convert(
|
|
14
13
|
url => `{ ${url} }`,
|
|
15
|
-
|
|
14
|
+
union([textlink])))));
|
|
16
15
|
|
|
17
|
-
const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creator(precedence(
|
|
16
|
+
const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creator(precedence(2, union([
|
|
18
17
|
surround('(', some(union([bracket, unescsource]), ')'), ')', true),
|
|
19
18
|
surround('[', some(union([bracket, unescsource]), ']'), ']', true),
|
|
20
19
|
surround('{', some(union([bracket, unescsource]), '}'), '}', true),
|
|
@@ -39,6 +39,7 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
39
39
|
assert.deepStrictEqual(inspect(parser('(ABBR, ABBR)')), [['(', 'ABBR, ABBR', ')'], '']);
|
|
40
40
|
assert.deepStrictEqual(inspect(parser('(\\a)')), [['<span class="paren">(a)</span>'], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser('(==)')), [['<span class="paren">(==)</span>'], '']);
|
|
42
|
+
assert.deepStrictEqual(inspect(parser('($)$')), [['', '(', '<span class="math" translate="no" data-src="$)$">$)$</span>'], '']);
|
|
42
43
|
assert.deepStrictEqual(inspect(parser(')')), undefined);
|
|
43
44
|
assert.deepStrictEqual(inspect(parser('(1,2)')), [['(', '1,2', ')'], '']);
|
|
44
45
|
assert.deepStrictEqual(inspect(parser('(0-1)')), [['(', '0-1', ')'], '']);
|
|
@@ -55,6 +56,7 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
55
56
|
assert.deepStrictEqual(inspect(parser('[a')), [['', '[', 'a'], '']);
|
|
56
57
|
assert.deepStrictEqual(inspect(parser('[a]')), [['[', 'a', ']'], '']);
|
|
57
58
|
assert.deepStrictEqual(inspect(parser('[==]')), [['[', '==', ']'], '']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('[$]$')), [['', '[', '<span class="math" translate="no" data-src="$]$">$]$</span>'], '']);
|
|
58
60
|
assert.deepStrictEqual(inspect(parser(']')), undefined);
|
|
59
61
|
});
|
|
60
62
|
|
|
@@ -73,7 +75,7 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
73
75
|
assert.deepStrictEqual(inspect(parser('"a')), [['"', 'a'], '']);
|
|
74
76
|
assert.deepStrictEqual(inspect(parser('"a"')), [['"', 'a', '"'], '']);
|
|
75
77
|
assert.deepStrictEqual(inspect(parser('"(")"')), [['"', '', '(', '"'], ')"']);
|
|
76
|
-
assert.deepStrictEqual(inspect(parser('"(("')), [['"', '((', '"'], '']);
|
|
78
|
+
assert.deepStrictEqual(inspect(parser('"(("')), [['"', '', '((', '"'], '']);
|
|
77
79
|
assert.deepStrictEqual(inspect(parser('"(\\")"')), [['"', '<span class="paren">(")</span>', '"'], '']);
|
|
78
80
|
assert.deepStrictEqual(inspect(parser('"(\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
|
|
79
81
|
assert.deepStrictEqual(inspect(parser('"(\\\n)"')), [['"', '<span class="paren">(<span class="linebreak"> </span>)</span>', '"'], '']);
|
|
@@ -9,18 +9,18 @@ import { unshift, push } from 'spica/array';
|
|
|
9
9
|
const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
|
|
10
10
|
|
|
11
11
|
export const bracket: BracketParser = lazy(() => creator(0, union([
|
|
12
|
-
surround(str('('), precedence(
|
|
13
|
-
surround(str('('), precedence(
|
|
12
|
+
surround(str('('), precedence(2, str(index)), str(')')),
|
|
13
|
+
surround(str('('), precedence(2, some(inline, ')', [[')', 2]])), str(')'), true,
|
|
14
14
|
([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
|
|
15
15
|
([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
|
|
16
|
-
surround(str('('), precedence(
|
|
17
|
-
surround(str('('), precedence(
|
|
16
|
+
surround(str('('), precedence(2, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0))))), str(')')),
|
|
17
|
+
surround(str('('), precedence(2, some(inline, ')', [[')', 2]])), str(')'), true,
|
|
18
18
|
([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
|
|
19
19
|
([as, bs = []], rest) => [unshift(as, bs), rest]),
|
|
20
|
-
surround(str('['), precedence(
|
|
20
|
+
surround(str('['), precedence(2, some(inline, ']', [[']', 2]])), str(']'), true,
|
|
21
21
|
undefined,
|
|
22
22
|
([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
|
|
23
|
-
surround(str('{'), precedence(
|
|
23
|
+
surround(str('{'), precedence(2, some(inline, '}', [['}', 2]])), str('}'), true,
|
|
24
24
|
undefined,
|
|
25
25
|
([as, bs = []], rest) => [unshift(as, bs), rest]),
|
|
26
26
|
// Control media blinking in editing rather than control confusion of pairs of quote marks.
|
|
@@ -57,6 +57,7 @@ describe('Unit: parser/inline/comment', () => {
|
|
|
57
57
|
assert.deepStrictEqual(inspect(parser('[% &copy; %]')), [['<span class="comment"><input type="checkbox"><span>[% &copy; %]</span></span>'], '']);
|
|
58
58
|
assert.deepStrictEqual(inspect(parser('[% [ %]')), [['<span class="comment"><input type="checkbox"><span>[% [ %]</span></span>'], '']);
|
|
59
59
|
assert.deepStrictEqual(inspect(parser('[% \\ a %]')), [['<span class="comment"><input type="checkbox"><span>[% a %]</span></span>'], '']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser('[% $-a %]$')), [['<span class="comment"><input type="checkbox"><span>[% <a class="label" data-label="$-a">$-a</a> %]</span></span>'], '$']);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
});
|
|
@@ -6,7 +6,7 @@ import { blankWith } from '../util';
|
|
|
6
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
7
|
import { unshift } from 'spica/array';
|
|
8
8
|
|
|
9
|
-
export const deletion: DeletionParser = lazy(() => creator(precedence(
|
|
9
|
+
export const deletion: DeletionParser = lazy(() => creator(precedence(1, surround(
|
|
10
10
|
str('~~'),
|
|
11
11
|
some(union([
|
|
12
12
|
some(inline, blankWith('\n', '~~')),
|
|
@@ -9,7 +9,7 @@ import { html, define, defrag } from 'typed-dom/dom';
|
|
|
9
9
|
|
|
10
10
|
import IndexParser = ExtensionParser.IndexParser;
|
|
11
11
|
|
|
12
|
-
export const index: IndexParser = lazy(() => validate('[#', creator(precedence(
|
|
12
|
+
export const index: IndexParser = lazy(() => validate('[#', creator(precedence(2, fmap(indexee(surround(
|
|
13
13
|
'[#',
|
|
14
14
|
guard(context => context.syntax?.inline?.index ?? true,
|
|
15
15
|
startTight(
|
|
@@ -25,7 +25,7 @@ export const index: IndexParser = lazy(() => validate('[#', creator(precedence(3
|
|
|
25
25
|
open(stropt(/^\|?/), trimBlankEnd(some(union([
|
|
26
26
|
signature,
|
|
27
27
|
inline,
|
|
28
|
-
]), ']', [[/^\\?\n/, 9], [']',
|
|
28
|
+
]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))),
|
|
29
29
|
']',
|
|
30
30
|
false,
|
|
31
31
|
([, ns], rest) => [[html('a', defrag(ns))], rest])),
|
|
@@ -10,9 +10,9 @@ import { unshift } from 'spica/array';
|
|
|
10
10
|
|
|
11
11
|
// All syntax surrounded by square brackets shouldn't contain line breaks.
|
|
12
12
|
|
|
13
|
-
export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate(['[:', '[^'], creator(precedence(
|
|
13
|
+
export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate(['[:', '[^'], creator(precedence(2, surround(
|
|
14
14
|
str(/^\[[:^]/),
|
|
15
|
-
startTight(some(union([inline]), ']', [[/^\\?\n/, 9], [']',
|
|
15
|
+
startTight(some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]])),
|
|
16
16
|
str(']'), false,
|
|
17
17
|
([as, bs], rest) => [[
|
|
18
18
|
html('span', {
|
|
@@ -6,7 +6,7 @@ import { blankWith } from '../util';
|
|
|
6
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
7
|
import { unshift } from 'spica/array';
|
|
8
8
|
|
|
9
|
-
export const insertion: InsertionParser = lazy(() => creator(precedence(
|
|
9
|
+
export const insertion: InsertionParser = lazy(() => creator(precedence(1, surround(
|
|
10
10
|
str('++'),
|
|
11
11
|
some(union([
|
|
12
12
|
some(inline, blankWith('\n', '++')),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { undefined, location, encodeURI, decodeURI, Location } from 'spica/global';
|
|
2
|
-
import { LinkParser } from '../inline';
|
|
3
|
-
import { eval } from '../../combinator/data/parser';
|
|
4
|
-
import { union, inits, tails, some, validate, guard, context, precedence, creator, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
|
|
2
|
+
import { LinkParser, TextLinkParser } from '../inline';
|
|
3
|
+
import { Result, eval } from '../../combinator/data/parser';
|
|
4
|
+
import { union, inits, tails, subsequence, some, validate, guard, context, precedence, creator, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
|
|
5
5
|
import { inline, media, shortmedia } from '../inline';
|
|
6
6
|
import { attributes } from './html';
|
|
7
7
|
import { autolink } from '../autolink';
|
|
8
|
-
import { str } from '../source';
|
|
9
|
-
import {
|
|
8
|
+
import { unescsource, str } from '../source';
|
|
9
|
+
import { trimNode, stringify } from '../util';
|
|
10
10
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
11
11
|
import { ReadonlyURL } from 'spica/url';
|
|
12
12
|
|
|
@@ -15,9 +15,9 @@ const optspec = {
|
|
|
15
15
|
} as const;
|
|
16
16
|
Object.setPrototypeOf(optspec, null);
|
|
17
17
|
|
|
18
|
-
export const link: LinkParser = lazy(() => validate(['[', '{'], creator(10, precedence(
|
|
18
|
+
export const link: LinkParser = lazy(() => validate(['[', '{'], creator(10, precedence(2, bind(
|
|
19
19
|
guard(context => context.syntax?.inline?.link ?? true,
|
|
20
|
-
|
|
20
|
+
fmap(subsequence([
|
|
21
21
|
context({ syntax: { inline: {
|
|
22
22
|
link: false,
|
|
23
23
|
}}},
|
|
@@ -36,15 +36,22 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], creator(10, prec
|
|
|
36
36
|
media: false,
|
|
37
37
|
autolink: false,
|
|
38
38
|
}}},
|
|
39
|
-
|
|
39
|
+
some(inline, ']', [[/^\\?\n/, 9], [']', 2]])),
|
|
40
40
|
']',
|
|
41
|
-
true
|
|
41
|
+
true,
|
|
42
|
+
undefined,
|
|
43
|
+
([, ns = [], rest], next) => next[0] === ']' ? undefined : optimize('[', ns, rest)),
|
|
42
44
|
]))),
|
|
45
|
+
// 全体の失敗が確定した時も解析し予算を浪費している
|
|
43
46
|
dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
|
|
44
|
-
])
|
|
45
|
-
([
|
|
47
|
+
]),
|
|
48
|
+
([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []])),
|
|
49
|
+
([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
|
|
50
|
+
if (params.length === 0) return;
|
|
51
|
+
assert(content[0] !== '' || params.length === 0);
|
|
52
|
+
if (content[0] === '') return [content, rest];
|
|
46
53
|
assert(params.every(p => typeof p === 'string'));
|
|
47
|
-
content
|
|
54
|
+
if (content.length !== 0 && trimNode(content).length === 0) return;
|
|
48
55
|
if (eval(some(autolink)(stringify(content), context))?.some(node => typeof node === 'object')) return;
|
|
49
56
|
assert(!html('div', content).querySelector('a, .media, .annotation, .reference') || (content[0] as HTMLElement).matches('.media'));
|
|
50
57
|
const INSECURE_URI = params.shift()!;
|
|
@@ -62,10 +69,35 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], creator(10, prec
|
|
|
62
69
|
return [[define(el, attributes('link', [], optspec, params))], rest];
|
|
63
70
|
})))));
|
|
64
71
|
|
|
65
|
-
export const
|
|
72
|
+
export const textlink: TextLinkParser = lazy(() => validate(['[', '{'], creator(10, precedence(2, bind(
|
|
73
|
+
reverse(tails([
|
|
74
|
+
dup(surround('[', some(union([unescsource]), ']'), ']')),
|
|
75
|
+
dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
|
|
76
|
+
])),
|
|
77
|
+
([params, content = []], rest, context) => {
|
|
78
|
+
assert(params[0] === '\r');
|
|
79
|
+
params.shift();
|
|
80
|
+
assert(params.every(p => typeof p === 'string'));
|
|
81
|
+
trimNode(content);
|
|
82
|
+
const INSECURE_URI = params.shift()!;
|
|
83
|
+
assert(INSECURE_URI === INSECURE_URI.trim());
|
|
84
|
+
assert(!INSECURE_URI.match(/\s/));
|
|
85
|
+
const el = elem(
|
|
86
|
+
INSECURE_URI,
|
|
87
|
+
defrag(content),
|
|
88
|
+
new ReadonlyURL(
|
|
89
|
+
resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
|
|
90
|
+
context.host?.href || location.href),
|
|
91
|
+
context.host?.origin || location.origin);
|
|
92
|
+
assert(!el.classList.contains('invalid'));
|
|
93
|
+
assert(el.classList.length === 0);
|
|
94
|
+
return [[define(el, attributes('link', [], optspec, params))], rest];
|
|
95
|
+
})))));
|
|
96
|
+
|
|
97
|
+
export const uri: LinkParser.ParameterParser.UriParser = fmap(union([
|
|
66
98
|
open(/^[^\S\n]+/, str(/^\S+/)),
|
|
67
99
|
str(/^[^\s{}]+/),
|
|
68
|
-
]);
|
|
100
|
+
]), ([uri]) => ['\r', uri]);
|
|
69
101
|
|
|
70
102
|
export const option: LinkParser.ParameterParser.OptionParser = union([
|
|
71
103
|
fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
|
|
@@ -163,3 +195,12 @@ function decode(uri: string): string {
|
|
|
163
195
|
return uri.replace(/\s+/g, encodeURI);
|
|
164
196
|
}
|
|
165
197
|
}
|
|
198
|
+
|
|
199
|
+
export function optimize(opener: string, ns: readonly (string | HTMLElement)[], rest: string): Result<string> {
|
|
200
|
+
let count = 0;
|
|
201
|
+
for (let i = 0; i < ns.length - 1; i += 2) {
|
|
202
|
+
if (ns[i] !== '' || ns[i + 1] !== opener[0]) break;
|
|
203
|
+
++count;
|
|
204
|
+
}
|
|
205
|
+
return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
|
|
206
|
+
}
|
|
@@ -6,7 +6,7 @@ import { startTight, blankWith } from '../util';
|
|
|
6
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
7
|
import { unshift } from 'spica/array';
|
|
8
8
|
|
|
9
|
-
export const mark: MarkParser = lazy(() => creator(precedence(
|
|
9
|
+
export const mark: MarkParser = lazy(() => creator(precedence(1, surround(
|
|
10
10
|
str('=='),
|
|
11
11
|
startTight(some(union([
|
|
12
12
|
some(inline, blankWith('==')),
|