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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.293.5
4
+
5
+ - Refactoring.
6
+
3
7
  ## 0.293.4
4
8
 
5
9
  - Refactoring.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.293.4 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
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
- }) => apply(parser, context, changes, values, true);
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)((0, segment_1.validate)(source, segment_1.MAX_INPUT_SIZE) ? source : source.slice(0, segment_1.MAX_INPUT_SIZE + 1));
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(`[${[...new Set(unreadableEscapableCharacters)].join('')}]`, 'g');
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, util_1.linearize)((0, combinator_1.some)(inline_1.inline), 1)))])), ns => [(0, dom_1.html)('p', (0, visibility_1.trimBlankNodeEnd)((0, dom_1.defrag)(ns)))])));
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, util_1.linearize)((0, combinator_1.convert)(source => source.replace(/(?<=^>+[^\S\n])/mg, '\r'), (0, combinator_1.some)((0, combinator_1.union)([
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), -1)), ns => [(0, dom_1.html)('span', {
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)), (0, dom_1.html)('br')]), false));
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[^\S\n]*(?=\S)/y, (0, combinator_1.inits)([(0, combinator_1.rewrite)(({
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)(/---[^\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}---[^\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', {
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 offset = 0;
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
- offset = 0;
8279
+ index = i;
8273
8280
  continue;
8274
8281
  }
8275
8282
  break;
8276
8283
  }
8277
- if (index === position + 1 && offset === 0 && isAlphanumeric(source[index - 1])) {
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 offset = 0;
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
- offset = 0;
8302
+ index = i;
8302
8303
  continue;
8303
8304
  }
8304
8305
  break;
8305
8306
  }
8306
- if (index === position + 1 && offset === 0 && isAlphanumeric(source[index - 1])) {
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 = exports.linearize = void 0;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.293.4",
3
+ "version": "0.293.5",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -157,8 +157,8 @@ export function setBacktrack(
157
157
  position: number,
158
158
  length: number = 1,
159
159
  ): void {
160
- const { source, state = 0 } = context;
161
- if (state === 0) return;
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 === 0);
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: 1, recursions: [1] } };
31
- assert.deepStrictEqual(reset(base, parser)(input('1', ctx)), [[1]]);
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 === 0);
34
- assert.throws(() => reset(base, parser)(input('1', ctx)));
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: 3, recursions: [1] } };
50
- assert.deepStrictEqual(context(base, parser)(input('123', ctx)), [[true, true, true]]);
51
- assert(ctx.resources?.clock === 0);
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(() => reset(base, parser)(input('1', ctx)));
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
- apply(parser, context, changes, values, true);
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;
@@ -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, validate, MAX_INPUT_SIZE } from '../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(validate(source, MAX_INPUT_SIZE) ? source : source.slice(0, MAX_INPUT_SIZE + 1));
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
- linearize(convert(
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), -1)),
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
- html('br'),
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(linearize(some(inline), 1))),
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('----\na: b\n----'), ctx), [['<pre class="invalid" translate="no">----\na: b\n----</pre>'], '']);
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
  });
@@ -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[^\S\n]*(?=\S)/y,
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(/---[^\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}---[^\S\v\f\r\n]*(?:$|\r?\n)/y,
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 offset = 0;
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
- offset = 0;
150
+ index = i;
154
151
  continue;
155
152
  }
156
153
  break;
157
154
  }
158
- if (index === position + 1 && offset === 0 && isAlphanumeric(source[index - 1])) {
159
- return delim;
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 offset = 0;
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
- offset = 0;
174
+ index = i;
182
175
  continue;
183
176
  }
184
177
  break;
185
178
  }
186
- if (index === position + 1 && offset === 0 && isAlphanumeric(source[index - 1])) {
187
- return delim;
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);
@@ -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 = [];