securemark 0.279.1 → 0.280.1
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 +10 -0
- package/README.md +10 -10
- package/dist/index.js +120 -138
- package/markdown.d.ts +1 -7
- package/package.json +1 -1
- package/src/combinator/data/parser/context/delimiter.ts +49 -21
- package/src/combinator/data/parser/context/memo.ts +6 -6
- package/src/combinator/data/parser/context.ts +23 -7
- package/src/combinator/data/parser/some.ts +1 -1
- package/src/parser/api/bind.ts +4 -1
- package/src/parser/api/parse.test.ts +13 -13
- package/src/parser/api/parse.ts +4 -1
- package/src/parser/block/extension/table.ts +1 -1
- package/src/parser/block/heading.ts +1 -1
- package/src/parser/block/reply/quote.ts +9 -46
- package/src/parser/block.ts +2 -2
- package/src/parser/inline/annotation.ts +4 -4
- package/src/parser/inline/autolink/account.ts +1 -1
- package/src/parser/inline/autolink/url.ts +1 -1
- package/src/parser/inline/autolink.ts +2 -2
- package/src/parser/inline/bracket.test.ts +3 -2
- package/src/parser/inline/bracket.ts +8 -9
- package/src/parser/inline/deletion.ts +4 -4
- package/src/parser/inline/emphasis.ts +4 -4
- package/src/parser/inline/emstrong.ts +4 -4
- package/src/parser/inline/extension/index.test.ts +6 -0
- package/src/parser/inline/extension/index.ts +11 -11
- package/src/parser/inline/extension/placeholder.ts +4 -4
- package/src/parser/inline/html.test.ts +1 -1
- package/src/parser/inline/html.ts +5 -5
- package/src/parser/inline/insertion.ts +4 -4
- package/src/parser/inline/link.ts +6 -6
- package/src/parser/inline/mark.ts +4 -4
- package/src/parser/inline/media.ts +4 -4
- package/src/parser/inline/reference.ts +3 -3
- package/src/parser/inline/remark.test.ts +1 -0
- package/src/parser/inline/remark.ts +4 -4
- package/src/parser/inline/ruby.ts +2 -2
- package/src/parser/inline/strong.ts +4 -4
- package/src/parser/inline/template.ts +7 -4
- package/src/parser/inline.test.ts +2 -5
- package/src/parser/source/str.ts +0 -19
- package/src/parser/source/text.test.ts +1 -1
- package/src/parser/source/text.ts +3 -3
- package/src/parser/source.ts +1 -1
- package/src/parser/visibility.ts +3 -1
- package/src/util/toc.ts +4 -1
package/markdown.d.ts
CHANGED
|
@@ -631,12 +631,6 @@ export namespace MarkdownParser {
|
|
|
631
631
|
export namespace QuoteParser {
|
|
632
632
|
export interface BlockParser extends
|
|
633
633
|
Block<'reply/quote/block'>,
|
|
634
|
-
Parser<string | HTMLElement, Context, [
|
|
635
|
-
TextParser,
|
|
636
|
-
]> {
|
|
637
|
-
}
|
|
638
|
-
export interface TextParser extends
|
|
639
|
-
Block<'reply/quote/text'>,
|
|
640
634
|
Parser<string | HTMLElement, Context, [
|
|
641
635
|
InlineParser.MathParser,
|
|
642
636
|
InlineParser.AutolinkParser,
|
|
@@ -784,8 +778,8 @@ export namespace MarkdownParser {
|
|
|
784
778
|
// [#index|signature]
|
|
785
779
|
Inline<'extension/index'>,
|
|
786
780
|
Parser<HTMLAnchorElement, Context, [
|
|
787
|
-
IndexParser.SignatureParser,
|
|
788
781
|
InlineParser,
|
|
782
|
+
IndexParser.SignatureParser,
|
|
789
783
|
]> {
|
|
790
784
|
}
|
|
791
785
|
export namespace IndexParser {
|
package/package.json
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { memoize, reduce } from 'spica/memoize';
|
|
2
2
|
|
|
3
|
+
interface Delimiter {
|
|
4
|
+
readonly index: number;
|
|
5
|
+
readonly signature: string;
|
|
6
|
+
readonly matcher: (source: string) => boolean | undefined;
|
|
7
|
+
readonly precedence: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
export class Delimiters {
|
|
4
11
|
public static signature(pattern: string | RegExp | undefined): string {
|
|
5
12
|
switch (typeof pattern) {
|
|
@@ -23,43 +30,64 @@ export class Delimiters {
|
|
|
23
30
|
}
|
|
24
31
|
},
|
|
25
32
|
this.signature);
|
|
26
|
-
private readonly
|
|
27
|
-
private readonly
|
|
28
|
-
private
|
|
33
|
+
private readonly registry = memoize<(signature: string) => Delimiter[]>(() => []);
|
|
34
|
+
private readonly delimiters: Delimiter[] = [];
|
|
35
|
+
private readonly order: number[] = [];
|
|
29
36
|
public push(
|
|
30
|
-
|
|
37
|
+
delims: readonly {
|
|
31
38
|
readonly signature: string;
|
|
32
39
|
readonly matcher: (source: string) => boolean | undefined;
|
|
33
40
|
readonly precedence?: number;
|
|
34
41
|
}[]
|
|
35
42
|
): void {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const { registry, delimiters, order } = this;
|
|
44
|
+
for (let i = 0; i < delims.length; ++i) {
|
|
45
|
+
const { signature, matcher, precedence = 1 } = delims[i];
|
|
46
|
+
const stack = registry(signature);
|
|
47
|
+
const index = stack[0]?.index ?? delimiters.length;
|
|
48
|
+
if (stack.length === 0 || precedence > delimiters[index].precedence) {
|
|
49
|
+
const delimiter: Delimiter = {
|
|
50
|
+
index,
|
|
51
|
+
signature,
|
|
52
|
+
matcher,
|
|
53
|
+
precedence,
|
|
54
|
+
};
|
|
55
|
+
delimiters[index] = delimiter;
|
|
56
|
+
stack.push(delimiter);
|
|
57
|
+
order.push(index);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
order.push(-1);
|
|
43
61
|
}
|
|
44
|
-
++this.length;
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
public pop(count = 1): void {
|
|
48
65
|
assert(count > 0);
|
|
66
|
+
const { registry, delimiters, order } = this;
|
|
49
67
|
for (let i = 0; i < count; ++i) {
|
|
50
|
-
assert(this.
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
68
|
+
assert(this.order.length > 0);
|
|
69
|
+
const index = order.pop()!;
|
|
70
|
+
if (index === -1) continue;
|
|
71
|
+
const stack = registry(delimiters[index].signature);
|
|
72
|
+
assert(stack.length > 0);
|
|
73
|
+
if (stack.length === 1) {
|
|
74
|
+
assert(index === delimiters.length - 1);
|
|
75
|
+
assert(stack[0] === delimiters.at(-1));
|
|
76
|
+
stack.pop();
|
|
77
|
+
delimiters.pop();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
stack.pop();
|
|
81
|
+
delimiters[index] = stack.at(-1)!;
|
|
54
82
|
}
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
85
|
public match(source: string, precedence = 1): boolean {
|
|
58
|
-
const {
|
|
59
|
-
for (let i = 0; i <
|
|
60
|
-
const
|
|
61
|
-
if (precedence >=
|
|
62
|
-
switch (matcher
|
|
86
|
+
const { delimiters } = this;
|
|
87
|
+
for (let i = 0; i < delimiters.length; ++i) {
|
|
88
|
+
const delimiter = delimiters[i];
|
|
89
|
+
if (precedence >= delimiter.precedence) continue;
|
|
90
|
+
switch (delimiter.matcher(source)) {
|
|
63
91
|
case true:
|
|
64
92
|
return true;
|
|
65
93
|
case false:
|
|
@@ -3,7 +3,7 @@ export class Memo {
|
|
|
3
3
|
this.targets = targets;
|
|
4
4
|
}
|
|
5
5
|
public readonly targets: number;
|
|
6
|
-
private readonly memory: Record<
|
|
6
|
+
private readonly memory: Record<number, Record<number, readonly [any[], number] | readonly []>>[/* pos */] = [];
|
|
7
7
|
public get length(): number {
|
|
8
8
|
return this.memory.length;
|
|
9
9
|
}
|
|
@@ -12,8 +12,8 @@ export class Memo {
|
|
|
12
12
|
syntax: number,
|
|
13
13
|
state: number,
|
|
14
14
|
): readonly [any[], number] | readonly [] | undefined {
|
|
15
|
-
//console.log('get', position, syntax, state, this.memory[position - 1]?.[
|
|
16
|
-
const cache = this.memory[position - 1]?.[
|
|
15
|
+
//console.log('get', position, syntax, state, this.memory[position - 1]?.[syntax]?.[state]);
|
|
16
|
+
const cache = this.memory[position - 1]?.[syntax]?.[state];
|
|
17
17
|
return cache?.length === 2
|
|
18
18
|
? [cache[0].slice(), cache[1]]
|
|
19
19
|
: cache;
|
|
@@ -26,11 +26,11 @@ export class Memo {
|
|
|
26
26
|
offset: number,
|
|
27
27
|
): void {
|
|
28
28
|
const record = this.memory[position - 1] ??= {};
|
|
29
|
-
assert(!record[
|
|
30
|
-
record[
|
|
29
|
+
assert(!record[syntax]?.[state]);
|
|
30
|
+
(record[syntax] ??= {})[state] = nodes
|
|
31
31
|
? [nodes.slice(), offset]
|
|
32
32
|
: [];
|
|
33
|
-
//console.log('set', position, syntax, state, record[
|
|
33
|
+
//console.log('set', position, syntax, state, record[syntax]?.[state]);
|
|
34
34
|
}
|
|
35
35
|
public clear(position: number): void {
|
|
36
36
|
const memory = this.memory;
|
|
@@ -66,9 +66,9 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: read
|
|
|
66
66
|
return result;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number,
|
|
70
|
-
export function syntax<T>(syntax: number, prec: number,
|
|
71
|
-
return
|
|
69
|
+
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, state: number, parser: P): P;
|
|
70
|
+
export function syntax<T>(syntax: number, prec: number, state: number, parser?: Parser<T>): Parser<T> {
|
|
71
|
+
return precedence(prec, ({ source, context }) => {
|
|
72
72
|
if (source === '') return;
|
|
73
73
|
const memo = context.memo ??= new Memo();
|
|
74
74
|
context.offset ??= 0;
|
|
@@ -91,7 +91,7 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
|
|
|
91
91
|
}
|
|
92
92
|
context.state = stateOuter;
|
|
93
93
|
return result;
|
|
94
|
-
})
|
|
94
|
+
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
export function creation<P extends Parser<unknown>>(parser: P): P;
|
|
@@ -100,11 +100,13 @@ export function creation<P extends Parser<unknown>>(cost: number, recursion: boo
|
|
|
100
100
|
export function creation(cost: number | Parser<unknown>, recursion?: boolean | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
101
101
|
if (typeof cost === 'function') return creation(1, true, cost);
|
|
102
102
|
if (typeof recursion === 'function') return creation(cost, true, recursion);
|
|
103
|
-
assert(cost
|
|
103
|
+
assert(cost >= 0);
|
|
104
|
+
assert(recursion !== undefined);
|
|
104
105
|
return ({ source, context }) => {
|
|
105
|
-
|
|
106
|
+
assert([recursion = recursion!]);
|
|
107
|
+
const resources = context.resources ?? { clock: cost || 1, recursion: 1 };
|
|
106
108
|
if (resources.clock <= 0) throw new Error('Too many creations');
|
|
107
|
-
if (resources.recursion
|
|
109
|
+
if (resources.recursion < +recursion) throw new Error('Too much recursion');
|
|
108
110
|
recursion && --resources.recursion;
|
|
109
111
|
const result = parser!({ source, context });
|
|
110
112
|
recursion && ++resources.recursion;
|
|
@@ -171,3 +173,17 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
|
|
|
171
173
|
return result;
|
|
172
174
|
};
|
|
173
175
|
}
|
|
176
|
+
|
|
177
|
+
//export function log<P extends Parser<unknown>>(log: number, parser: P, cond?: (ns: readonly Tree<P>[]) => boolean): P;
|
|
178
|
+
//export function log<T>(log: number, parser: Parser<T>, cond: (ns: readonly T[]) => boolean = () => true): Parser<T> {
|
|
179
|
+
// assert(log);
|
|
180
|
+
// return ({ source, context }) => {
|
|
181
|
+
// const l = context.log ?? 0;
|
|
182
|
+
// context.log = 0;
|
|
183
|
+
// const result = parser!({ source, context });
|
|
184
|
+
// context.log = result && cond(eval(result))
|
|
185
|
+
// ? l | log
|
|
186
|
+
// : l;
|
|
187
|
+
// return result;
|
|
188
|
+
// };
|
|
189
|
+
//}
|
|
@@ -22,7 +22,7 @@ export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delim
|
|
|
22
22
|
let nodes: T[] | undefined;
|
|
23
23
|
if (delims.length > 0) {
|
|
24
24
|
context.delimiters ??= new Delimiters();
|
|
25
|
-
context.delimiters.push(
|
|
25
|
+
context.delimiters.push(delims);
|
|
26
26
|
}
|
|
27
27
|
while (true) {
|
|
28
28
|
if (rest === '') break;
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -26,9 +26,12 @@ 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
|
+
assert(!context.offset);
|
|
30
|
+
assert(!context.precedence);
|
|
31
|
+
assert(!context.delimiters);
|
|
32
|
+
assert(!context.state);
|
|
29
33
|
if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
|
|
30
34
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
31
|
-
assert(!settings.id);
|
|
32
35
|
type Block = readonly [segment: string, blocks: readonly HTMLElement[], url: string];
|
|
33
36
|
const blocks: Block[] = [];
|
|
34
37
|
const adds: [HTMLElement, Node | null][] = [];
|
|
@@ -298,17 +298,17 @@ describe('Unit: parser/api/parse', () => {
|
|
|
298
298
|
|
|
299
299
|
it('recursion', () => {
|
|
300
300
|
assert.deepStrictEqual(
|
|
301
|
-
[...parse('{'.repeat(
|
|
302
|
-
[`<p>${'{'.repeat(
|
|
301
|
+
[...parse('{'.repeat(21)).children].map(el => el.outerHTML),
|
|
302
|
+
[`<p>${'{'.repeat(21)}</p>`]);
|
|
303
303
|
assert.deepStrictEqual(
|
|
304
|
-
[...parse('{'.repeat(
|
|
304
|
+
[...parse('{'.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
305
305
|
[
|
|
306
306
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
307
|
-
`<pre class="error" translate="no">${'{'.repeat(
|
|
307
|
+
`<pre class="error" translate="no">${'{'.repeat(22)}</pre>`,
|
|
308
308
|
]);
|
|
309
309
|
assert.deepStrictEqual(
|
|
310
|
-
[...parse('('.repeat(
|
|
311
|
-
[`<p>${'('.repeat(
|
|
310
|
+
[...parse('('.repeat(21)).children].map(el => el.outerHTML),
|
|
311
|
+
[`<p>${'('.repeat(21)}</p>`]);
|
|
312
312
|
assert.deepStrictEqual(
|
|
313
313
|
[...parse('('.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
314
314
|
[
|
|
@@ -316,8 +316,8 @@ describe('Unit: parser/api/parse', () => {
|
|
|
316
316
|
`<pre class="error" translate="no">${'('.repeat(22)}</pre>`,
|
|
317
317
|
]);
|
|
318
318
|
assert.deepStrictEqual(
|
|
319
|
-
[...parse('['.repeat(
|
|
320
|
-
[`<p>${'['.repeat(
|
|
319
|
+
[...parse('['.repeat(21)).children].map(el => el.outerHTML),
|
|
320
|
+
[`<p>${'['.repeat(21)}</p>`]);
|
|
321
321
|
assert.deepStrictEqual(
|
|
322
322
|
[...parse('['.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
323
323
|
[
|
|
@@ -325,8 +325,8 @@ describe('Unit: parser/api/parse', () => {
|
|
|
325
325
|
`<pre class="error" translate="no">${'['.repeat(22)}</pre>`,
|
|
326
326
|
]);
|
|
327
327
|
assert.deepStrictEqual(
|
|
328
|
-
[...parse('['.repeat(
|
|
329
|
-
[`<p>${'['.repeat(
|
|
328
|
+
[...parse('['.repeat(20) + '\na').children].map(el => el.outerHTML),
|
|
329
|
+
[`<p>${'['.repeat(20)}<br>a</p>`]);
|
|
330
330
|
});
|
|
331
331
|
|
|
332
332
|
if (!navigator.userAgent.includes('Chrome')) return;
|
|
@@ -335,15 +335,15 @@ describe('Unit: parser/api/parse', () => {
|
|
|
335
335
|
this.timeout(5000);
|
|
336
336
|
// 実測500ms程度
|
|
337
337
|
assert.deepStrictEqual(
|
|
338
|
-
[...parse('.'.repeat(
|
|
339
|
-
[`<p>${'.'.repeat(
|
|
338
|
+
[...parse('.'.repeat(20000)).children].map(el => el.outerHTML),
|
|
339
|
+
[`<p>${'.'.repeat(20000)}</p>`]);
|
|
340
340
|
});
|
|
341
341
|
|
|
342
342
|
it('creation error', function () {
|
|
343
343
|
this.timeout(5000);
|
|
344
344
|
// 実測500ms程度
|
|
345
345
|
assert.deepStrictEqual(
|
|
346
|
-
[...parse('.'.repeat(
|
|
346
|
+
[...parse('.'.repeat(20001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
347
347
|
[
|
|
348
348
|
'<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
|
|
349
349
|
`<pre class="error" translate="no">${'.'.repeat(1000).slice(0, 997)}...</pre>`,
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -21,7 +21,6 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
21
21
|
if (!validate(source, MAX_SEGMENT_SIZE)) throw new Error(`Too large input over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes`);
|
|
22
22
|
const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
23
23
|
source = !context ? normalize(source) : source;
|
|
24
|
-
assert(!context?.delimiters);
|
|
25
24
|
context = {
|
|
26
25
|
host: opts.host ?? context?.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
27
26
|
url: url ? new ReadonlyURL(url as ':') : context?.url,
|
|
@@ -32,6 +31,10 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
32
31
|
},
|
|
33
32
|
memo: new Memo({ targets: State.backtrackers }),
|
|
34
33
|
};
|
|
34
|
+
assert(!context.offset);
|
|
35
|
+
assert(!context.precedence);
|
|
36
|
+
assert(!context.delimiters);
|
|
37
|
+
assert(!context.state);
|
|
35
38
|
if (context.id?.match(/[^0-9a-z/-]/i)) throw new Error('Invalid ID: ID must be alphanumeric');
|
|
36
39
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
37
40
|
const node = frag();
|
|
@@ -119,7 +119,7 @@ const dataline: CellParser.DatalineParser = creation(1, false, line(
|
|
|
119
119
|
convert(source => `: ${source}`, data),
|
|
120
120
|
]))));
|
|
121
121
|
|
|
122
|
-
function attributes(source: string) {
|
|
122
|
+
function attributes(source: string): Record<string, string | undefined> {
|
|
123
123
|
let [, rowspan = undefined, colspan = undefined, highlight = undefined, extension = undefined] =
|
|
124
124
|
source.match(/^[#:](?:(\d+)?:(\d+)?)?(?:(!+)([+]?))?$/) ?? [];
|
|
125
125
|
assert(rowspan?.[0] !== '0');
|
|
@@ -18,7 +18,7 @@ export const heading: HeadingParser = block(rewrite(segment,
|
|
|
18
18
|
visualize(trimBlankStart(some(union([indexer, inline])))), true),
|
|
19
19
|
open(
|
|
20
20
|
str('#'),
|
|
21
|
-
state(State.
|
|
21
|
+
state(State.linkers,
|
|
22
22
|
visualize(trimBlankStart(some(union([indexer, inline]))))), true),
|
|
23
23
|
]),
|
|
24
24
|
([h, ...ns]: [string, ...(HTMLElement | string)[]]) => [
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
|
-
import {
|
|
3
|
-
import { union, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
|
|
2
|
+
import { union, some, creation, block, line, validate, rewrite, convert, lazy, fmap } from '../../../combinator';
|
|
4
3
|
import { math } from '../../inline/math';
|
|
5
4
|
import { autolink } from '../../inline/autolink';
|
|
6
5
|
import { linebreak, unescsource, str, anyline } from '../../source';
|
|
@@ -33,47 +32,11 @@ export const quote: ReplyParser.QuoteParser = lazy(() => creation(1, false, bloc
|
|
|
33
32
|
]),
|
|
34
33
|
false)));
|
|
35
34
|
|
|
36
|
-
const qblock: ReplyParser.QuoteParser.BlockParser = (
|
|
37
|
-
source
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const nodes = eval(text({ source: `\r${content}`, context }), []);
|
|
45
|
-
nodes.unshift(quotes.shift()!);
|
|
46
|
-
for (let i = 0; i < nodes.length; ++i) {
|
|
47
|
-
const child = nodes[i] as string | Text | Element;
|
|
48
|
-
if (typeof child === 'string') continue;
|
|
49
|
-
if ('wholeText' in child) {
|
|
50
|
-
nodes[i] = child.data;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
assert(child instanceof HTMLElement);
|
|
54
|
-
if (child.tagName === 'BR') {
|
|
55
|
-
assert(quotes.length > 0);
|
|
56
|
-
nodes.splice(i + 1, 0, quotes.shift()!);
|
|
57
|
-
++i;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (child.className === 'cite' || child.classList.contains('quote')) {
|
|
61
|
-
context.resources && (context.resources.clock -= child.childNodes.length);
|
|
62
|
-
nodes.splice(i, 1, ...child.childNodes as NodeListOf<HTMLElement>);
|
|
63
|
-
--i;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
nodes.unshift('');
|
|
68
|
-
assert(nodes.length > 1);
|
|
69
|
-
assert(nodes.every(n => typeof n === 'string' || n instanceof HTMLElement));
|
|
70
|
-
assert(quotes.length === 0);
|
|
71
|
-
return [nodes, ''];
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const text: ReplyParser.QuoteParser.TextParser = some(union([
|
|
75
|
-
math, // quote補助関数が残した数式をパースする。他の構文で数式を残す場合はソーステキストを直接使用する。
|
|
76
|
-
autolink,
|
|
77
|
-
linebreak,
|
|
78
|
-
unescsource,
|
|
79
|
-
]));
|
|
35
|
+
const qblock: ReplyParser.QuoteParser.BlockParser = convert(
|
|
36
|
+
source => source.replace(/\n$/, '').replace(/(?<=^>+[^\S\n])/mg, '\r'),
|
|
37
|
+
some(union([
|
|
38
|
+
math, // quote補助関数が残した数式をパースする。他の構文で数式を残す場合はソーステキストを直接使用する。
|
|
39
|
+
autolink,
|
|
40
|
+
linebreak,
|
|
41
|
+
unescsource,
|
|
42
|
+
])));
|
package/src/parser/block.ts
CHANGED
|
@@ -36,8 +36,8 @@ export import MediaBlockParser = BlockParser.MediaBlockParser;
|
|
|
36
36
|
export import ReplyParser = BlockParser.ReplyParser;
|
|
37
37
|
export import ParagraphParser = BlockParser.ParagraphParser;
|
|
38
38
|
|
|
39
|
-
export const block: BlockParser = creation(
|
|
40
|
-
reset({ resources: { clock:
|
|
39
|
+
export const block: BlockParser = creation(0, false, error(
|
|
40
|
+
reset({ resources: { clock: 20000, recursion: 20 + 1 } },
|
|
41
41
|
union([
|
|
42
42
|
emptyline,
|
|
43
43
|
pagebreak,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { AnnotationParser } from '../inline';
|
|
2
|
-
import { union, some, syntax, constraint, surround, lazy } from '../../combinator';
|
|
2
|
+
import { union, some, syntax, creation, constraint, surround, lazy } from '../../combinator';
|
|
3
3
|
import { inline } from '../inline';
|
|
4
4
|
import { Syntax, State } from '../context';
|
|
5
5
|
import { trimBlankStart, trimNodeEnd } from '../visibility';
|
|
6
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
7
|
|
|
8
|
-
export const annotation: AnnotationParser = lazy(() => surround(
|
|
8
|
+
export const annotation: AnnotationParser = lazy(() => creation(surround(
|
|
9
9
|
'((',
|
|
10
10
|
constraint(State.annotation, false,
|
|
11
|
-
syntax(Syntax.none, 6,
|
|
11
|
+
syntax(Syntax.none, 6, State.annotation | State.media,
|
|
12
12
|
trimBlankStart(some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])))),
|
|
13
13
|
'))',
|
|
14
14
|
false,
|
|
15
|
-
([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNodeEnd(defrag(ns)))])], rest]));
|
|
15
|
+
([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNodeEnd(defrag(ns)))])], rest])));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { union,
|
|
2
|
+
import { union, tails, 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';
|
|
@@ -28,5 +28,5 @@ const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creation(prec
|
|
|
28
28
|
surround('(', some(union([bracket, unescsource]), ')'), ')', true),
|
|
29
29
|
surround('[', some(union([bracket, unescsource]), ']'), ']', true),
|
|
30
30
|
surround('{', some(union([bracket, unescsource]), '}'), '}', true),
|
|
31
|
-
surround('"', precedence(
|
|
31
|
+
surround('"', precedence(3, some(unescsource, '"')), '"', true),
|
|
32
32
|
]))));
|
|
@@ -12,9 +12,9 @@ import { Syntax, State } from '../context';
|
|
|
12
12
|
import { stringify } from '../util';
|
|
13
13
|
|
|
14
14
|
export const autolink: AutolinkParser = lazy(() =>
|
|
15
|
-
validate(/^(?:[@#>0-9a-z
|
|
15
|
+
validate(/^(?:[@#>0-9a-z]|\S[#>]|[\r\n]!?https?:\/\/)/iu,
|
|
16
16
|
constraint(State.autolink, false,
|
|
17
|
-
syntax(Syntax.autolink, 1,
|
|
17
|
+
syntax(Syntax.autolink, 1, ~State.shortcut,
|
|
18
18
|
union([
|
|
19
19
|
some(union([lineurl])),
|
|
20
20
|
fmap(some(union([
|
|
@@ -48,6 +48,9 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
48
48
|
assert.deepStrictEqual(inspect(parser('(A)')), [['(', 'A', ')'], '']);
|
|
49
49
|
assert.deepStrictEqual(inspect(parser('(A,B)')), [['(', 'A,B', ')'], '']);
|
|
50
50
|
assert.deepStrictEqual(inspect(parser('(A、B)')), [['(', 'A、B', ')'], '']);
|
|
51
|
+
assert.deepStrictEqual(inspect(parser('(<bdi>a\\\nb</bdi>)')), [['<span class="paren">(<bdi>a<br>b</bdi>)</span>'], '']);
|
|
52
|
+
assert.deepStrictEqual(inspect(parser('([% a\\\nb %])')), [['<span class="paren">(<span class="remark"><input type="checkbox"><span>[% a<br>b %]</span></span>)</span>'], '']);
|
|
53
|
+
assert.deepStrictEqual(inspect(parser('({{\\\n}})')), [['<span class="paren">(<span class="template">{{\\\n}}</span>)</span>'], '']);
|
|
51
54
|
});
|
|
52
55
|
|
|
53
56
|
it('[', () => {
|
|
@@ -77,8 +80,6 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
77
80
|
assert.deepStrictEqual(inspect(parser('"(")"')), [['"', '(', '"'], ')"']);
|
|
78
81
|
assert.deepStrictEqual(inspect(parser('"(("')), [['"', '(', '(', '"'], '']);
|
|
79
82
|
assert.deepStrictEqual(inspect(parser('"(\\")"')), [['"', '<span class="paren">(")</span>', '"'], '']);
|
|
80
|
-
assert.deepStrictEqual(inspect(parser('"(\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
|
|
81
|
-
assert.deepStrictEqual(inspect(parser('"(\\\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BracketParser } from '../inline';
|
|
2
|
-
import { union, some, syntax, surround, lazy } from '../../combinator';
|
|
2
|
+
import { union, some, syntax, creation, surround, lazy } from '../../combinator';
|
|
3
3
|
import { inline } from '../inline';
|
|
4
4
|
import { str } from '../source';
|
|
5
5
|
import { Syntax, State } from '../context';
|
|
@@ -9,22 +9,21 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
9
9
|
const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
|
|
10
10
|
|
|
11
11
|
export const bracket: BracketParser = lazy(() => union([
|
|
12
|
-
surround(str('('), syntax(Syntax.none, 2,
|
|
13
|
-
surround(str('('), syntax(Syntax.bracket, 2,
|
|
12
|
+
surround(str('('), creation(syntax(Syntax.none, 2, State.none, str(index))), str(')')),
|
|
13
|
+
surround(str('('), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ')', [[/^\\?\n/, 3], [')', 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(as, bs), rest]),
|
|
16
|
-
surround(str('('), syntax(Syntax.none, 2,
|
|
17
|
-
surround(str('('), syntax(Syntax.bracket, 2,
|
|
16
|
+
surround(str('('), creation(syntax(Syntax.none, 2, State.none, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0)))))), str(')')),
|
|
17
|
+
surround(str('('), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ')', [[/^\\?\n/, 3], [')', 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('['), syntax(Syntax.bracket, 2,
|
|
20
|
+
surround(str('['), creation(syntax(Syntax.bracket, 2, State.none, some(inline, ']', [[/^\\?\n/, 3], [']', 2]]))), str(']'), true,
|
|
21
21
|
undefined,
|
|
22
22
|
([as, bs = []], rest) => [unshift(as, bs), rest]),
|
|
23
|
-
surround(str('{'), syntax(Syntax.bracket, 2,
|
|
23
|
+
surround(str('{'), creation(syntax(Syntax.bracket, 2, State.none, some(inline, '}', [[/^\\?\n/, 3], ['}', 2]]))), str('}'), true,
|
|
24
24
|
undefined,
|
|
25
25
|
([as, bs = []], rest) => [unshift(as, bs), rest]),
|
|
26
|
-
|
|
27
|
-
surround(str('"'), syntax(Syntax.none, 3, 1, State.none, some(inline, '"', [['"', 3]])), str('"'), true,
|
|
26
|
+
surround(str('"'), creation(syntax(Syntax.none, 3, State.none, some(inline, '"', [[/^\\?\n/, 4], ['"', 3]]))), str('"'), true,
|
|
28
27
|
undefined,
|
|
29
28
|
([as, bs = []], rest) => [unshift(as, bs), rest]),
|
|
30
29
|
]));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DeletionParser } from '../inline';
|
|
2
|
-
import { union, some, syntax, surround, open, lazy } from '../../combinator';
|
|
2
|
+
import { union, some, syntax, creation, surround, open, lazy } from '../../combinator';
|
|
3
3
|
import { inline } from '../inline';
|
|
4
4
|
import { str } from '../source';
|
|
5
5
|
import { Syntax, State } from '../context';
|
|
@@ -7,13 +7,13 @@ import { blankWith } from '../visibility';
|
|
|
7
7
|
import { unshift } from 'spica/array';
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
10
|
-
export const deletion: DeletionParser = lazy(() => surround(
|
|
10
|
+
export const deletion: DeletionParser = lazy(() => creation(surround(
|
|
11
11
|
str('~~', '~'),
|
|
12
|
-
syntax(Syntax.none, 1,
|
|
12
|
+
syntax(Syntax.none, 1, State.none,
|
|
13
13
|
some(union([
|
|
14
14
|
some(inline, blankWith('\n', '~~')),
|
|
15
15
|
open('\n', some(inline, '~'), true),
|
|
16
16
|
]))),
|
|
17
17
|
str('~~'), false,
|
|
18
18
|
([, bs], rest) => [[html('del', defrag(bs))], rest],
|
|
19
|
-
([as, bs], rest) => [unshift(as, bs), rest]));
|
|
19
|
+
([as, bs], rest) => [unshift(as, bs), rest])));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EmphasisParser } from '../inline';
|
|
2
|
-
import { union, some, syntax, surround, open, lazy } from '../../combinator';
|
|
2
|
+
import { union, some, syntax, creation, surround, open, lazy } from '../../combinator';
|
|
3
3
|
import { inline } from '../inline';
|
|
4
4
|
import { emstrong } from './emstrong';
|
|
5
5
|
import { strong } from './strong';
|
|
@@ -9,9 +9,9 @@ import { startTight, blankWith } from '../visibility';
|
|
|
9
9
|
import { unshift } from 'spica/array';
|
|
10
10
|
import { html, defrag } from 'typed-dom/dom';
|
|
11
11
|
|
|
12
|
-
export const emphasis: EmphasisParser = lazy(() => surround(
|
|
12
|
+
export const emphasis: EmphasisParser = lazy(() => creation(surround(
|
|
13
13
|
str('*', '*'),
|
|
14
|
-
syntax(Syntax.none, 1,
|
|
14
|
+
syntax(Syntax.none, 1, State.none,
|
|
15
15
|
startTight(some(union([
|
|
16
16
|
strong,
|
|
17
17
|
some(inline, blankWith('*'), [[/^\\?\n/, 9]]),
|
|
@@ -23,4 +23,4 @@ export const emphasis: EmphasisParser = lazy(() => surround(
|
|
|
23
23
|
])))),
|
|
24
24
|
str('*'), false,
|
|
25
25
|
([, bs], rest) => [[html('em', defrag(bs))], rest],
|
|
26
|
-
([as, bs], rest) => [unshift(as, bs), rest]));
|
|
26
|
+
([as, bs], rest) => [unshift(as, bs), rest])));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EmStrongParser, EmphasisParser, StrongParser } from '../inline';
|
|
2
2
|
import { Result, IntermediateParser } from '../../combinator/data/parser';
|
|
3
|
-
import { union, syntax, some, surround, open, lazy, bind } from '../../combinator';
|
|
3
|
+
import { union, syntax, creation, some, surround, open, lazy, bind } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
5
|
import { strong } from './strong';
|
|
6
6
|
import { emphasis } from './emphasis';
|
|
@@ -27,9 +27,9 @@ const subemphasis: IntermediateParser<EmphasisParser> = lazy(() => some(union([
|
|
|
27
27
|
])),
|
|
28
28
|
])));
|
|
29
29
|
|
|
30
|
-
export const emstrong: EmStrongParser = lazy(() => surround(
|
|
30
|
+
export const emstrong: EmStrongParser = lazy(() => creation(surround(
|
|
31
31
|
str('***'),
|
|
32
|
-
syntax(Syntax.none, 1,
|
|
32
|
+
syntax(Syntax.none, 1, State.none,
|
|
33
33
|
startTight(some(union([
|
|
34
34
|
some(inline, blankWith('*'), [[/^\\?\n/, 9]]),
|
|
35
35
|
open(some(inline, '*', [[/^\\?\n/, 9]]), inline),
|
|
@@ -59,4 +59,4 @@ export const emstrong: EmStrongParser = lazy(() => surround(
|
|
|
59
59
|
}
|
|
60
60
|
assert(false);
|
|
61
61
|
},
|
|
62
|
-
([as, bs], rest) => [unshift(as, bs), rest]));
|
|
62
|
+
([as, bs], rest) => [unshift(as, bs), rest])));
|
|
@@ -20,6 +20,12 @@ describe('Unit: parser/inline/extension/index', () => {
|
|
|
20
20
|
assert.deepStrictEqual(inspect(parser('[#\\]')), undefined);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser('[#a')), undefined);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('[#*a\nb*]')), undefined);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('[#(a\nb)]')), undefined);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('[#"a\nb"]')), undefined);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser('[#<bdi>a\nb</bdi>]')), undefined);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser('[#[% a\nb %]]')), undefined);
|
|
27
|
+
assert.deepStrictEqual(inspect(parser('[#{{ a\nb }}]')), undefined);
|
|
28
|
+
assert.deepStrictEqual(inspect(parser('[#({{ a\nb }})]')), undefined);
|
|
23
29
|
assert.deepStrictEqual(inspect(parser('[#a\n|b]')), undefined);
|
|
24
30
|
assert.deepStrictEqual(inspect(parser('[#a|\n]')), undefined);
|
|
25
31
|
assert.deepStrictEqual(inspect(parser('[#a|\\\n]')), undefined);
|