securemark 0.266.0 → 0.268.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 +8 -0
- package/README.md +1 -1
- package/dist/index.js +75 -79
- package/index.d.ts +7 -8
- package/markdown.d.ts +26 -9
- package/package.json +7 -7
- package/src/combinator/control/constraint/block.ts +2 -2
- package/src/combinator/control/constraint/line.ts +2 -2
- package/src/combinator/control/manipulation/fence.ts +4 -4
- package/src/parser/api/bind.ts +1 -1
- package/src/parser/api/parse.test.ts +0 -2
- package/src/parser/api/parse.ts +1 -1
- package/src/parser/autolink.ts +15 -22
- package/src/parser/block/blockquote.ts +1 -1
- package/src/parser/block/codeblock.ts +2 -2
- package/src/parser/block/olist.test.ts +2 -2
- package/src/parser/block/olist.ts +2 -2
- package/src/parser/block/pagebreak.test.ts +31 -0
- package/src/parser/block/pagebreak.ts +7 -0
- package/src/parser/block/paragraph.test.ts +3 -0
- package/src/parser/block/reply/cite.ts +1 -2
- package/src/parser/block/reply/quote.test.ts +3 -0
- package/src/parser/block/reply/quote.ts +15 -9
- package/src/parser/block/sidefence.ts +1 -1
- package/src/parser/block/ulist.test.ts +2 -2
- package/src/parser/block/ulist.ts +2 -2
- package/src/parser/block.ts +3 -3
- package/src/parser/inline/autolink/account.ts +3 -5
- package/src/parser/inline/autolink/anchor.test.ts +2 -2
- package/src/parser/inline/autolink/anchor.ts +4 -3
- package/src/parser/inline/autolink/channel.test.ts +0 -2
- package/src/parser/inline/autolink/channel.ts +0 -1
- package/src/parser/inline/autolink/hashnum.test.ts +1 -1
- package/src/parser/inline/autolink/hashtag.test.ts +2 -6
- package/src/parser/inline/autolink/hashtag.ts +7 -20
- package/src/parser/inline/extension/index.ts +1 -1
- package/src/parser/inline/extension/indexee.ts +1 -0
- package/src/parser/inline/link.ts +1 -0
- package/src/parser/inline.test.ts +5 -2
- package/src/parser/source/line.ts +3 -3
- package/src/parser/source/str.ts +1 -1
- package/src/parser/util.ts +10 -0
- package/src/parser/visibility.ts +2 -1
- package/src/util/quote.ts +9 -2
- package/src/parser/block/horizontalrule.test.ts +0 -31
- package/src/parser/block/horizontalrule.ts +0 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, Ctx } from '../../data/parser';
|
|
2
|
-
import { firstline,
|
|
2
|
+
import { firstline, isBlank } from '../constraint/line';
|
|
3
3
|
import { unshift } from 'spica/array';
|
|
4
4
|
|
|
5
5
|
export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
|
|
@@ -13,20 +13,20 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
|
|
|
13
13
|
if (matches[0].indexOf(delim, delim.length) !== -1) return;
|
|
14
14
|
let rest = source.slice(matches[0].length);
|
|
15
15
|
// Prevent annoying parsing in editing.
|
|
16
|
-
if (
|
|
16
|
+
if (isBlank(firstline(rest)) && firstline(rest.slice(firstline(rest).length)).trimEnd() !== delim) return;
|
|
17
17
|
let block = '';
|
|
18
18
|
let closer = '';
|
|
19
19
|
let overflow = '';
|
|
20
20
|
for (let count = 1; ; ++count) {
|
|
21
21
|
if (rest === '') break;
|
|
22
22
|
const line = firstline(rest);
|
|
23
|
-
if ((closer || count > limit + 1) &&
|
|
23
|
+
if ((closer || count > limit + 1) && isBlank(line)) break;
|
|
24
24
|
if(closer) {
|
|
25
25
|
overflow += line;
|
|
26
26
|
}
|
|
27
27
|
if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
|
|
28
28
|
closer = line;
|
|
29
|
-
if (
|
|
29
|
+
if (isBlank(firstline(rest.slice(line.length)))) {
|
|
30
30
|
rest = rest.slice(line.length);
|
|
31
31
|
break;
|
|
32
32
|
}
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
26
26
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
27
27
|
memo: new Memo({ targets: State.backtrackers }),
|
|
28
28
|
};
|
|
29
|
-
if (context.id?.
|
|
29
|
+
if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
|
|
30
30
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
31
31
|
assert(!settings.id);
|
|
32
32
|
type Block = readonly [segment: string, blocks: readonly HTMLElement[], url: string];
|
|
@@ -99,7 +99,6 @@ describe('Unit: parser/api/parse', () => {
|
|
|
99
99
|
'@a#b',
|
|
100
100
|
'@domain/a#b',
|
|
101
101
|
'#a',
|
|
102
|
-
'#domain/a',
|
|
103
102
|
'[#a]',
|
|
104
103
|
'$-a\n$$\n$$',
|
|
105
104
|
'$-a',
|
|
@@ -122,7 +121,6 @@ describe('Unit: parser/api/parse', () => {
|
|
|
122
121
|
'<p><a class="channel" href="https://source/@a?ch=b" target="_blank">@a#b</a></p>',
|
|
123
122
|
'<p><a class="channel" href="https://domain/@a?ch=b" target="_blank">@domain/a#b</a></p>',
|
|
124
123
|
'<p><a class="hashtag" href="https://source/hashtags/a" target="_blank">#a</a></p>',
|
|
125
|
-
'<p><a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a></p>',
|
|
126
124
|
'<p><a class="index" href="#index::a">a</a></p>',
|
|
127
125
|
'<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>',
|
|
128
126
|
'<p><a class="label" data-label="$-a" href="#label:$-a">(1)</a></p>',
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -19,7 +19,6 @@ interface Options extends ParserOptions {
|
|
|
19
19
|
|
|
20
20
|
export function parse(source: string, opts: Options = {}, context?: MarkdownParser.Context): DocumentFragment {
|
|
21
21
|
if (!validate(source, MAX_SEGMENT_SIZE)) throw new Error(`Too large input over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes`);
|
|
22
|
-
if (context?.id?.includes(':')) throw new Error('ID must not contain ":"');
|
|
23
22
|
const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
24
23
|
source = !context ? normalize(source) : source;
|
|
25
24
|
assert(!context?.delimiters);
|
|
@@ -33,6 +32,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
33
32
|
},
|
|
34
33
|
memo: new Memo({ targets: State.backtrackers }),
|
|
35
34
|
};
|
|
35
|
+
if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
|
|
36
36
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
37
37
|
const node = frag();
|
|
38
38
|
let index = 0;
|
package/src/parser/autolink.ts
CHANGED
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../markdown';
|
|
2
|
-
import { union, lazy } from '../combinator';
|
|
2
|
+
import { union, tails, subsequence, some, line, focus, lazy } from '../combinator';
|
|
3
|
+
import { link } from './inline/link';
|
|
3
4
|
import { autolink as autolink_ } from './inline/autolink';
|
|
4
|
-
import { linebreak, unescsource } from './source';
|
|
5
|
+
import { linebreak, unescsource, str } from './source';
|
|
6
|
+
import { format } from './util';
|
|
5
7
|
|
|
6
8
|
export import AutolinkParser = MarkdownParser.AutolinkParser;
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
export const autolink: AutolinkParser = lazy(() => some(line(subsequence([
|
|
11
|
+
lineurl,
|
|
12
|
+
some(union([
|
|
13
|
+
autolink_,
|
|
14
|
+
linebreak,
|
|
15
|
+
unescsource,
|
|
16
|
+
])),
|
|
17
|
+
]))));
|
|
9
18
|
|
|
10
|
-
export const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const i = source.search(delimiter);
|
|
14
|
-
switch (i) {
|
|
15
|
-
case -1:
|
|
16
|
-
return [[source], ''];
|
|
17
|
-
case 0:
|
|
18
|
-
return parser({ source, context });
|
|
19
|
-
default:
|
|
20
|
-
return [[source.slice(0, i)], source.slice(i)];
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const parser: AutolinkParser = lazy(() => union([
|
|
25
|
-
autolink_,
|
|
26
|
-
linebreak,
|
|
27
|
-
unescsource
|
|
28
|
-
]));
|
|
19
|
+
export const lineurl: AutolinkParser.LineUrlParser = lazy(() => focus(
|
|
20
|
+
/^!?https?:\/\/\S+(?=[^\S\n]*(?:$|\n))/,
|
|
21
|
+
format(tails([str('!'), link]))));
|
|
@@ -25,7 +25,7 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
|
|
|
25
25
|
convert(unindent, source)),
|
|
26
26
|
rewrite(
|
|
27
27
|
some(contentline, opener),
|
|
28
|
-
convert(unindent, fmap(
|
|
28
|
+
convert(unindent, fmap(autolink, ns => [html('pre', defrag(ns))]))),
|
|
29
29
|
]))),
|
|
30
30
|
ns => [html('blockquote', ns)]));
|
|
31
31
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CodeBlockParser } from '../block';
|
|
2
2
|
import { eval } from '../../combinator/data/parser';
|
|
3
|
-
import {
|
|
3
|
+
import { block, validate, fence, clear, fmap } from '../../combinator';
|
|
4
4
|
import { autolink } from '../autolink';
|
|
5
5
|
import { html, defrag } from 'typed-dom/dom';
|
|
6
6
|
|
|
@@ -69,6 +69,6 @@ export const codeblock: CodeBlockParser = block(validate('```', fmap(
|
|
|
69
69
|
params.lang
|
|
70
70
|
? context.caches?.code?.get(`${params.lang ?? ''}\n${body.slice(0, -1)}`)?.cloneNode(true).childNodes ||
|
|
71
71
|
body.slice(0, -1) || undefined
|
|
72
|
-
: defrag(eval(
|
|
72
|
+
: defrag(eval(autolink({ source: body.slice(0, -1), context }), [])));
|
|
73
73
|
return [el];
|
|
74
74
|
})));
|
|
@@ -37,8 +37,8 @@ describe('Unit: parser/block/olist', () => {
|
|
|
37
37
|
// pending
|
|
38
38
|
assert.deepStrictEqual(inspect(parser('1. ')), [['<ol><li></li></ol>'], '']);
|
|
39
39
|
// filled
|
|
40
|
-
assert.deepStrictEqual(inspect(parser('1. \\')), [['<ol><li
|
|
41
|
-
assert.deepStrictEqual(inspect(parser('1. \\\n')), [['<ol><li
|
|
40
|
+
assert.deepStrictEqual(inspect(parser('1. \\')), [['<ol><li id="index::\\">\\</li></ol>'], '']);
|
|
41
|
+
assert.deepStrictEqual(inspect(parser('1. \\\n')), [['<ol><li id="index::\\">\\</li></ol>'], '']);
|
|
42
42
|
assert.deepStrictEqual(inspect(parser('1. -')), [['<ol><li id="index::-">-</li></ol>'], '']);
|
|
43
43
|
assert.deepStrictEqual(inspect(parser('1. -\n')), [['<ol><li id="index::-">-</li></ol>'], '']);
|
|
44
44
|
// pending
|
|
@@ -6,7 +6,7 @@ import { ilist_ } from './ilist';
|
|
|
6
6
|
import { inline, indexee, indexer } from '../inline';
|
|
7
7
|
import { contentline } from '../source';
|
|
8
8
|
import { State } from '../context';
|
|
9
|
-
import { trimBlank } from '../visibility';
|
|
9
|
+
import { visualize, trimBlank } from '../visibility';
|
|
10
10
|
import { memoize } from 'spica/memoize';
|
|
11
11
|
import { shift } from 'spica/array';
|
|
12
12
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
@@ -37,7 +37,7 @@ const list = (type: string, form: string): OListParser.ListParser => fmap(
|
|
|
37
37
|
some(creation(1, false, union([
|
|
38
38
|
indexee(fmap(fallback(
|
|
39
39
|
inits([
|
|
40
|
-
line(open(heads[form], subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
|
|
40
|
+
line(open(heads[form], subsequence([checkbox, trimBlank(visualize(some(union([indexer, inline]))))]), true)),
|
|
41
41
|
indent(union([ulist_, olist_, ilist_])),
|
|
42
42
|
]),
|
|
43
43
|
invalid),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { pagebreak } from './pagebreak';
|
|
2
|
+
import { some } from '../../combinator';
|
|
3
|
+
import { inspect } from '../../debug.test';
|
|
4
|
+
|
|
5
|
+
describe('Unit: parser/block/pagebreak', () => {
|
|
6
|
+
describe('pagebreak', () => {
|
|
7
|
+
const parser = (source: string) => some(pagebreak)({ source, context: {} });
|
|
8
|
+
|
|
9
|
+
it('invalid', () => {
|
|
10
|
+
assert.deepStrictEqual(inspect(parser('')), undefined);
|
|
11
|
+
assert.deepStrictEqual(inspect(parser('\n')), undefined);
|
|
12
|
+
assert.deepStrictEqual(inspect(parser('=')), undefined);
|
|
13
|
+
assert.deepStrictEqual(inspect(parser('==')), undefined);
|
|
14
|
+
assert.deepStrictEqual(inspect(parser('==\n=')), undefined);
|
|
15
|
+
assert.deepStrictEqual(inspect(parser('===a')), undefined);
|
|
16
|
+
assert.deepStrictEqual(inspect(parser('===\na')), undefined);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser('= = =')), undefined);
|
|
18
|
+
assert.deepStrictEqual(inspect(parser(' ===')), undefined);
|
|
19
|
+
assert.deepStrictEqual(inspect(parser('---')), undefined);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('valid', () => {
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('===')), [['<hr>'], '']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('=== ')), [['<hr>'], '']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser('===\n')), [['<hr>'], '']);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser('====')), [['<hr>'], '']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
});
|
|
@@ -30,6 +30,9 @@ describe('Unit: parser/block/paragraph', () => {
|
|
|
30
30
|
assert.deepStrictEqual(inspect(parser('_a\n<wbr>_\nb')), [['<p>_a<br><wbr>_<br>b</p>'], '']);
|
|
31
31
|
assert.deepStrictEqual(inspect(parser('*a\n<wbr>*\nb')), [['<p>*a<br><wbr>*<br>b</p>'], '']);
|
|
32
32
|
assert.deepStrictEqual(inspect(parser('==a\n<wbr>==\nb')), [['<p>==a<br><wbr>==<br>b</p>'], '']);
|
|
33
|
+
assert.deepStrictEqual(inspect(parser('http://host#!')), [['<p><a class="url" href="http://host#!" target="_blank">http://host#!</a></p>'], '']);
|
|
34
|
+
assert.deepStrictEqual(inspect(parser('a\nhttp://host#\\ \nb')), [['<p>a<br><a class="url" href="http://host#\\" target="_blank">http://host#\\</a><br>b</p>'], '']);
|
|
35
|
+
assert.deepStrictEqual(inspect(parser('!http://host#!')), [['<p><a href="http://host#!" target="_blank"><img class="media" data-src="http://host#!" alt=""></a></p>'], '']);
|
|
33
36
|
assert.deepStrictEqual(inspect(parser('\ta')), [['<p>\ta</p>'], '']);
|
|
34
37
|
});
|
|
35
38
|
|
|
@@ -14,8 +14,7 @@ export const cite: ReplyParser.CiteParser = creation(1, false, line(fmap(validat
|
|
|
14
14
|
// リンクの実装は後で検討
|
|
15
15
|
focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
|
|
16
16
|
focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
|
|
17
|
-
|
|
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)], '']),
|
|
17
|
+
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)], '']),
|
|
19
18
|
]),
|
|
20
19
|
]))),
|
|
21
20
|
([el, quotes = '']: [HTMLElement, string?]) => [
|
|
@@ -55,6 +55,9 @@ describe('Unit: parser/block/reply/quote', () => {
|
|
|
55
55
|
assert.deepStrictEqual(inspect(parser('> $-a, $-b')), [['<span class="quote">> $-a, $-b</span>', '<br>'], '']);
|
|
56
56
|
assert.deepStrictEqual(inspect(parser('> $a=b$')), [['<span class="quote">> <span class="math" translate="no" data-src="$a=b$">$a=b$</span></span>', '<br>'], '']);
|
|
57
57
|
assert.deepStrictEqual(inspect(parser('> ${a}$')), [['<span class="quote">> <span class="math" translate="no" data-src="${a}$">${a}$</span></span>', '<br>'], '']);
|
|
58
|
+
assert.deepStrictEqual(inspect(parser('> http://host#!')), [['<span class="quote">> <a class="url" href="http://host#!" target="_blank">http://host#!</a></span>', '<br>'], '']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('> a\n> http://host#\\ \n> b')), [['<span class="quote">> a<br>> <a class="url" href="http://host#\\" target="_blank">http://host#\\</a> <br>> b</span>', '<br>'], '']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser('> !http://host#!')), [['<span class="quote">> !<a class="url" href="http://host#!" target="_blank">http://host#!</a></span>', '<br>'], '']);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
63
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
2
|
import { eval } from '../../../combinator/data/parser';
|
|
3
|
-
import { union, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
|
|
3
|
+
import { union, subsequence, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
|
|
4
4
|
import { math } from '../../inline/math';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { autolink } from '../../inline/autolink';
|
|
6
|
+
import { linebreak, unescsource, str, anyline } from '../../source';
|
|
7
|
+
import { lineurl } from '../../autolink';
|
|
7
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
8
9
|
|
|
9
10
|
export const syntax = /^>+(?=[^\S\n])|^>(?=[^\s>])|^>+(?=[^\s>])(?![0-9a-z]+(?:-[0-9a-z]+)*(?![0-9A-Za-z@#:]))/;
|
|
@@ -40,8 +41,8 @@ const qblock: ReplyParser.QuoteParser.BlockParser = ({ source, context }) => {
|
|
|
40
41
|
const quotes = source.match(/^>+[^\S\n]/mg)!;
|
|
41
42
|
assert(quotes);
|
|
42
43
|
assert(quotes.length > 0);
|
|
43
|
-
const content = lines.reduce((acc, line,
|
|
44
|
-
const nodes = eval(
|
|
44
|
+
const content = lines.reduce((acc, line, i) => acc + line.slice(quotes[i].length), '');
|
|
45
|
+
const nodes = eval(text({ source: content, context }), []);
|
|
45
46
|
nodes.unshift(quotes.shift()!);
|
|
46
47
|
for (let i = 0; i < nodes.length; ++i) {
|
|
47
48
|
const child = nodes[i] as string | Text | Element;
|
|
@@ -71,7 +72,12 @@ const qblock: ReplyParser.QuoteParser.BlockParser = ({ source, context }) => {
|
|
|
71
72
|
return [nodes, ''];
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
const text: ReplyParser.QuoteParser.TextParser =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const text: ReplyParser.QuoteParser.TextParser = some(line(subsequence([
|
|
76
|
+
lineurl,
|
|
77
|
+
some(union([
|
|
78
|
+
math, // quote補助関数が残した数式をパースする。他の構文で数式を残す場合はソーステキストを直接使用する。
|
|
79
|
+
autolink,
|
|
80
|
+
linebreak,
|
|
81
|
+
unescsource,
|
|
82
|
+
])),
|
|
83
|
+
])));
|
|
@@ -26,6 +26,6 @@ const source: SidefenceParser.SourceParser = lazy(() => fmap(
|
|
|
26
26
|
convert(unindent, source)),
|
|
27
27
|
rewrite(
|
|
28
28
|
some(contentline, opener),
|
|
29
|
-
convert(unindent, fmap(
|
|
29
|
+
convert(unindent, fmap(autolink, ns => [html('pre', defrag(ns))]))),
|
|
30
30
|
]))),
|
|
31
31
|
ns => [html('blockquote', ns)]));
|
|
@@ -26,8 +26,8 @@ describe('Unit: parser/block/ulist', () => {
|
|
|
26
26
|
// pending
|
|
27
27
|
assert.deepStrictEqual(inspect(parser('- ')), [['<ul><li></li></ul>'], '']);
|
|
28
28
|
// filled
|
|
29
|
-
assert.deepStrictEqual(inspect(parser('- \\')), [['<ul><li
|
|
30
|
-
assert.deepStrictEqual(inspect(parser('- \\\n')), [['<ul><li
|
|
29
|
+
assert.deepStrictEqual(inspect(parser('- \\')), [['<ul><li id="index::\\">\\</li></ul>'], '']);
|
|
30
|
+
assert.deepStrictEqual(inspect(parser('- \\\n')), [['<ul><li id="index::\\">\\</li></ul>'], '']);
|
|
31
31
|
assert.deepStrictEqual(inspect(parser('- -')), [['<ul><li id="index::-">-</li></ul>'], '']);
|
|
32
32
|
assert.deepStrictEqual(inspect(parser('- -\n')), [['<ul><li id="index::-">-</li></ul>'], '']);
|
|
33
33
|
});
|
|
@@ -4,7 +4,7 @@ import { olist_, invalid } from './olist';
|
|
|
4
4
|
import { ilist_ } from './ilist';
|
|
5
5
|
import { inline, indexer, indexee } from '../inline';
|
|
6
6
|
import { State } from '../context';
|
|
7
|
-
import { trimBlank } from '../visibility';
|
|
7
|
+
import { visualize, trimBlank } from '../visibility';
|
|
8
8
|
import { unshift } from 'spica/array';
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ export const ulist_: UListParser = lazy(() => block(fmap(validate(
|
|
|
18
18
|
some(creation(1, false, union([
|
|
19
19
|
indexee(fmap(fallback(
|
|
20
20
|
inits([
|
|
21
|
-
line(open(/^-(?:$|\s)/, subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
|
|
21
|
+
line(open(/^-(?:$|\s)/, subsequence([checkbox, trimBlank(visualize(some(union([indexer, inline]))))]), true)),
|
|
22
22
|
indent(union([ulist_, olist_, ilist_])),
|
|
23
23
|
]),
|
|
24
24
|
invalid),
|
package/src/parser/block.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../markdown';
|
|
2
2
|
import { union, reset, creation, open, fallback, recover } from '../combinator';
|
|
3
3
|
import { emptyline } from './source';
|
|
4
|
-
import {
|
|
4
|
+
import { pagebreak } from './block/pagebreak';
|
|
5
5
|
import { heading } from './block/heading';
|
|
6
6
|
import { ulist } from './block/ulist';
|
|
7
7
|
import { olist } from './block/olist';
|
|
@@ -19,7 +19,7 @@ import { rnd0Z } from 'spica/random';
|
|
|
19
19
|
import { html } from 'typed-dom/dom';
|
|
20
20
|
|
|
21
21
|
export import BlockParser = MarkdownParser.BlockParser;
|
|
22
|
-
export import
|
|
22
|
+
export import PagebreakParser = BlockParser.PagebreakParser;
|
|
23
23
|
export import HeadingParser = BlockParser.HeadingParser;
|
|
24
24
|
export import UListParser = BlockParser.UListParser;
|
|
25
25
|
export import OListParser = BlockParser.OListParser;
|
|
@@ -38,7 +38,7 @@ export const block: BlockParser = creation(1, false, error(
|
|
|
38
38
|
reset({ resources: { clock: 50 * 1000, recursion: 20 } },
|
|
39
39
|
union([
|
|
40
40
|
emptyline,
|
|
41
|
-
|
|
41
|
+
pagebreak,
|
|
42
42
|
heading,
|
|
43
43
|
ulist,
|
|
44
44
|
olist,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union, constraint, tails,
|
|
2
|
+
import { union, constraint, tails, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
3
|
import { unsafelink } from '../link';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { State } from '../../context';
|
|
@@ -12,10 +12,8 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
|
|
|
12
12
|
open(
|
|
13
13
|
'@',
|
|
14
14
|
tails([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
([source]) => source.length <= 253 + 1),
|
|
18
|
-
str(/^[a-z](?:-(?=[0-9a-z])|[0-9a-z]){0,63}/i),
|
|
15
|
+
str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
|
|
16
|
+
str(/^[a-z][0-9a-z]*(?:-[0-9a-z]+)*/i),
|
|
19
17
|
]))),
|
|
20
18
|
convert(
|
|
21
19
|
source =>
|
|
@@ -28,8 +28,8 @@ describe('Unit: parser/inline/autolink/anchor', () => {
|
|
|
28
28
|
assert.deepStrictEqual(inspect(parser('>>0-a')), [['<a class="anchor" href="?at=0-a">>>0-a</a>'], '']);
|
|
29
29
|
assert.deepStrictEqual(inspect(parser('>>0-A')), [['<a class="anchor" href="?at=0-A">>>0-A</a>'], '']);
|
|
30
30
|
assert.deepStrictEqual(inspect(parser('>>0--a')), [['<a class="anchor" href="?at=0">>>0</a>'], '--a']);
|
|
31
|
-
assert.deepStrictEqual(inspect(parser('>>2000-
|
|
32
|
-
assert.deepStrictEqual(inspect(parser('>>A/2000-
|
|
31
|
+
assert.deepStrictEqual(inspect(parser('>>2000-0131-2359-59999')), [['<a class="anchor" href="?at=2000-0131-2359-59999">>>2000-0131-2359-59999</a>'], '']);
|
|
32
|
+
assert.deepStrictEqual(inspect(parser('>>A/2000-0131-2359-59')), [['<a class="anchor" href="/@A/timeline?at=2000-0131-2359-59">>>A/2000-0131-2359-59</a>'], '']);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
});
|
|
@@ -7,8 +7,9 @@ import { define } from 'typed-dom/dom';
|
|
|
7
7
|
// Timeline(pseudonym): user/tid
|
|
8
8
|
// Thread(anonymous): cid
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// UTC
|
|
11
|
+
// tid: YYYY-MMDD-HHMM-SS
|
|
12
|
+
// cid: YYYY-MMDD-HHMM-SSmmm
|
|
12
13
|
|
|
13
14
|
// 内部表現はUnixTimeに統一する(時系列順)
|
|
14
15
|
// 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
|
|
@@ -21,7 +22,7 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fma
|
|
|
21
22
|
source =>
|
|
22
23
|
`[${source}]{ ${
|
|
23
24
|
source.includes('/')
|
|
24
|
-
? `/@${source.slice(2).replace('/', '/timeline
|
|
25
|
+
? `/@${source.slice(2).replace('/', '/timeline?at=')}`
|
|
25
26
|
: `?at=${source.slice(2)}`
|
|
26
27
|
} }`,
|
|
27
28
|
union([unsafelink])))),
|
|
@@ -16,8 +16,6 @@ describe('Unit: parser/inline/autolink/channel', () => {
|
|
|
16
16
|
assert.deepStrictEqual(inspect(parser('@a#1@b')), [['@a#1@b'], '']);
|
|
17
17
|
assert.deepStrictEqual(inspect(parser('@a#b#')), [['@a#b#'], '']);
|
|
18
18
|
assert.deepStrictEqual(inspect(parser('@a#b#1')), [['@a#b#1'], '']);
|
|
19
|
-
assert.deepStrictEqual(inspect(parser('@a#domain/b')), [['@a#domain/b'], '']);
|
|
20
|
-
assert.deepStrictEqual(inspect(parser('@domain/a#domain/b')), [['@domain/a#domain/b'], '']);
|
|
21
19
|
assert.deepStrictEqual(inspect(parser(' @a#b')), undefined);
|
|
22
20
|
});
|
|
23
21
|
|
|
@@ -14,7 +14,6 @@ export const channel: AutolinkParser.ChannelParser = validate('@', bind(
|
|
|
14
14
|
]),
|
|
15
15
|
(es, rest) => {
|
|
16
16
|
const source = stringify(es);
|
|
17
|
-
if (source.includes('/', source.indexOf('#'))) return;
|
|
18
17
|
const el = es[0];
|
|
19
18
|
const url = `${el.getAttribute('href')}?ch=${source.slice(source.indexOf('#') + 1).replace(/#/g, '+')}`;
|
|
20
19
|
return [[define(el, { class: 'channel', href: url }, source)], rest];
|
|
@@ -33,7 +33,7 @@ describe('Unit: parser/inline/autolink/hashnum', () => {
|
|
|
33
33
|
assert.deepStrictEqual(inspect(parser('あ#1')), [['あ#1'], '']);
|
|
34
34
|
assert.deepStrictEqual(inspect(parser(' #1')), undefined);
|
|
35
35
|
assert.deepStrictEqual(inspect(parser('#12345678901234567')), [['#12345678901234567'], '']);
|
|
36
|
-
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(
|
|
36
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(16)}a`)), [[`#${'1'.repeat(16)}a`], '']);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('valid', () => {
|
|
@@ -34,9 +34,6 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
34
34
|
assert.deepStrictEqual(inspect(parser('a##1')), [['a##1'], '']);
|
|
35
35
|
assert.deepStrictEqual(inspect(parser('a##b')), [['a##b'], '']);
|
|
36
36
|
assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
|
|
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`]);
|
|
40
37
|
assert.deepStrictEqual(inspect(parser(' #a')), undefined);
|
|
41
38
|
});
|
|
42
39
|
|
|
@@ -51,15 +48,13 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
51
48
|
assert.deepStrictEqual(inspect(parser('#a(b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b']);
|
|
52
49
|
assert.deepStrictEqual(inspect(parser('#a(b)')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '(b)']);
|
|
53
50
|
assert.deepStrictEqual(inspect(parser('#a_')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '_']);
|
|
54
|
-
assert.deepStrictEqual(inspect(parser('#a__b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
|
|
55
51
|
assert.deepStrictEqual(inspect(parser('#a_b')), [['<a class="hashtag" href="/hashtags/a_b">#a_b</a>'], '']);
|
|
52
|
+
assert.deepStrictEqual(inspect(parser('#a__b')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
|
|
56
53
|
assert.deepStrictEqual(inspect(parser('#あ')), [['<a class="hashtag" href="/hashtags/あ">#あ</a>'], '']);
|
|
57
54
|
assert.deepStrictEqual(inspect(parser('#👩')), [['<a class="hashtag" href="/hashtags/👩">#👩</a>'], '']);
|
|
58
55
|
assert.deepStrictEqual(inspect(parser('#1a')), [['<a class="hashtag" href="/hashtags/1a">#1a</a>'], '']);
|
|
59
56
|
assert.deepStrictEqual(inspect(parser('#1あ')), [['<a class="hashtag" href="/hashtags/1あ">#1あ</a>'], '']);
|
|
60
57
|
assert.deepStrictEqual(inspect(parser('#1👩')), [['<a class="hashtag" href="/hashtags/1👩">#1👩</a>'], '']);
|
|
61
|
-
assert.deepStrictEqual(inspect(parser('#domain/a')), [['<a class="hashtag" href="https://domain/hashtags/a" target="_blank">#domain/a</a>'], '']);
|
|
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>'], '']);
|
|
63
58
|
assert.deepStrictEqual(inspect(parser(`#'0`)), [[`<a class="hashtag" href="/hashtags/'0">#'0</a>`], '']);
|
|
64
59
|
assert.deepStrictEqual(inspect(parser(`#'00`)), [[`<a class="hashtag" href="/hashtags/'00">#'00</a>`], '']);
|
|
65
60
|
assert.deepStrictEqual(inspect(parser(`#1'`)), [[`<a class="hashtag" href="/hashtags/1'">#1'</a>`], '']);
|
|
@@ -69,6 +64,7 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
69
64
|
assert.deepStrictEqual(inspect(parser(`#a'b`)), [[`<a class="hashtag" href="/hashtags/a'b">#a'b</a>`], '']);
|
|
70
65
|
assert.deepStrictEqual(inspect(parser(`#a'_b`)), [[`<a class="hashtag" href="/hashtags/a'_b">#a'_b</a>`], '']);
|
|
71
66
|
assert.deepStrictEqual(inspect(parser(`#a_'b`)), [[`<a class="hashtag" href="/hashtags/a_'b">#a_'b</a>`], '']);
|
|
67
|
+
assert.deepStrictEqual(inspect(parser(`#${'1'.repeat(15)}a`)), [[`<a class="hashtag" href="/hashtags/${'1'.repeat(15)}a">#${'1'.repeat(15)}a</a>`], '']);
|
|
72
68
|
});
|
|
73
69
|
|
|
74
70
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union,
|
|
2
|
+
import { union, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
3
3
|
import { unsafelink } from '../link';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { State } from '../../context';
|
|
@@ -14,24 +14,11 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
|
|
|
14
14
|
constraint(State.shortcut, false,
|
|
15
15
|
open(
|
|
16
16
|
'#',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
verify(
|
|
22
|
-
str(new RegExp([
|
|
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
|
-
].join('').replace(/emoji/g, emoji), 'u')),
|
|
27
|
-
([source]) => source.length <= 128),
|
|
28
|
-
]))),
|
|
17
|
+
str(new RegExp([
|
|
18
|
+
/^(?=(?:[0-9]{1,15})?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'))/u.source,
|
|
19
|
+
/(?:[^\p{C}\p{S}\p{P}\s]|emoji|(?<!')'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|'))+/u.source,
|
|
20
|
+
].join('').replace(/emoji/g, emoji), 'u')))),
|
|
29
21
|
convert(
|
|
30
|
-
source =>
|
|
31
|
-
`[${source}]{ ${
|
|
32
|
-
source.includes('/')
|
|
33
|
-
? `https://${source.slice(1).replace('/', '/hashtags/')}`
|
|
34
|
-
: `/hashtags/${source.slice(1)}`
|
|
35
|
-
} }`,
|
|
22
|
+
source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
|
|
36
23
|
union([unsafelink]))),
|
|
37
|
-
([el]) => [define(el, { class: 'hashtag' }
|
|
24
|
+
([el]) => [define(el, { class: 'hashtag' })]));
|
|
@@ -14,7 +14,7 @@ export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surroun
|
|
|
14
14
|
constraint(State.index, false,
|
|
15
15
|
syntax(Syntax.index, 2, 1, State.linkers | State.media,
|
|
16
16
|
startTight(
|
|
17
|
-
open(stropt(
|
|
17
|
+
open(stropt('|'), trimBlankEnd(some(union([
|
|
18
18
|
signature,
|
|
19
19
|
inline,
|
|
20
20
|
]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))),
|
|
@@ -9,6 +9,7 @@ export function indexee(parser: Parser<HTMLElement, MarkdownParser.Context>, opt
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function identity(id: string | undefined, text: string, name: 'index' | 'mark' = 'index'): string | undefined {
|
|
12
|
+
assert(!id?.match(/[^0-9a-z/-]/i));
|
|
12
13
|
assert(!text.includes('\n'));
|
|
13
14
|
if (id === '') return undefined;
|
|
14
15
|
text &&= text.trim().replace(/\s+/g, '_');
|
|
@@ -180,6 +180,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
|
|
|
180
180
|
case uri.slice(0, 2) === '^/':
|
|
181
181
|
const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
|
|
182
182
|
return last.includes('.') // isFile
|
|
183
|
+
// Exclude ISO 6709.
|
|
183
184
|
&& /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
|
|
184
185
|
? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
|
|
185
186
|
: `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
|
|
@@ -122,6 +122,7 @@ describe('Unit: parser/inline', () => {
|
|
|
122
122
|
assert.deepStrictEqual(inspect(parser('a@b')), [['<a class="email" href="mailto:a@b">a@b</a>'], '']);
|
|
123
123
|
assert.deepStrictEqual(inspect(parser('_a@b')), [['_', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
|
|
124
124
|
assert.deepStrictEqual(inspect(parser('_a@b_')), [['<em><a class="email" href="mailto:a@b">a@b</a></em>'], '']);
|
|
125
|
+
assert.deepStrictEqual(inspect(parser('_a_b@c_')), [['<em><a class="email" href="mailto:a_b@c">a_b@c</a></em>'], '']);
|
|
125
126
|
assert.deepStrictEqual(inspect(parser('*a@b*')), [['<strong><a class="email" href="mailto:a@b">a@b</a></strong>'], '']);
|
|
126
127
|
assert.deepStrictEqual(inspect(parser('(a@b)')), [['<span class="paren">(<a class="email" href="mailto:a@b">a@b</a>)</span>'], '']);
|
|
127
128
|
assert.deepStrictEqual(inspect(parser(' a@b')), [[' ', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
|
|
@@ -130,9 +131,7 @@ describe('Unit: parser/inline', () => {
|
|
|
130
131
|
|
|
131
132
|
it('channel', () => {
|
|
132
133
|
assert.deepStrictEqual(inspect(parser('@a#b')), [['<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
|
|
133
|
-
assert.deepStrictEqual(inspect(parser('@a#domain/b')), [['@a#domain/b'], '']);
|
|
134
134
|
assert.deepStrictEqual(inspect(parser('@domain/a#b')), [['<a class="channel" href="https://domain/@a?ch=b" target="_blank">@domain/a#b</a>'], '']);
|
|
135
|
-
assert.deepStrictEqual(inspect(parser('@domain/a#domain/b')), [['@domain/a#domain/b'], '']);
|
|
136
135
|
assert.deepStrictEqual(inspect(parser('_@a#b')), [['_', '<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
|
|
137
136
|
assert.deepStrictEqual(inspect(parser(' @a#b')), [[' ', '<a class="channel" href="/@a?ch=b">@a#b</a>'], '']);
|
|
138
137
|
});
|
|
@@ -152,6 +151,8 @@ describe('Unit: parser/inline', () => {
|
|
|
152
151
|
assert.deepStrictEqual(inspect(parser('#a')), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '']);
|
|
153
152
|
assert.deepStrictEqual(inspect(parser('#a\nb\n#c\n[#d]')), [['<a class="hashtag" href="/hashtags/a">#a</a>', '<br>', 'b', '<br>', '<a class="hashtag" href="/hashtags/c">#c</a>', '<br>', '<a class="index" href="#index::d">d</a>'], '']);
|
|
154
153
|
assert.deepStrictEqual(inspect(parser('##a')), [['##a'], '']);
|
|
154
|
+
assert.deepStrictEqual(inspect(parser('_#a')), [['_', '<a class="hashtag" href="/hashtags/a">#a</a>'], '']);
|
|
155
|
+
assert.deepStrictEqual(inspect(parser('_#a_')), [['<em><a class="hashtag" href="/hashtags/a">#a</a></em>'], '']);
|
|
155
156
|
assert.deepStrictEqual(inspect(parser('a#b')), [['a#b'], '']);
|
|
156
157
|
assert.deepStrictEqual(inspect(parser('0a#b')), [['0a#b'], '']);
|
|
157
158
|
assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
|
|
@@ -173,6 +174,8 @@ describe('Unit: parser/inline', () => {
|
|
|
173
174
|
it('hashnum', () => {
|
|
174
175
|
assert.deepStrictEqual(inspect(parser('#1')), [['<a class="hashnum">#1</a>'], '']);
|
|
175
176
|
assert.deepStrictEqual(inspect(parser('#12345678901234567@a')), [['#12345678901234567@a'], '']);
|
|
177
|
+
assert.deepStrictEqual(inspect(parser('_#1_')), [['<em><a class="hashnum">#1</a></em>'], '']);
|
|
178
|
+
assert.deepStrictEqual(inspect(parser('_#1_0')), [['<em><a class="hashnum">#1</a></em>', '0'], '']);
|
|
176
179
|
assert.deepStrictEqual(inspect(parser('「#1」')), [['「', '<a class="hashnum">#1</a>', '」'], '']);
|
|
177
180
|
});
|
|
178
181
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnyLineParser, EmptyLineParser, ContentLineParser } from '../source';
|
|
2
|
-
import { line,
|
|
2
|
+
import { line, isBlank } from '../../combinator';
|
|
3
3
|
|
|
4
4
|
export const anyline: AnyLineParser = line(() => [[], '']);
|
|
5
|
-
export const emptyline: EmptyLineParser = line(i =>
|
|
6
|
-
export const contentline: ContentLineParser = line(i => !
|
|
5
|
+
export const emptyline: EmptyLineParser = line(i => isBlank(i.source) ? [[], ''] : undefined);
|
|
6
|
+
export const contentline: ContentLineParser = line(i => !isBlank(i.source) ? [[], ''] : undefined);
|