securemark 0.293.4 → 0.293.5
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 +4 -0
- package/dist/index.js +32 -39
- package/package.json +1 -1
- package/src/combinator/control/manipulation/surround.ts +2 -2
- package/src/combinator/data/parser/context.test.ts +10 -9
- package/src/combinator/data/parser/context.ts +2 -1
- package/src/parser/api/bind.ts +2 -2
- package/src/parser/api/normalize.ts +1 -3
- package/src/parser/block/reply/quote.ts +6 -6
- package/src/parser/block/reply.ts +3 -2
- package/src/parser/header.test.ts +3 -1
- package/src/parser/header.ts +2 -2
- package/src/parser/source/text.ts +10 -18
- package/src/parser/util.ts +0 -19
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! securemark v0.293.
|
|
1
|
+
/*! securemark v0.293.5 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
|
|
2
2
|
(function webpackUniversalModuleDefinition(root, factory) {
|
|
3
3
|
if(typeof exports === 'object' && typeof module === 'object')
|
|
4
4
|
module.exports = factory(require("Prism"), require("DOMPurify"));
|
|
@@ -3073,11 +3073,10 @@ function isBacktrack(context, backtracks, position = context.position, length =
|
|
|
3073
3073
|
}
|
|
3074
3074
|
exports.isBacktrack = isBacktrack;
|
|
3075
3075
|
function setBacktrack(context, backtracks, position, length = 1) {
|
|
3076
|
+
// 以降バックトラックの可能性がなく記録不要の場合もあるが判別が面倒なので省略
|
|
3076
3077
|
const {
|
|
3077
|
-
source
|
|
3078
|
-
state = 0
|
|
3078
|
+
source
|
|
3079
3079
|
} = context;
|
|
3080
|
-
if (state === 0) return;
|
|
3081
3080
|
if (position === source.length) return;
|
|
3082
3081
|
if (length === 0) return;
|
|
3083
3082
|
for (const backtrack of backtracks) {
|
|
@@ -3250,7 +3249,11 @@ function reset(base, parser) {
|
|
|
3250
3249
|
const values = Array(changes.length);
|
|
3251
3250
|
return ({
|
|
3252
3251
|
context
|
|
3253
|
-
}) =>
|
|
3252
|
+
}) =>
|
|
3253
|
+
// 大域離脱時の汚染回避のため複製
|
|
3254
|
+
apply(parser, {
|
|
3255
|
+
...context
|
|
3256
|
+
}, changes, values, true);
|
|
3254
3257
|
}
|
|
3255
3258
|
exports.reset = reset;
|
|
3256
3259
|
function context(base, parser) {
|
|
@@ -3917,7 +3920,7 @@ function bind(target, settings) {
|
|
|
3917
3920
|
function* parse(source) {
|
|
3918
3921
|
if (settings.chunk && revision) throw new Error('Chunks cannot be updated');
|
|
3919
3922
|
const url = (0, header_2.headers)(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
3920
|
-
source = (0, normalize_1.normalize)(
|
|
3923
|
+
source = (0, normalize_1.normalize)(source);
|
|
3921
3924
|
// Change the object identity.
|
|
3922
3925
|
context = {
|
|
3923
3926
|
...context,
|
|
@@ -4165,7 +4168,7 @@ function sanitize(source) {
|
|
|
4165
4168
|
exports.invisibleHTMLEntityNames = ['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'shy', 'ensp', 'emsp', 'emsp13', 'emsp14', 'numsp', 'puncsp', 'ThinSpace', 'thinsp', 'VeryThinSpace', 'hairsp', 'ZeroWidthSpace', 'NegativeVeryThinSpace', 'NegativeThinSpace', 'NegativeMediumSpace', 'NegativeThickSpace', 'zwj', 'zwnj', 'lrm', 'rlm', 'MediumSpace', 'NoBreak', 'ApplyFunction', 'af', 'InvisibleTimes', 'it', 'InvisibleComma', 'ic'];
|
|
4166
4169
|
const unreadableHTMLEntityNames = exports.invisibleHTMLEntityNames.slice(2);
|
|
4167
4170
|
const unreadableEscapableCharacters = unreadableHTMLEntityNames.map(name => (0, parser_1.eval)((0, htmlentity_1.unsafehtmlentity)((0, parser_1.input)(`&${name};`, {})))[0]);
|
|
4168
|
-
const unreadableEscapableCharacter = new RegExp(`[${
|
|
4171
|
+
const unreadableEscapableCharacter = new RegExp(`[${unreadableEscapableCharacters.join('')}]`, 'g');
|
|
4169
4172
|
// https://www.pandanoir.info/entry/2018/03/11/193000
|
|
4170
4173
|
// http://anti.rosx.net/etc/memo/002_space.html
|
|
4171
4174
|
// http://nicowiki.com/%E7%A9%BA%E7%99%BD%E3%83%BB%E7%89%B9%E6%AE%8A%E8%A8%98%E5%8F%B7.html
|
|
@@ -5447,11 +5450,14 @@ const cite_1 = __webpack_require__(1200);
|
|
|
5447
5450
|
const quote_1 = __webpack_require__(4847);
|
|
5448
5451
|
const inline_1 = __webpack_require__(7973);
|
|
5449
5452
|
const source_1 = __webpack_require__(8745);
|
|
5450
|
-
const util_1 = __webpack_require__(4992);
|
|
5451
5453
|
const visibility_1 = __webpack_require__(6364);
|
|
5454
|
+
const array_1 = __webpack_require__(6876);
|
|
5452
5455
|
const dom_1 = __webpack_require__(394);
|
|
5453
5456
|
const delimiter = new RegExp(`${cite_1.syntax.source}|${quote_1.syntax.source}`, 'y');
|
|
5454
|
-
exports.reply = (0, combinator_1.block)((0, combinator_1.validate)(cite_1.syntax, (0, combinator_1.fmap)((0, combinator_1.some)((0, combinator_1.union)([cite_1.cite, quote_1.quote, (0, combinator_1.rewrite)((0, combinator_1.some)(source_1.anyline, delimiter), (0, visibility_1.visualize)((0,
|
|
5457
|
+
exports.reply = (0, combinator_1.block)((0, combinator_1.validate)(cite_1.syntax, (0, combinator_1.fmap)((0, combinator_1.some)((0, combinator_1.union)([cite_1.cite, quote_1.quote, (0, combinator_1.rewrite)((0, combinator_1.some)(source_1.anyline, delimiter), (0, visibility_1.visualize)((0, combinator_1.fmap)((0, combinator_1.some)(inline_1.inline), (ns, {
|
|
5458
|
+
source,
|
|
5459
|
+
position
|
|
5460
|
+
}) => source[position - 1] === '\n' ? ns : (0, array_1.push)(ns, [(0, dom_1.html)('br')]))))])), ns => [(0, dom_1.html)('p', (0, visibility_1.trimBlankNodeEnd)((0, dom_1.defrag)(ns)))])));
|
|
5455
5461
|
|
|
5456
5462
|
/***/ },
|
|
5457
5463
|
|
|
@@ -5517,14 +5523,18 @@ const combinator_1 = __webpack_require__(3484);
|
|
|
5517
5523
|
const math_1 = __webpack_require__(2962);
|
|
5518
5524
|
const autolink_1 = __webpack_require__(8072);
|
|
5519
5525
|
const source_1 = __webpack_require__(8745);
|
|
5520
|
-
const util_1 = __webpack_require__(4992);
|
|
5521
5526
|
const dom_1 = __webpack_require__(394);
|
|
5522
5527
|
exports.syntax = />+[^\S\n]/y;
|
|
5523
|
-
exports.quote = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.some)((0, combinator_1.validate)(exports.syntax, source_1.anyline)), (0,
|
|
5528
|
+
exports.quote = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.some)((0, combinator_1.validate)(exports.syntax, source_1.anyline)), (0, combinator_1.convert)(
|
|
5529
|
+
// TODO: インデント数を渡してインデント数前の行頭確認を行う実装に置き換える
|
|
5530
|
+
source => source.replace(/(?<=^>+[^\S\n])/mg, '\r'), (0, combinator_1.some)((0, combinator_1.union)([
|
|
5524
5531
|
// quote補助関数が残した数式をパースする。
|
|
5525
|
-
math_1.math, autolink_1.autolink, source_1.linebreak, source_1.unescsource])), false)
|
|
5532
|
+
math_1.math, autolink_1.autolink, source_1.linebreak, source_1.unescsource])), false)), (ns, {
|
|
5533
|
+
source,
|
|
5534
|
+
position
|
|
5535
|
+
}) => [source[position - 1] === '\n' ? ns.pop() : (0, dom_1.html)('br'), (0, dom_1.html)('span', {
|
|
5526
5536
|
class: 'quote'
|
|
5527
|
-
}, (0, dom_1.defrag)(ns))
|
|
5537
|
+
}, (0, dom_1.defrag)(ns))].reverse()), false));
|
|
5528
5538
|
|
|
5529
5539
|
/***/ },
|
|
5530
5540
|
|
|
@@ -5665,7 +5675,7 @@ const source_1 = __webpack_require__(8745);
|
|
|
5665
5675
|
const util_1 = __webpack_require__(4992);
|
|
5666
5676
|
const normalize_1 = __webpack_require__(4490);
|
|
5667
5677
|
const dom_1 = __webpack_require__(394);
|
|
5668
|
-
exports.header = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(/---+[^\S\v\f\r\n]*\r?\n
|
|
5678
|
+
exports.header = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(/---+[^\S\v\f\r\n]*\r?\n(?=\S)/y, (0, combinator_1.inits)([(0, combinator_1.rewrite)(({
|
|
5669
5679
|
context
|
|
5670
5680
|
}) => {
|
|
5671
5681
|
const {
|
|
@@ -5679,7 +5689,7 @@ exports.header = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(/---+[^
|
|
|
5679
5689
|
return [[]];
|
|
5680
5690
|
}, (0, combinator_1.block)((0, combinator_1.union)([(0, combinator_1.validate)(({
|
|
5681
5691
|
context
|
|
5682
|
-
}) => context.header ?? true, (0, combinator_1.focus)(
|
|
5692
|
+
}) => context.header ?? true, (0, combinator_1.focus)(/(---+)[^\S\v\f\r\n]*\r?\n(?:[A-Za-z][0-9A-Za-z]*(?:-[A-Za-z][0-9A-Za-z]*)*:[ \t]+\S[^\v\f\r\n]*\r?\n){1,100}\1[^\S\v\f\r\n]*(?:$|\r?\n)/y, (0, combinator_1.convert)(source => (0, normalize_1.normalize)(source.slice(source.indexOf('\n') + 1, source.trimEnd().lastIndexOf('\n'))).replace(/(\S)\s+$/mg, '$1'), (0, combinator_1.fmap)((0, combinator_1.some)((0, combinator_1.union)([field])), es => [(0, dom_1.html)('aside', {
|
|
5683
5693
|
class: 'header'
|
|
5684
5694
|
}, [(0, dom_1.html)('details', {
|
|
5685
5695
|
open: ''
|
|
@@ -8255,37 +8265,29 @@ exports.backToWhitespace = backToWhitespace;
|
|
|
8255
8265
|
function backToUrlHead(source, position, index) {
|
|
8256
8266
|
const delim = index;
|
|
8257
8267
|
let state = false;
|
|
8258
|
-
let
|
|
8259
|
-
for (let i = index; --i > position;) {
|
|
8260
|
-
index = i;
|
|
8268
|
+
for (let i = index - 1; i >= position; --i) {
|
|
8261
8269
|
const char = source[i];
|
|
8262
8270
|
if (state) switch (char) {
|
|
8263
8271
|
case '.':
|
|
8264
8272
|
case '+':
|
|
8265
8273
|
case '-':
|
|
8266
8274
|
state = false;
|
|
8267
|
-
offset = 1;
|
|
8268
8275
|
continue;
|
|
8269
8276
|
}
|
|
8270
8277
|
if (isAlphanumeric(char)) {
|
|
8271
8278
|
state = true;
|
|
8272
|
-
|
|
8279
|
+
index = i;
|
|
8273
8280
|
continue;
|
|
8274
8281
|
}
|
|
8275
8282
|
break;
|
|
8276
8283
|
}
|
|
8277
|
-
|
|
8278
|
-
return delim;
|
|
8279
|
-
}
|
|
8280
|
-
return index + offset;
|
|
8284
|
+
return index === position ? delim : index;
|
|
8281
8285
|
}
|
|
8282
8286
|
exports.backToUrlHead = backToUrlHead;
|
|
8283
8287
|
function backToEmailHead(source, position, index) {
|
|
8284
8288
|
const delim = index;
|
|
8285
8289
|
let state = false;
|
|
8286
|
-
let
|
|
8287
|
-
for (let i = index; --i > position;) {
|
|
8288
|
-
index = i;
|
|
8290
|
+
for (let i = index - 1; i >= position; --i) {
|
|
8289
8291
|
const char = source[i];
|
|
8290
8292
|
if (state) switch (char) {
|
|
8291
8293
|
case '_':
|
|
@@ -8293,20 +8295,16 @@ function backToEmailHead(source, position, index) {
|
|
|
8293
8295
|
case '+':
|
|
8294
8296
|
case '-':
|
|
8295
8297
|
state = false;
|
|
8296
|
-
offset = 1;
|
|
8297
8298
|
continue;
|
|
8298
8299
|
}
|
|
8299
8300
|
if (isAlphanumeric(char)) {
|
|
8300
8301
|
state = true;
|
|
8301
|
-
|
|
8302
|
+
index = i;
|
|
8302
8303
|
continue;
|
|
8303
8304
|
}
|
|
8304
8305
|
break;
|
|
8305
8306
|
}
|
|
8306
|
-
|
|
8307
|
-
return delim;
|
|
8308
|
-
}
|
|
8309
|
-
return index + offset;
|
|
8307
|
+
return index === position ? delim : index;
|
|
8310
8308
|
}
|
|
8311
8309
|
exports.backToEmailHead = backToEmailHead;
|
|
8312
8310
|
function isAlphanumeric(char) {
|
|
@@ -8484,15 +8482,10 @@ exports.unescsource = unescsource;
|
|
|
8484
8482
|
Object.defineProperty(exports, "__esModule", ({
|
|
8485
8483
|
value: true
|
|
8486
8484
|
}));
|
|
8487
|
-
exports.stringify = exports.unmarkInvalid = exports.markInvalid = exports.invalid = exports.repeat =
|
|
8485
|
+
exports.stringify = exports.unmarkInvalid = exports.markInvalid = exports.invalid = exports.repeat = void 0;
|
|
8488
8486
|
const alias_1 = __webpack_require__(5413);
|
|
8489
8487
|
const parser_1 = __webpack_require__(605);
|
|
8490
|
-
const combinator_1 = __webpack_require__(3484);
|
|
8491
8488
|
const dom_1 = __webpack_require__(394);
|
|
8492
|
-
function linearize(parser, trim = 0) {
|
|
8493
|
-
return (0, combinator_1.convert)(source => `${trim === 0 ? source : trim > 0 ? source.at(-1) === '\n' ? source : source + '\n' : source.at(-1) === '\n' ? source.slice(0, -1) : source}`, parser, trim === 0);
|
|
8494
|
-
}
|
|
8495
|
-
exports.linearize = linearize;
|
|
8496
8489
|
function repeat(symbol, parser, cons, termination = (nodes, context, prefix, postfix) => {
|
|
8497
8490
|
const acc = [];
|
|
8498
8491
|
if (prefix > 0) {
|
package/package.json
CHANGED
|
@@ -157,8 +157,8 @@ export function setBacktrack(
|
|
|
157
157
|
position: number,
|
|
158
158
|
length: number = 1,
|
|
159
159
|
): void {
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
// 以降バックトラックの可能性がなく記録不要の場合もあるが判別が面倒なので省略
|
|
161
|
+
const { source } = context;
|
|
162
162
|
if (position === source.length) return;
|
|
163
163
|
if (length === 0) return;
|
|
164
164
|
for (const backtrack of backtracks) {
|
|
@@ -22,16 +22,16 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
22
22
|
assert(base.resources?.clock === 3);
|
|
23
23
|
assert(ctx.resources?.clock === undefined);
|
|
24
24
|
assert.throws(() => reset(base, parser)(input('1234', ctx)));
|
|
25
|
-
assert(ctx.resources?.clock ===
|
|
25
|
+
assert(ctx.resources?.clock === undefined);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
it('node', () => {
|
|
29
29
|
const base: Context = { resources: { clock: 3, recursions: [1] } };
|
|
30
|
-
const ctx: Context = { resources: { clock:
|
|
31
|
-
assert.deepStrictEqual(reset(base, parser)(input('1', ctx)), [[
|
|
30
|
+
const ctx: Context = { resources: { clock: 2, recursions: [1] } };
|
|
31
|
+
assert.deepStrictEqual(reset(base, parser)(input('1', ctx)), [[2]]);
|
|
32
32
|
assert(base.resources?.clock === 3);
|
|
33
|
-
assert(ctx.resources?.clock ===
|
|
34
|
-
assert.throws(() => reset(base, parser)(input('
|
|
33
|
+
assert(ctx.resources?.clock === 1);
|
|
34
|
+
assert.throws(() => reset(base, parser)(input('12', ctx)));
|
|
35
35
|
assert(ctx.resources?.clock === 0);
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -46,11 +46,12 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
46
46
|
|
|
47
47
|
it('', () => {
|
|
48
48
|
const base: Context = { status: true };
|
|
49
|
-
const ctx: Context = { resources: { clock:
|
|
50
|
-
assert.deepStrictEqual(context(base, parser)(input('
|
|
51
|
-
assert(
|
|
49
|
+
const ctx: Context = { resources: { clock: 2, recursions: [1] } };
|
|
50
|
+
assert.deepStrictEqual(context(base, parser)(input('1', ctx)), [[true]]);
|
|
51
|
+
assert(base.resources?.clock === undefined);
|
|
52
|
+
assert(ctx.resources?.clock === 1);
|
|
52
53
|
assert(ctx.status === undefined);
|
|
53
|
-
assert.throws(() =>
|
|
54
|
+
assert.throws(() => context(base, parser)(input('12', ctx)));
|
|
54
55
|
assert(ctx.resources?.clock === 0);
|
|
55
56
|
assert(ctx.status === true);
|
|
56
57
|
});
|
|
@@ -9,7 +9,8 @@ export function reset<N>(base: Ctx, parser: Parser<N>): Parser<N> {
|
|
|
9
9
|
const changes = Object.entries(base);
|
|
10
10
|
const values = Array(changes.length);
|
|
11
11
|
return ({ context }) =>
|
|
12
|
-
|
|
12
|
+
// 大域離脱時の汚染回避のため複製
|
|
13
|
+
apply(parser, { ...context }, changes, values, true);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function context<P extends Parser<unknown>>(base: CtxOptions, parser: P): P;
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserSettings, Progress } from '../../..';
|
|
2
2
|
import { MarkdownParser } from '../../../markdown';
|
|
3
3
|
import { input, eval } from '../../combinator/data/parser';
|
|
4
|
-
import { segment
|
|
4
|
+
import { segment } from '../segment';
|
|
5
5
|
import { header } from '../header';
|
|
6
6
|
import { block } from '../block';
|
|
7
7
|
import { normalize } from './normalize';
|
|
@@ -44,7 +44,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
44
44
|
function* parse(source: string): Generator<Progress, undefined, undefined> {
|
|
45
45
|
if (settings.chunk && revision) throw new Error('Chunks cannot be updated');
|
|
46
46
|
const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
47
|
-
source = normalize(
|
|
47
|
+
source = normalize(source);
|
|
48
48
|
// Change the object identity.
|
|
49
49
|
context = {
|
|
50
50
|
...context,
|
|
@@ -63,9 +63,7 @@ const unreadableEscapableCharacters = unreadableHTMLEntityNames
|
|
|
63
63
|
.map(name => eval(unsafehtmlentity(input(`&${name};`, {})))![0]);
|
|
64
64
|
assert(unreadableEscapableCharacters.length === unreadableHTMLEntityNames.length);
|
|
65
65
|
assert(unreadableEscapableCharacters.every(c => c.length === 1));
|
|
66
|
-
const unreadableEscapableCharacter = new RegExp(`[${
|
|
67
|
-
[...new Set<string>(unreadableEscapableCharacters)].join('')
|
|
68
|
-
}]`, 'g');
|
|
66
|
+
const unreadableEscapableCharacter = new RegExp(`[${unreadableEscapableCharacters.join('')}]`, 'g');
|
|
69
67
|
assert(!unreadableEscapableCharacter.source.includes('&'));
|
|
70
68
|
|
|
71
69
|
// https://www.pandanoir.info/entry/2018/03/11/193000
|
|
@@ -3,7 +3,6 @@ import { union, some, block, validate, rewrite, convert, lazy, fmap } from '../.
|
|
|
3
3
|
import { math } from '../../inline/math';
|
|
4
4
|
import { autolink } from '../../inline/autolink';
|
|
5
5
|
import { linebreak, unescsource, anyline } from '../../source';
|
|
6
|
-
import { linearize } from '../../util';
|
|
7
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
8
7
|
|
|
9
8
|
export const syntax = />+[^\S\n]/y;
|
|
@@ -11,7 +10,8 @@ export const syntax = />+[^\S\n]/y;
|
|
|
11
10
|
export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
|
|
12
11
|
rewrite(
|
|
13
12
|
some(validate(syntax, anyline)),
|
|
14
|
-
|
|
13
|
+
convert(
|
|
14
|
+
// TODO: インデント数を渡してインデント数前の行頭確認を行う実装に置き換える
|
|
15
15
|
source => source.replace(/(?<=^>+[^\S\n])/mg, '\r'),
|
|
16
16
|
some(union([
|
|
17
17
|
// quote補助関数が残した数式をパースする。
|
|
@@ -20,9 +20,9 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
|
|
|
20
20
|
linebreak,
|
|
21
21
|
unescsource,
|
|
22
22
|
])),
|
|
23
|
-
false)
|
|
24
|
-
(ns: [string, ...(string | HTMLElement)[]]) => [
|
|
23
|
+
false)),
|
|
24
|
+
(ns: [string, ...(string | HTMLElement)[]], { source, position }) => [
|
|
25
|
+
source[position - 1] === '\n' ? ns.pop() as HTMLBRElement : html('br'),
|
|
25
26
|
html('span', { class: 'quote' }, defrag(ns)),
|
|
26
|
-
|
|
27
|
-
]),
|
|
27
|
+
].reverse()),
|
|
28
28
|
false));
|
|
@@ -4,8 +4,8 @@ import { cite, syntax as csyntax } from './reply/cite';
|
|
|
4
4
|
import { quote, syntax as qsyntax } from './reply/quote';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
6
|
import { anyline } from '../source';
|
|
7
|
-
import { linearize } from '../util';
|
|
8
7
|
import { visualize, trimBlankNodeEnd } from '../visibility';
|
|
8
|
+
import { push } from 'spica/array';
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
11
11
|
const delimiter = new RegExp(`${csyntax.source}|${qsyntax.source}`, 'y');
|
|
@@ -16,6 +16,7 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
|
|
|
16
16
|
quote,
|
|
17
17
|
rewrite(
|
|
18
18
|
some(anyline, delimiter),
|
|
19
|
-
visualize(
|
|
19
|
+
visualize(fmap(some(inline), (ns, { source, position }) =>
|
|
20
|
+
source[position - 1] === '\n' ? ns : push(ns, [html('br')])))),
|
|
20
21
|
])),
|
|
21
22
|
ns => [html('p', trimBlankNodeEnd(defrag(ns)))])));
|
|
@@ -21,7 +21,8 @@ describe('Unit: parser/header', () => {
|
|
|
21
21
|
assert.deepStrictEqual(inspect(parser('---\n\n---'), ctx), undefined);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('---\n \n---'), ctx), undefined);
|
|
23
23
|
assert.deepStrictEqual(inspect(parser('---\n-\n---'), ctx), [['<pre class="invalid" translate="no">---\n-\n---</pre>'], '']);
|
|
24
|
-
assert.deepStrictEqual(inspect(parser('
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('---\na: b\n----'), ctx), [['<pre class="invalid" translate="no">---\na: b\n----</pre>'], '']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser('----\na: b\n---'), ctx), [['<pre class="invalid" translate="no">----\na: b\n---</pre>'], '']);
|
|
25
26
|
assert.deepStrictEqual(inspect(parser(`---\n${'a: b\n'.repeat(101)}---`), ctx), [[`<pre class="invalid" translate="no">---\n${'a: b\n'.repeat(101)}---</pre>`], '']);
|
|
26
27
|
});
|
|
27
28
|
|
|
@@ -30,6 +31,7 @@ describe('Unit: parser/header', () => {
|
|
|
30
31
|
assert.deepStrictEqual(inspect(parser('---\na: b\n---\n'), ctx), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], '']);
|
|
31
32
|
assert.deepStrictEqual(inspect(parser('---\na: b\nC: D e\n---\n'), ctx), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span><span class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span>\n</span></details></aside>'], '']);
|
|
32
33
|
assert.deepStrictEqual(inspect(parser('--- \r\na: b \r\n--- \r\n \r\n \r\na'), ctx), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], ' \r\na']);
|
|
34
|
+
assert.deepStrictEqual(inspect(parser('----\na: b\n----'), ctx), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], '']);
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
});
|
package/src/parser/header.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { normalize } from './api/normalize';
|
|
|
7
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
8
8
|
|
|
9
9
|
export const header: MarkdownParser.HeaderParser = lazy(() => validate(
|
|
10
|
-
/---+[^\S\v\f\r\n]*\r?\n
|
|
10
|
+
/---+[^\S\v\f\r\n]*\r?\n(?=\S)/y,
|
|
11
11
|
inits([
|
|
12
12
|
rewrite(
|
|
13
13
|
({ context }) => {
|
|
@@ -23,7 +23,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
|
|
|
23
23
|
block(
|
|
24
24
|
union([
|
|
25
25
|
validate(({ context }) => context.header ?? true,
|
|
26
|
-
focus(
|
|
26
|
+
focus(/(---+)[^\S\v\f\r\n]*\r?\n(?:[A-Za-z][0-9A-Za-z]*(?:-[A-Za-z][0-9A-Za-z]*)*:[ \t]+\S[^\v\f\r\n]*\r?\n){1,100}\1[^\S\v\f\r\n]*(?:$|\r?\n)/y,
|
|
27
27
|
convert(source =>
|
|
28
28
|
normalize(source.slice(source.indexOf('\n') + 1, source.trimEnd().lastIndexOf('\n'))).replace(/(\S)\s+$/mg, '$1'),
|
|
29
29
|
fmap(
|
|
@@ -136,36 +136,30 @@ export function backToWhitespace(source: string, position: number, index: number
|
|
|
136
136
|
export function backToUrlHead(source: string, position: number, index: number): number {
|
|
137
137
|
const delim = index;
|
|
138
138
|
let state = false;
|
|
139
|
-
let
|
|
140
|
-
for (let i = index; --i > position;) {
|
|
141
|
-
index = i;
|
|
139
|
+
for (let i = index - 1; i >= position; --i) {
|
|
142
140
|
const char = source[i];
|
|
143
141
|
if (state) switch (char) {
|
|
144
142
|
case '.':
|
|
145
143
|
case '+':
|
|
146
144
|
case '-':
|
|
147
145
|
state = false;
|
|
148
|
-
offset = 1;
|
|
149
146
|
continue;
|
|
150
147
|
}
|
|
151
148
|
if (isAlphanumeric(char)) {
|
|
152
149
|
state = true;
|
|
153
|
-
|
|
150
|
+
index = i;
|
|
154
151
|
continue;
|
|
155
152
|
}
|
|
156
153
|
break;
|
|
157
154
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return index + offset;
|
|
155
|
+
return index === position
|
|
156
|
+
? delim
|
|
157
|
+
: index;
|
|
162
158
|
}
|
|
163
159
|
export function backToEmailHead(source: string, position: number, index: number): number {
|
|
164
160
|
const delim = index;
|
|
165
161
|
let state = false;
|
|
166
|
-
let
|
|
167
|
-
for (let i = index; --i > position;) {
|
|
168
|
-
index = i;
|
|
162
|
+
for (let i = index - 1; i >= position; --i) {
|
|
169
163
|
const char = source[i];
|
|
170
164
|
if (state) switch (char) {
|
|
171
165
|
case '_':
|
|
@@ -173,20 +167,18 @@ export function backToEmailHead(source: string, position: number, index: number)
|
|
|
173
167
|
case '+':
|
|
174
168
|
case '-':
|
|
175
169
|
state = false;
|
|
176
|
-
offset = 1;
|
|
177
170
|
continue;
|
|
178
171
|
}
|
|
179
172
|
if (isAlphanumeric(char)) {
|
|
180
173
|
state = true;
|
|
181
|
-
|
|
174
|
+
index = i;
|
|
182
175
|
continue;
|
|
183
176
|
}
|
|
184
177
|
break;
|
|
185
178
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return index + offset;
|
|
179
|
+
return index === position
|
|
180
|
+
? delim
|
|
181
|
+
: index;
|
|
190
182
|
}
|
|
191
183
|
function isAlphanumeric(char: string): boolean {
|
|
192
184
|
assert(char.length === 1);
|
package/src/parser/util.ts
CHANGED
|
@@ -2,27 +2,8 @@ import { min } from 'spica/alias';
|
|
|
2
2
|
import { MarkdownParser } from '../../markdown';
|
|
3
3
|
import { Command } from './context';
|
|
4
4
|
import { Parser, Result, Ctx, Node, Context, eval, failsafe } from '../combinator/data/parser';
|
|
5
|
-
import { convert } from '../combinator';
|
|
6
5
|
import { define } from 'typed-dom/dom';
|
|
7
6
|
|
|
8
|
-
export function linearize<P extends Parser<HTMLElement | string>>(parser: P, trim?: 0 | 1 | -1): P;
|
|
9
|
-
export function linearize<N extends HTMLElement | string>(parser: Parser<N>, trim = 0): Parser<N> {
|
|
10
|
-
return convert(
|
|
11
|
-
source => `${
|
|
12
|
-
trim === 0
|
|
13
|
-
? source
|
|
14
|
-
: trim > 0
|
|
15
|
-
? source.at(-1) === '\n'
|
|
16
|
-
? source
|
|
17
|
-
: source + '\n'
|
|
18
|
-
: source.at(-1) === '\n'
|
|
19
|
-
? source.slice(0, -1)
|
|
20
|
-
: source
|
|
21
|
-
}`,
|
|
22
|
-
parser,
|
|
23
|
-
trim === 0);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
7
|
export function repeat<P extends Parser<HTMLElement | string, MarkdownParser.Context>>(symbol: string, parser: P, cons: (nodes: Node<P>[], context: Context<P>) => Node<P>[], termination?: (acc: Node<P>[], context: Ctx, prefix: number, postfix: number, state: boolean) => Result<string | Node<P>>): P;
|
|
27
8
|
export function repeat<N extends HTMLElement | string>(symbol: string, parser: Parser<N>, cons: (nodes: N[], context: MarkdownParser.Context) => N[], termination: (acc: N[], context: Ctx, prefix: number, postfix: number, state: boolean) => Result<string | N, MarkdownParser.Context> = (nodes, context, prefix, postfix) => {
|
|
28
9
|
const acc = [];
|