securemark 0.254.2 → 0.256.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.256.0
4
+
5
+ - Decrease the buget size to 50,000.
6
+
7
+ ## 0.255.1
8
+
9
+ - Refactoring.
10
+
11
+ ## 0.255.0
12
+
13
+ - Change html parser not to use backtracking.
14
+
3
15
  ## 0.254.2
4
16
 
5
17
  - Refactoring.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.254.2 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.256.0 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("DOMPurify"), require("Prism"));
@@ -2009,22 +2009,13 @@ const alias_1 = __webpack_require__(5406);
2009
2009
 
2010
2010
  const parser_1 = __webpack_require__(6728);
2011
2011
 
2012
- function validate(patterns, has, end, parser) {
2013
- if (typeof has === 'function') return validate(patterns, '', '', has);
2014
- if (typeof end === 'function') return validate(patterns, has, '', end);
2015
- if (!(0, alias_1.isArray)(patterns)) return validate([patterns], has, end, parser);
2012
+ function validate(patterns, has, parser) {
2013
+ if (typeof has === 'function') return validate(patterns, '', has);
2014
+ if (!(0, alias_1.isArray)(patterns)) return validate([patterns], has, parser);
2016
2015
  const match = (0, global_1.Function)(['"use strict";', 'return source =>', '0', ...patterns.map(pattern => typeof pattern === 'string' ? `|| source.slice(0, ${pattern.length}) === '${pattern}'` : `|| /${pattern.source}/${pattern.flags}.test(source)`)].join(''))();
2017
-
2018
- const match2 = source => {
2019
- if (!has) return true;
2020
- const i = end ? source.indexOf(end, 1) : -1;
2021
- return i !== -1 ? source.slice(0, i).indexOf(has, 1) !== -1 : source.indexOf(has, 1) !== -1;
2022
- };
2023
-
2024
2016
  return (source, context) => {
2025
2017
  if (source === '') return;
2026
2018
  if (!match(source)) return;
2027
- if (!match2(source)) return;
2028
2019
  const result = parser(source, context);
2029
2020
  if (!result) return;
2030
2021
  return (0, parser_1.exec)(result).length < source.length ? result : global_1.undefined;
@@ -2127,14 +2118,12 @@ function guard(f, parser) {
2127
2118
  exports.guard = guard;
2128
2119
 
2129
2120
  function reset(base, parser) {
2130
- if (isEmpty(base)) return parser;
2131
2121
  return (source, context) => parser(source, inherit((0, alias_1.ObjectCreate)(context), base));
2132
2122
  }
2133
2123
 
2134
2124
  exports.reset = reset;
2135
2125
 
2136
2126
  function context(base, parser) {
2137
- if (isEmpty(base)) return parser;
2138
2127
  const override = (0, memoize_1.memoize)(context => inherit((0, alias_1.ObjectCreate)(context), base), new global_1.WeakMap());
2139
2128
  return (source, context) => parser(source, override(context));
2140
2129
  }
@@ -2166,12 +2155,6 @@ const inherit = (0, assign_1.template)((prop, target, source) => {
2166
2155
  }
2167
2156
  });
2168
2157
 
2169
- function isEmpty(context) {
2170
- for (const _ in context) return false;
2171
-
2172
- return true;
2173
- }
2174
-
2175
2158
  /***/ }),
2176
2159
 
2177
2160
  /***/ 7957:
@@ -3550,12 +3533,13 @@ function parse(source, opts = {}, context) {
3550
3533
  const url = (0, header_2.headers)(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
3551
3534
  source = !context ? (0, normalize_1.normalize)(source) : source;
3552
3535
  context = {
3553
- url: url ? new url_1.ReadonlyURL(url) : context?.url,
3554
3536
  host: opts.host ?? context?.host ?? new url_1.ReadonlyURL(global_1.location.pathname, global_1.location.origin),
3537
+ url: url ? new url_1.ReadonlyURL(url) : context?.url,
3538
+ id: opts.id ?? context?.id,
3555
3539
  caches: context?.caches,
3556
- footnotes: global_1.undefined,
3557
- test: global_1.undefined,
3558
- ...opts
3540
+ ...(context?.resources && {
3541
+ resources: context.resources
3542
+ })
3559
3543
  };
3560
3544
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3561
3545
  const node = (0, dom_1.frag)();
@@ -3652,7 +3636,7 @@ const random_1 = __webpack_require__(7325);
3652
3636
 
3653
3637
  exports.block = (0, combinator_1.creator)(error((0, combinator_1.reset)({
3654
3638
  resources: {
3655
- budget: 100 * 1000,
3639
+ budget: 50 * 1000,
3656
3640
  recursion: 200
3657
3641
  }
3658
3642
  }, (0, combinator_1.union)([source_1.emptyline, horizontalrule_1.horizontalrule, heading_1.heading, ulist_1.ulist, olist_1.olist, ilist_1.ilist, dlist_1.dlist, table_1.table, codeblock_1.codeblock, mathblock_1.mathblock, extension_1.extension, sidefence_1.sidefence, blockquote_1.blockquote, reply_1.reply, paragraph_1.paragraph]))));
@@ -3888,7 +3872,7 @@ const parse_1 = __webpack_require__(5013);
3888
3872
 
3889
3873
  const dom_1 = __webpack_require__(3252);
3890
3874
 
3891
- exports.aside = (0, combinator_1.creator)(100, (0, combinator_1.block)((0, combinator_1.validate)('~~~', (0, combinator_1.fmap)((0, combinator_1.fence)(/^(~{3,})aside(?!\S)([^\n]*)(?:$|\n)/, 300), // Bug: Type mismatch between outer and inner.
3875
+ exports.aside = (0, combinator_1.block)((0, combinator_1.validate)('~~~', (0, combinator_1.fmap)((0, combinator_1.fence)(/^(~{3,})aside(?!\S)([^\n]*)(?:$|\n)/, 300), // Bug: Type mismatch between outer and inner.
3892
3876
  ([body, overflow, closer, opener, delim, param], _, context) => {
3893
3877
  if (!closer || overflow || param.trimStart()) return [(0, dom_1.html)('pre', {
3894
3878
  class: 'invalid',
@@ -3920,7 +3904,7 @@ exports.aside = (0, combinator_1.creator)(100, (0, combinator_1.block)((0, combi
3920
3904
  id: (0, indexee_1.identity)((0, indexee_1.text)(heading)),
3921
3905
  class: 'aside'
3922
3906
  }, [document, references])];
3923
- }))));
3907
+ })));
3924
3908
 
3925
3909
  /***/ }),
3926
3910
 
@@ -3946,7 +3930,7 @@ const mathblock_1 = __webpack_require__(3754);
3946
3930
  const dom_1 = __webpack_require__(3252);
3947
3931
 
3948
3932
  const opener = /^(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/;
3949
- exports.example = (0, combinator_1.creator)(100, (0, combinator_1.block)((0, combinator_1.validate)('~~~', (0, combinator_1.fmap)((0, combinator_1.fence)(opener, 300), // Bug: Type mismatch between outer and inner.
3933
+ exports.example = (0, combinator_1.block)((0, combinator_1.validate)('~~~', (0, combinator_1.fmap)((0, combinator_1.fence)(opener, 300), // Bug: Type mismatch between outer and inner.
3950
3934
  ([body, overflow, closer, opener, delim, type = 'markdown', param], _, context) => {
3951
3935
  if (!closer || overflow || param.trimStart()) return [(0, dom_1.html)('pre', {
3952
3936
  class: 'invalid',
@@ -3993,7 +3977,7 @@ exports.example = (0, combinator_1.creator)(100, (0, combinator_1.block)((0, com
3993
3977
  'data-invalid-message': 'Invalid example type'
3994
3978
  }, `${opener}${body}${closer}`)];
3995
3979
  }
3996
- }))));
3980
+ })));
3997
3981
 
3998
3982
  /***/ }),
3999
3983
 
@@ -5390,7 +5374,7 @@ const util_1 = __webpack_require__(9437);
5390
5374
 
5391
5375
  const dom_1 = __webpack_require__(3252);
5392
5376
 
5393
- exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('((', '))', '\n', (0, combinator_1.fmap)((0, combinator_1.surround)('((', (0, combinator_1.guard)(context => context.syntax?.inline?.annotation ?? true, (0, combinator_1.context)({
5377
+ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('((', (0, combinator_1.fmap)((0, combinator_1.surround)('((', (0, combinator_1.guard)(context => context.syntax?.inline?.annotation ?? true, (0, combinator_1.context)({
5394
5378
  syntax: {
5395
5379
  inline: {
5396
5380
  annotation: false,
@@ -5405,9 +5389,9 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0,
5405
5389
  }
5406
5390
  },
5407
5391
  delimiters: global_1.undefined
5408
- }, (0, util_1.trimBlank)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', /^\\?\n/)))), '))'), ns => [(0, dom_1.html)('sup', {
5392
+ }, (0, util_1.trimBlankStart)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', /^\\?\n/)))), '))'), ns => [(0, dom_1.html)('sup', {
5409
5393
  class: 'annotation'
5410
- }, [(0, dom_1.html)('span', (0, dom_1.defrag)(ns))])]))));
5394
+ }, [(0, dom_1.html)('span', (0, util_1.trimNodeEnd)((0, dom_1.defrag)(ns)))])]))));
5411
5395
 
5412
5396
  /***/ }),
5413
5397
 
@@ -5702,7 +5686,7 @@ const dom_1 = __webpack_require__(3252);
5702
5686
  const array_1 = __webpack_require__(8112);
5703
5687
 
5704
5688
  const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
5705
- exports.bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.union)([(0, combinator_1.surround)((0, source_1.str)('('), (0, source_1.str)(index), (0, source_1.str)(')')), (0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)(inline_1.inline, ')'), (0, source_1.str)(')'), true, ([as, bs = [], cs], rest) => [[(0, dom_1.html)('span', {
5689
+ exports.bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(0, (0, combinator_1.union)([(0, combinator_1.surround)((0, source_1.str)('('), (0, source_1.str)(index), (0, source_1.str)(')')), (0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)(inline_1.inline, ')'), (0, source_1.str)(')'), true, ([as, bs = [], cs], rest) => [[(0, dom_1.html)('span', {
5706
5690
  class: 'paren'
5707
5691
  }, (0, dom_1.defrag)((0, array_1.push)((0, array_1.unshift)(as, bs), cs)))], rest], ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('('), (0, source_1.str)(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0)))), (0, source_1.str)(')')), (0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)(inline_1.inline, ')'), (0, source_1.str)(')'), true, ([as, bs = [], cs], rest) => [[(0, dom_1.html)('span', {
5708
5692
  class: 'paren'
@@ -5955,7 +5939,7 @@ const util_1 = __webpack_require__(9437);
5955
5939
 
5956
5940
  const dom_1 = __webpack_require__(3252);
5957
5941
 
5958
- exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[#', ']', '\n', (0, combinator_1.fmap)((0, indexee_1.indexee)((0, combinator_1.fmap)((0, combinator_1.surround)('[#', (0, combinator_1.guard)(context => context.syntax?.inline?.index ?? true, (0, util_1.startTight)((0, combinator_1.context)({
5942
+ exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[#', (0, combinator_1.fmap)((0, indexee_1.indexee)((0, combinator_1.fmap)((0, combinator_1.surround)('[#', (0, combinator_1.guard)(context => context.syntax?.inline?.index ?? true, (0, util_1.startTight)((0, combinator_1.context)({
5959
5943
  syntax: {
5960
5944
  inline: {
5961
5945
  annotation: false,
@@ -6172,7 +6156,7 @@ const array_1 = __webpack_require__(8112); // Don't use the symbols already used
6172
6156
  // All syntax surrounded by square brackets shouldn't contain line breaks.
6173
6157
 
6174
6158
 
6175
- exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)(['[:', '[^'], ']', '\n', (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, util_1.startTight)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']')), (0, source_1.str)(']'), false, ([as, bs], rest) => [[(0, dom_1.html)('span', {
6159
+ exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)(['[:', '[^'], (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, util_1.startTight)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', /^\\?\n/)), (0, source_1.str)(']'), false, ([as, bs], rest) => [[(0, dom_1.html)('span', {
6176
6160
  class: 'invalid',
6177
6161
  'data-invalid-syntax': 'extension',
6178
6162
  'data-invalid-type': 'syntax',
@@ -6219,14 +6203,17 @@ const attrspecs = {
6219
6203
  global_1.Object.setPrototypeOf(attrspecs, null);
6220
6204
  global_1.Object.values(attrspecs).forEach(o => global_1.Object.setPrototypeOf(o, null));
6221
6205
  exports.html = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('<', (0, combinator_1.validate)(/^<[a-z]+(?=[^\S\n]|>)/, (0, combinator_1.union)([(0, combinator_1.focus)('<wbr>', () => [[(0, dom_1.html)('wbr')], '']), (0, combinator_1.focus)( // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
6222
- /^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/, source => [[source], '']), (0, combinator_1.match)(new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, util_1.startLoose)((0, combinator_1.some)((0, combinator_1.union)([(0, combinator_1.open)(/^\n?/, (0, combinator_1.some)(inline_1.inline, (0, util_1.blankWith)('\n', `</${tag}>`)), true)])), `</${tag}>`), (0, source_1.str)(`</${tag}>`), false, ([as, bs, cs], rest) => [[elem(tag, as, bs, cs)], rest]), ([, tag]) => TAGS.indexOf(tag), [])), (0, combinator_1.match)(new RegExp(String.raw`^<(?!${TAGS.join('|')}\b)([a-z]+)(?=[^\S\n]|>)`), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, util_1.startLoose)((0, combinator_1.some)((0, combinator_1.union)([(0, combinator_1.open)(/^\n?/, (0, combinator_1.some)(inline_1.inline, (0, util_1.blankWith)('\n', `</${tag}>`)), true)])), `</${tag}>`), (0, source_1.str)(`</${tag}>`), false, ([as, bs, cs], rest) => [[elem(tag, as, bs, cs)], rest]), ([, tag]) => tag, new cache_1.Cache(10000)))])))));
6206
+ /^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/, source => [[source], '']), (0, combinator_1.match)(new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)((0, combinator_1.open)(/^\n?/, (0, combinator_1.some)(inline_1.inline, (0, util_1.blankWith)('\n', `</${tag}>`)), true))]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => TAGS.indexOf(tag), [])), (0, combinator_1.match)(/^<([a-z]+)(?=[^\S\n]|>)/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)(inline_1.inline, `</${tag}>`)]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => tag, new cache_1.Cache(10000)))])))));
6223
6207
  exports.attribute = (0, combinator_1.union)([(0, source_1.str)(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/)]); // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
6224
6208
  // [...document.querySelectorAll('tbody > tr > td:first-child')].map(el => el.textContent.slice(1, -1))
6225
6209
 
6226
6210
  const TAGS = global_1.Object.freeze(["html", "base", "head", "link", "meta", "style", "title", "body", "address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4", "h5", "h6", "main", "nav", "section", "blockquote", "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li", "menu", "ol", "p", "pre", "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i", "kbd", "mark", "q", "rp", "rt", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "area", "audio", "img", "map", "track", "video", "embed", "iframe", "object", "picture", "portal", "source", "svg", "math", "canvas", "noscript", "script", "del", "ins", "caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "button", "datalist", "fieldset", "form", "input", "label", "legend", "meter", "optgroup", "option", "output", "progress", "select", "textarea", "details", "dialog", "summary", "slot", "template", "acronym", "applet", "basefont", "bgsound", "big", "blink", "center", "content", "dir", "font", "frame", "frameset", "hgroup", "image", "keygen", "marquee", "menuitem", "nobr", "noembed", "noframes", "param", "plaintext", "rb", "rtc", "shadow", "spacer", "strike", "tt", "xmp"]);
6227
6211
 
6228
6212
  function elem(tag, as, bs, cs) {
6229
- if (!tags.includes(tag)) return invalid('tag', `Invalid HTML tag <${tag}>`, as, bs, cs);
6213
+ if (!tags.includes(tag)) return invalid('tag', `Invalid HTML tag name "${tag}"`, as, bs, cs);
6214
+ if (cs.length === 0) return invalid('tag', `Missing the closing HTML tag "</${tag}>"`, as, bs, cs);
6215
+ if (bs.length === 0) return invalid('content', `Missing the content`, as, bs, cs);
6216
+ if (!(0, util_1.isStartLooseNodes)(bs)) return invalid('content', `Missing the visible content in the same line`, as, bs, cs);
6230
6217
  const attrs = attributes('html', [], attrspecs[tag], as.slice(1, -1));
6231
6218
  return 'data-invalid-syntax' in attrs ? invalid('attribute', 'Invalid HTML attribute', as, bs, cs) : (0, dom_1.html)(tag, attrs, (0, dom_1.defrag)(bs));
6232
6219
  }
@@ -6369,7 +6356,7 @@ const optspec = {
6369
6356
  rel: ['nofollow']
6370
6357
  };
6371
6358
  Object.setPrototypeOf(optspec, null);
6372
- exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(10, (0, combinator_1.validate)(['[', '{'], '}', '\n', (0, combinator_1.bind)((0, combinator_1.guard)(context => context.syntax?.inline?.link ?? true, (0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.context)({
6359
+ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(10, (0, combinator_1.validate)(['[', '{'], (0, combinator_1.bind)((0, combinator_1.guard)(context => context.syntax?.inline?.link ?? true, (0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.context)({
6373
6360
  syntax: {
6374
6361
  inline: {
6375
6362
  link: false
@@ -6388,7 +6375,8 @@ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(10, (0, co
6388
6375
  autolink: false
6389
6376
  }
6390
6377
  }
6391
- }, (0, util_1.trimBlank)((0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/))), ']', true)]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))]))), ([params, content = []], rest, context) => {
6378
+ }, (0, util_1.trimBlankStart)((0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/))), ']', true)]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))]))), ([params, content = []], rest, context) => {
6379
+ content = (0, util_1.trimNodeEnd)(content);
6392
6380
  if ((0, parser_1.eval)((0, combinator_1.some)(autolink_1.autolink)((0, util_1.stringify)(content), context))?.some(node => typeof node === 'object')) return;
6393
6381
  const INSECURE_URI = params.shift();
6394
6382
  const el = elem(INSECURE_URI, (0, dom_1.defrag)(content), new url_1.ReadonlyURL(resolve(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href), context.host?.origin || global_1.location.origin);
@@ -6576,7 +6564,7 @@ const optspec = {
6576
6564
  rel: global_1.undefined
6577
6565
  };
6578
6566
  Object.setPrototypeOf(optspec, null);
6579
- exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(10, (0, combinator_1.validate)(['![', '!{'], '}', '\n', (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.open)('!', (0, combinator_1.guard)(context => context.syntax?.inline?.media ?? true, (0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)(/^\[(?!\s*\\\s)/, (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', /^\\?\n/), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([link_1.uri, (0, combinator_1.some)(option)]), /^[^\S\n]*}/))]))), ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
6567
+ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.creator)(10, (0, combinator_1.validate)(['![', '!{'], (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.open)('!', (0, combinator_1.guard)(context => context.syntax?.inline?.media ?? true, (0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)(/^\[(?!\s*\\\s)/, (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', /^\\?\n/), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([link_1.uri, (0, combinator_1.some)(option)]), /^[^\S\n]*}/))]))), ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
6580
6568
  const INSECURE_URI = params.shift();
6581
6569
  const url = new url_1.ReadonlyURL((0, link_1.resolve)(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href);
6582
6570
  let cache;
@@ -6667,7 +6655,7 @@ const util_1 = __webpack_require__(9437);
6667
6655
 
6668
6656
  const dom_1 = __webpack_require__(3252);
6669
6657
 
6670
- exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[[', ']]', '\n', (0, combinator_1.fmap)((0, combinator_1.surround)('[[', (0, combinator_1.guard)(context => context.syntax?.inline?.reference ?? true, (0, combinator_1.context)({
6658
+ exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[[', (0, combinator_1.fmap)((0, combinator_1.surround)('[[', (0, combinator_1.guard)(context => context.syntax?.inline?.reference ?? true, (0, combinator_1.context)({
6671
6659
  syntax: {
6672
6660
  inline: {
6673
6661
  annotation: false,
@@ -6681,7 +6669,7 @@ exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, c
6681
6669
  }
6682
6670
  },
6683
6671
  delimiters: global_1.undefined
6684
- }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/)), (0, util_1.trimBlank)((0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/))]))), ']]'), ns => [(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, dom_1.defrag)(ns))])]))));
6672
+ }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/)), (0, util_1.trimBlankStart)((0, combinator_1.some)(inline_1.inline, ']', /^\\?\n/))]))), ']]'), ns => [(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, util_1.trimNodeEnd)((0, dom_1.defrag)(ns)))])]))));
6685
6673
  const abbr = (0, combinator_1.creator)((0, combinator_1.bind)((0, combinator_1.surround)('^', (0, combinator_1.union)([(0, source_1.str)(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]), /^\|?(?=]])|^\|[^\S\n]*/), ([source], rest) => [[(0, dom_1.html)('abbr', source)], rest.replace(util_1.regBlankStart, '')]));
6686
6674
 
6687
6675
  function attributes(ns) {
@@ -6727,7 +6715,7 @@ const dom_1 = __webpack_require__(3252);
6727
6715
 
6728
6716
  const array_1 = __webpack_require__(8112);
6729
6717
 
6730
- exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[', ')', '\n', (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'), (0, combinator_1.surround)('(', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')')]), ([texts]) => (0, util_1.isStartTightNodes)(texts)), ([texts, rubies], rest) => {
6718
+ exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.validate)('[', (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'), (0, combinator_1.surround)('(', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')')]), ([texts]) => (0, util_1.isStartTightNodes)(texts)), ([texts, rubies], rest) => {
6731
6719
  const tail = typeof texts[texts.length - 1] === 'object' ? [texts.pop()] : [];
6732
6720
  tail.length === 0 && texts[texts.length - 1] === '' && texts.pop();
6733
6721
 
@@ -7773,7 +7761,7 @@ exports.unescsource = (0, combinator_1.creator)(source => {
7773
7761
  Object.defineProperty(exports, "__esModule", ({
7774
7762
  value: true
7775
7763
  }));
7776
- exports.stringify = exports.trimBlankEnd = exports.trimBlankStart = exports.trimBlank = exports.isStartTightNodes = exports.startTight = exports.startLoose = exports.visualize = exports.blankWith = exports.regBlankStart = void 0;
7764
+ exports.stringify = exports.trimNodeEnd = exports.trimBlankEnd = exports.trimBlankStart = exports.trimBlank = exports.isStartTightNodes = exports.isStartLooseNodes = exports.startTight = exports.startLoose = exports.visualize = exports.blankWith = exports.regBlankStart = void 0;
7777
7765
 
7778
7766
  const global_1 = __webpack_require__(4128);
7779
7767
 
@@ -7877,6 +7865,24 @@ const isStartTight = (0, memoize_1.reduce)((source, context, except) => {
7877
7865
  }
7878
7866
  }, (source, _, except = '') => `${source}\x1E${except}`);
7879
7867
 
7868
+ function isStartLooseNodes(nodes) {
7869
+ if (nodes.length === 0) return true;
7870
+
7871
+ for (let i = 0; i < nodes.length; ++i) {
7872
+ const node = nodes[i];
7873
+ if (isVisible(node)) return true;
7874
+
7875
+ if (typeof node === 'object') {
7876
+ if (node.tagName === 'BR') break;
7877
+ if (node.className === 'linebreak') break;
7878
+ }
7879
+ }
7880
+
7881
+ return false;
7882
+ }
7883
+
7884
+ exports.isStartLooseNodes = isStartLooseNodes;
7885
+
7880
7886
  function isStartTightNodes(nodes) {
7881
7887
  if (nodes.length === 0) return true;
7882
7888
  return isVisible(nodes[0], 0);
@@ -7972,6 +7978,8 @@ function trimNodeEnd(nodes) {
7972
7978
  return (0, array_1.push)(nodes, skip);
7973
7979
  }
7974
7980
 
7981
+ exports.trimNodeEnd = trimNodeEnd;
7982
+
7975
7983
  function stringify(nodes) {
7976
7984
  let acc = '';
7977
7985
 
package/markdown.d.ts CHANGED
@@ -985,6 +985,7 @@ export namespace MarkdownParser {
985
985
  Inline<'html/tag'>,
986
986
  Parser<HTMLElement | string, Context, [
987
987
  InlineParser,
988
+ InlineParser,
988
989
  ]> {
989
990
  }
990
991
  export namespace TagParser {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.254.2",
3
+ "version": "0.256.0",
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",
@@ -9,11 +9,9 @@ import { Parser, Ctx, Tree, Context, eval, exec, check } from '../../data/parser
9
9
 
10
10
  export function validate<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], parser: P): P;
11
11
  export function validate<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], has: string, parser: P): P;
12
- export function validate<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], has: string, end: string, parser: P): P;
13
- export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has: string | Parser<T>, end?: string | Parser<T>, parser?: Parser<T>): Parser<T> {
14
- if (typeof has === 'function') return validate(patterns, '', '', has);
15
- if (typeof end === 'function') return validate(patterns, has, '', end);
16
- if (!isArray(patterns)) return validate([patterns], has, end!, parser!);
12
+ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has: string | Parser<T>, parser?: Parser<T>): Parser<T> {
13
+ if (typeof has === 'function') return validate(patterns, '', has);
14
+ if (!isArray(patterns)) return validate([patterns], has, parser!);
17
15
  assert(patterns.length > 0);
18
16
  assert(patterns.every(pattern => pattern instanceof RegExp ? !pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^') : true));
19
17
  assert(parser);
@@ -26,17 +24,9 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
26
24
  ? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
27
25
  : `|| /${pattern.source}/${pattern.flags}.test(source)`),
28
26
  ].join(''))();
29
- const match2 = (source: string): boolean => {
30
- if (!has) return true;
31
- const i = end ? source.indexOf(end, 1) : -1;
32
- return i !== -1
33
- ? source.slice(0, i).indexOf(has, 1) !== -1
34
- : source.indexOf(has, 1) !== -1;
35
- };
36
27
  return (source, context) => {
37
28
  if (source === '') return;
38
29
  if (!match(source)) return;
39
- if (!match2(source)) return;
40
30
  const result = parser!(source, context);
41
31
  assert(check(source, result));
42
32
  if (!result) return;
@@ -17,7 +17,6 @@ export function reset<P extends Parser<unknown>>(context: Context<P>, parser: P)
17
17
  export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
18
18
  assert(Object.getPrototypeOf(base) === Object.prototype);
19
19
  assert(Object.freeze(base));
20
- if (isEmpty(base)) return parser;
21
20
  return (source, context) =>
22
21
  parser(source, inherit(ObjectCreate(context), base));
23
22
  }
@@ -26,7 +25,6 @@ export function context<P extends Parser<unknown>>(context: Context<P>, parser:
26
25
  export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
27
26
  assert(Object.getPrototypeOf(base) === Object.prototype);
28
27
  assert(Object.freeze(base));
29
- if (isEmpty(base)) return parser;
30
28
  const override = memoize<Ctx, Ctx>(context => inherit(ObjectCreate(context), base), new WeakMap());
31
29
  return (source, context) =>
32
30
  parser(source, override(context));
@@ -40,6 +38,7 @@ const inherit = template((prop, target, source) => {
40
38
  switch (prop) {
41
39
  case 'resources':
42
40
  assert(typeof source[prop] === 'object');
41
+ assert(target[prop] || !(prop in target));
43
42
  if (prop in target && !hasOwnProperty(target, prop)) return;
44
43
  return target[prop] = ObjectCreate(source[prop]);
45
44
  }
@@ -58,8 +57,3 @@ const inherit = template((prop, target, source) => {
58
57
  return target[prop] = source[prop];
59
58
  }
60
59
  });
61
-
62
- function isEmpty(context: Ctx): boolean {
63
- for (const _ in context) return false;
64
- return true;
65
- }
@@ -4,7 +4,7 @@ export function creator<P extends Parser<unknown>>(parser: P): P;
4
4
  export function creator<P extends Parser<unknown>>(cost: number, parser: P): P;
5
5
  export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
6
6
  if (typeof cost === 'function') return creator(1, cost);
7
- assert(cost > 0);
7
+ assert(cost >= 0);
8
8
  return (source, context) => {
9
9
  const { resources = { budget: 1, recursion: 1 } } = context;
10
10
  if (resources.budget <= 0) throw new Error('Too many creations.');
@@ -229,10 +229,15 @@ describe('Unit: parser/api/parse', () => {
229
229
  [`<p>${'"[% '.repeat(100).trim()}</p>`]);
230
230
  });
231
231
 
232
+ if (!navigator.userAgent.includes('Chrome')) return;
233
+
232
234
  it('recursion', () => {
233
235
  assert.deepStrictEqual(
234
236
  [...parse('('.repeat(199)).children].map(el => el.outerHTML),
235
237
  [`<p>${'('.repeat(199)}</p>`]);
238
+ });
239
+
240
+ it('recursion error', () => {
236
241
  assert.deepStrictEqual(
237
242
  [...parse('('.repeat(200) + '\n\na').children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
238
243
  [
@@ -1,4 +1,4 @@
1
- import { undefined, location } from 'spica/global';
1
+ import { location } from 'spica/global';
2
2
  import { ParserOptions } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
@@ -22,12 +22,13 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
22
22
  source = !context ? normalize(source) : source;
23
23
  assert(!context?.delimiters);
24
24
  context = {
25
- url: url ? new ReadonlyURL(url as ':') : context?.url,
26
25
  host: opts.host ?? context?.host ?? new ReadonlyURL(location.pathname, location.origin),
26
+ url: url ? new ReadonlyURL(url as ':') : context?.url,
27
+ id: opts.id ?? context?.id,
27
28
  caches: context?.caches,
28
- footnotes: undefined,
29
- test: undefined,
30
- ...opts,
29
+ ...context?.resources && {
30
+ resources: context.resources,
31
+ },
31
32
  };
32
33
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
33
34
  const node = frag();
@@ -1,10 +1,10 @@
1
1
  import { ExtensionParser } from '../../block';
2
- import { block, validate, fence, creator, fmap } from '../../../combinator';
2
+ import { block, validate, fence, fmap } from '../../../combinator';
3
3
  import { identity, text } from '../../inline/extension/indexee';
4
4
  import { parse } from '../../api/parse';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
- export const aside: ExtensionParser.AsideParser = creator(100, block(validate('~~~', fmap(
7
+ export const aside: ExtensionParser.AsideParser = block(validate('~~~', fmap(
8
8
  fence(/^(~{3,})aside(?!\S)([^\n]*)(?:$|\n)/, 300),
9
9
  // Bug: Type mismatch between outer and inner.
10
10
  ([body, overflow, closer, opener, delim, param]: string[], _, context) => {
@@ -43,4 +43,4 @@ export const aside: ExtensionParser.AsideParser = creator(100, block(validate('~
43
43
  references,
44
44
  ]),
45
45
  ];
46
- }))));
46
+ })));
@@ -1,13 +1,13 @@
1
1
  import { ExtensionParser } from '../../block';
2
2
  import { eval } from '../../../combinator/data/parser';
3
- import { block, validate, fence, creator, fmap } from '../../../combinator';
3
+ import { block, validate, fence, fmap } from '../../../combinator';
4
4
  import { parse } from '../../api/parse';
5
5
  import { mathblock } from '../mathblock';
6
6
  import { html } from 'typed-dom/dom';
7
7
 
8
8
  const opener = /^(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/;
9
9
 
10
- export const example: ExtensionParser.ExampleParser = creator(100, block(validate('~~~', fmap(
10
+ export const example: ExtensionParser.ExampleParser = block(validate('~~~', fmap(
11
11
  fence(opener, 300),
12
12
  // Bug: Type mismatch between outer and inner.
13
13
  ([body, overflow, closer, opener, delim, type = 'markdown', param]: string[], _, context) => {
@@ -58,4 +58,4 @@ export const example: ExtensionParser.ExampleParser = creator(100, block(validat
58
58
  }, `${opener}${body}${closer}`),
59
59
  ];
60
60
  }
61
- }))));
61
+ })));
@@ -36,7 +36,7 @@ export import ReplyParser = BlockParser.ReplyParser;
36
36
  export import ParagraphParser = BlockParser.ParagraphParser;
37
37
 
38
38
  export const block: BlockParser = creator(error(
39
- reset({ resources: { budget: 100 * 1000, recursion: 200 } },
39
+ reset({ resources: { budget: 50 * 1000, recursion: 200 } },
40
40
  union([
41
41
  emptyline,
42
42
  horizontalrule,
@@ -2,10 +2,10 @@ import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
3
  import { union, some, validate, guard, context, creator, surround, lazy, fmap } from '../../combinator';
4
4
  import { inline } from '../inline';
5
- import { trimBlank } from '../util';
5
+ import { trimBlankStart, trimNodeEnd } from '../util';
6
6
  import { html, defrag } from 'typed-dom/dom';
7
7
 
8
- export const annotation: AnnotationParser = lazy(() => creator(validate('((', '))', '\n', fmap(surround(
8
+ export const annotation: AnnotationParser = lazy(() => creator(validate('((', fmap(surround(
9
9
  '((',
10
10
  guard(context => context.syntax?.inline?.annotation ?? true,
11
11
  context({ syntax: { inline: {
@@ -19,6 +19,6 @@ export const annotation: AnnotationParser = lazy(() => creator(validate('((', ')
19
19
  //link: true,
20
20
  //autolink: true,
21
21
  }}, delimiters: undefined },
22
- trimBlank(some(union([inline]), ')', /^\\?\n/)))),
22
+ trimBlankStart(some(union([inline]), ')', /^\\?\n/)))),
23
23
  '))'),
24
- ns => [html('sup', { class: 'annotation' }, [html('span', defrag(ns))])]))));
24
+ ns => [html('sup', { class: 'annotation' }, [html('span', trimNodeEnd(defrag(ns)))])]))));
@@ -8,7 +8,7 @@ import { unshift, push } from 'spica/array';
8
8
 
9
9
  const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
10
10
 
11
- export const bracket: BracketParser = lazy(() => creator(union([
11
+ export const bracket: BracketParser = lazy(() => creator(0, union([
12
12
  surround(str('('), str(index), str(')')),
13
13
  surround(str('('), some(inline, ')'), str(')'), true,
14
14
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
@@ -9,7 +9,7 @@ import { html, define, defrag } from 'typed-dom/dom';
9
9
 
10
10
  import IndexParser = ExtensionParser.IndexParser;
11
11
 
12
- export const index: IndexParser = lazy(() => creator(validate('[#', ']', '\n', fmap(indexee(fmap(surround(
12
+ export const index: IndexParser = lazy(() => creator(validate('[#', fmap(indexee(fmap(surround(
13
13
  '[#',
14
14
  guard(context => context.syntax?.inline?.index ?? true,
15
15
  startTight(
@@ -21,13 +21,13 @@ describe('Unit: parser/inline/extension/placeholder', () => {
21
21
  assert.deepStrictEqual(inspect(parser('[^\na]')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('[^\\\na]')), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('[^ !http://host]')), undefined);
24
- assert.deepStrictEqual(inspect(parser('[^a')), undefined);
25
- assert.deepStrictEqual(inspect(parser('[^a\n]')), undefined);
26
- assert.deepStrictEqual(inspect(parser('[^a\n\n]')), undefined);
27
- assert.deepStrictEqual(inspect(parser('[^a\\\n]')), undefined);
28
- assert.deepStrictEqual(inspect(parser('[^a\\\n\\\n]')), undefined);
29
- assert.deepStrictEqual(inspect(parser('[^a\nb]')), undefined);
30
- assert.deepStrictEqual(inspect(parser('[^a\\\nb]')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('[^a')), [['[^', 'a'], '']);
25
+ assert.deepStrictEqual(inspect(parser('[^a\n]')), [['[^', 'a'], '\n]']);
26
+ assert.deepStrictEqual(inspect(parser('[^a\n\n]')), [['[^', 'a'], '\n\n]']);
27
+ assert.deepStrictEqual(inspect(parser('[^a\\\n]')), [['[^', 'a'], '\\\n]']);
28
+ assert.deepStrictEqual(inspect(parser('[^a\\\n\\\n]')), [['[^', 'a'], '\\\n\\\n]']);
29
+ assert.deepStrictEqual(inspect(parser('[^a\nb]')), [['[^', 'a'], '\nb]']);
30
+ assert.deepStrictEqual(inspect(parser('[^a\\\nb]')), [['[^', 'a'], '\\\nb]']);
31
31
  assert.deepStrictEqual(inspect(parser('[[]')), undefined);
32
32
  assert.deepStrictEqual(inspect(parser('[]]')), undefined);
33
33
  assert.deepStrictEqual(inspect(parser('[[]]')), undefined);
@@ -10,9 +10,9 @@ import { unshift } from 'spica/array';
10
10
 
11
11
  // All syntax surrounded by square brackets shouldn't contain line breaks.
12
12
 
13
- export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => creator(validate(['[:', '[^'], ']', '\n', surround(
13
+ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => creator(validate(['[:', '[^'], surround(
14
14
  str(/^\[[:^]/),
15
- startTight(some(union([inline]), ']')),
15
+ startTight(some(union([inline]), ']', /^\\?\n/)),
16
16
  str(']'), false,
17
17
  ([as, bs], rest) => [[
18
18
  html('span', {
@@ -7,12 +7,12 @@ describe('Unit: parser/inline/html', () => {
7
7
  const parser = (source: string) => some(html)(source, {});
8
8
 
9
9
  it('xss', () => {
10
- assert.deepStrictEqual(inspect(parser('<script>')), undefined);
11
- assert.deepStrictEqual(inspect(parser('<script>alert()<script>')), undefined);
10
+ assert.deepStrictEqual(inspect(parser('<script>')), [['<span class="invalid">&lt;script&gt;</span>'], '']);
11
+ assert.deepStrictEqual(inspect(parser('<script>alert()<script>')), [['<span class="invalid">&lt;script&gt;alert<span class="paren">()</span><span class="invalid">&lt;script&gt;</span></span>'], '']);
12
12
  assert.deepStrictEqual(inspect(parser('<script>alert()</script>')), [['<span class="invalid">&lt;script&gt;alert<span class="paren">()</span>&lt;/script&gt;</span>'], '']);
13
- assert.deepStrictEqual(inspect(parser('<script src="\\""></script>')), undefined);
14
- assert.deepStrictEqual(inspect(parser('<bdi onclick="alert()">')), undefined);
15
- assert.deepStrictEqual(inspect(parser('<bdi onclick="alert()"></bdi>')), undefined);
13
+ assert.deepStrictEqual(inspect(parser('<script src="\\""></script>')), [['<span class="invalid">&lt;script src="\\""&gt;&lt;/script&gt;</span>'], '']);
14
+ assert.deepStrictEqual(inspect(parser('<bdi onclick="alert()">')), [['<span class="invalid">&lt;bdi onclick="alert()"&gt;</span>'], '']);
15
+ assert.deepStrictEqual(inspect(parser('<bdi onclick="alert()"></bdi>')), [['<span class="invalid">&lt;bdi onclick="alert()"&gt;&lt;/bdi&gt;</span>'], '']);
16
16
  assert.deepStrictEqual(inspect(parser('<bdi onclick="alert()">a</bdi>')), [['<span class="invalid">&lt;bdi onclick="alert()"&gt;a&lt;/bdi&gt;</span>'], '']);
17
17
  assert.deepStrictEqual(inspect(parser('<bdi><bdi onclick="alert()">a</bdi></bdi>')), [['<bdi><span class="invalid">&lt;bdi onclick="alert()"&gt;a&lt;/bdi&gt;</span></bdi>'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl\\"><">a</bdo>')), [['<span class="invalid">&lt;bdo dir="rtl\\"&gt;&lt;"&gt;a&lt;/bdo&gt;</span>'], '']);
@@ -25,17 +25,19 @@ describe('Unit: parser/inline/html', () => {
25
25
  assert.deepStrictEqual(inspect(parser('<a,b>')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('<a, b>')), undefined);
27
27
  assert.deepStrictEqual(inspect(parser('<T>')), undefined);
28
- assert.deepStrictEqual(inspect(parser('<bdi>z')), undefined);
29
- assert.deepStrictEqual(inspect(parser('<bdi></bdi>')), undefined);
30
- assert.deepStrictEqual(inspect(parser('<bdi> </bdi>')), undefined);
31
- assert.deepStrictEqual(inspect(parser('<bdi>\\ </bdi>')), undefined);
32
- assert.deepStrictEqual(inspect(parser('<bdi>&Tab;</bdi>')), undefined);
33
- assert.deepStrictEqual(inspect(parser('<bdi><wbr></bdi>')), undefined);
34
- assert.deepStrictEqual(inspect(parser('<bdi>\n</bdi>')), undefined);
35
- assert.deepStrictEqual(inspect(parser('<bdi>\na</bdi>')), undefined);
36
- assert.deepStrictEqual(inspect(parser('<bdi>\\\na</bdi>')), undefined);
37
- assert.deepStrictEqual(inspect(parser('<bdi>a')), undefined);
38
- assert.deepStrictEqual(inspect(parser('<bdi>a</BDO>')), undefined);
28
+ assert.deepStrictEqual(inspect(parser('<bdi>z')), [['<span class="invalid">&lt;bdi&gt;z</span>'], '']);
29
+ assert.deepStrictEqual(inspect(parser('<bdi></bdi>')), [['<span class="invalid">&lt;bdi&gt;&lt;/bdi&gt;</span>'], '']);
30
+ assert.deepStrictEqual(inspect(parser('<bdi> </bdi>')), [['<span class="invalid">&lt;bdi&gt; &lt;/bdi&gt;</span>'], '']);
31
+ assert.deepStrictEqual(inspect(parser('<bdi> \n</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>&lt;/bdi&gt;</span>'], '']);
32
+ assert.deepStrictEqual(inspect(parser('<bdi> \na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>a&lt;/bdi&gt;</span>'], '']);
33
+ assert.deepStrictEqual(inspect(parser('<bdi>\\ </bdi>')), [['<span class="invalid">&lt;bdi&gt; &lt;/bdi&gt;</span>'], '']);
34
+ assert.deepStrictEqual(inspect(parser('<bdi>&Tab;</bdi>')), [['<span class="invalid">&lt;bdi&gt;\t&lt;/bdi&gt;</span>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('<bdi><wbr></bdi>')), [['<span class="invalid">&lt;bdi&gt;<wbr>&lt;/bdi&gt;</span>'], '']);
36
+ assert.deepStrictEqual(inspect(parser('<bdi>\n</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>&lt;/bdi&gt;</span>'], '']);
37
+ assert.deepStrictEqual(inspect(parser('<bdi>\na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>a&lt;/bdi&gt;</span>'], '']);
38
+ assert.deepStrictEqual(inspect(parser('<bdi>\\\na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<span class="linebreak"> </span>a&lt;/bdi&gt;</span>'], '']);
39
+ assert.deepStrictEqual(inspect(parser('<bdi>a')), [['<span class="invalid">&lt;bdi&gt;a</span>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('<bdi>a</BDO>')), [['<span class="invalid">&lt;bdi&gt;a&lt;/BDO&gt;</span>'], '']);
39
41
  assert.deepStrictEqual(inspect(parser('<BDI>a</BDI>')), undefined);
40
42
  assert.deepStrictEqual(inspect(parser('<BDI>a</bdo>')), undefined);
41
43
  assert.deepStrictEqual(inspect(parser('</bdi>')), undefined);
@@ -43,11 +45,11 @@ describe('Unit: parser/inline/html', () => {
43
45
  assert.deepStrictEqual(inspect(parser('<b><b><b>a</b></b></b>')), [['<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;a&lt;/b&gt;</span>&lt;/b&gt;</span>&lt;/b&gt;</span>'], '']);
44
46
  assert.deepStrictEqual(inspect(parser('<bdi><bdi><bdi>a</bdi></bdi></bdi>')), [['<bdi><bdi><bdi>a</bdi></bdi></bdi>'], '']);
45
47
  assert.deepStrictEqual(inspect(parser('<x a="*b*"')), undefined);
46
- assert.deepStrictEqual(inspect(parser('<x a="*b*">')), undefined);
47
- assert.deepStrictEqual(inspect(parser('<x a="*b*">c')), undefined);
48
+ assert.deepStrictEqual(inspect(parser('<x a="*b*">')), [['<span class="invalid">&lt;x a="*b*"&gt;</span>'], '']);
49
+ assert.deepStrictEqual(inspect(parser('<x a="*b*">c')), [['<span class="invalid">&lt;x a="*b*"&gt;c</span>'], '']);
48
50
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*"')), undefined);
49
- assert.deepStrictEqual(inspect(parser('<bdi a="*b*">')), undefined);
50
- assert.deepStrictEqual(inspect(parser('<bdi a="*b*">c')), undefined);
51
+ assert.deepStrictEqual(inspect(parser('<bdi a="*b*">')), [['<span class="invalid">&lt;bdi a="*b*"&gt;</span>'], '']);
52
+ assert.deepStrictEqual(inspect(parser('<bdi a="*b*">c')), [['<span class="invalid">&lt;bdi a="*b*"&gt;c</span>'], '']);
51
53
  assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*')), undefined);
52
54
  assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>')), undefined);
53
55
  assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>d</bdi>')), undefined);
@@ -80,7 +82,7 @@ describe('Unit: parser/inline/html', () => {
80
82
  });
81
83
 
82
84
  it('escape', () => {
83
- assert.deepStrictEqual(inspect(parser('<a>')), undefined);
85
+ assert.deepStrictEqual(inspect(parser('<a>')), [['<span class="invalid">&lt;a&gt;</span>'], '']);
84
86
  assert.deepStrictEqual(inspect(parser('<bdi><a>a</a></bdi>')), [['<bdi><span class="invalid">&lt;a&gt;a&lt;/a&gt;</span></bdi>'], '']);
85
87
  assert.deepStrictEqual(inspect(parser('<bdi>a<a>b</a>c</bdi>')), [['<bdi>a<span class="invalid">&lt;a&gt;b&lt;/a&gt;</span>c</bdi>'], '']);
86
88
  assert.deepStrictEqual(inspect(parser('<img>')), [['<img'], '>']);
@@ -1,9 +1,9 @@
1
1
  import { undefined, Object } from 'spica/global';
2
2
  import { HTMLParser } from '../inline';
3
- import { union, some, validate, focus, creator, surround, open, match, lazy } from '../../combinator';
3
+ import { union, subsequence, some, validate, focus, creator, surround, open, match, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { startLoose, blankWith } from '../util';
6
+ import { isStartLooseNodes, blankWith } from '../util';
7
7
  import { html as h, defrag } from 'typed-dom/dom';
8
8
  import { memoize } from 'spica/memoize';
9
9
  import { Cache } from 'spica/cache';
@@ -32,25 +32,31 @@ export const html: HTMLParser = lazy(() => creator(validate('<', validate(/^<[a-
32
32
  ([, tag]) =>
33
33
  surround<HTMLParser.TagParser, string>(surround(
34
34
  str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
35
- startLoose(some(union([
36
- open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`)), true),
37
- ])), `</${tag}>`),
38
- str(`</${tag}>`), false,
39
- ([as, bs, cs], rest) =>
40
- [[elem(tag, as, bs, cs)], rest]),
35
+ subsequence([
36
+ focus(/^[^\S\n]*\n/, some(inline)),
37
+ some(open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`)), true)),
38
+ ]),
39
+ str(`</${tag}>`), true,
40
+ ([as, bs = [], cs], rest) =>
41
+ [[elem(tag, as, bs, cs)], rest],
42
+ ([as, bs = []], rest) =>
43
+ [[elem(tag, as, bs, [])], rest]),
41
44
  ([, tag]) => TAGS.indexOf(tag), [])),
42
45
  match(
43
- new RegExp(String.raw`^<(?!${TAGS.join('|')}\b)([a-z]+)(?=[^\S\n]|>)`),
46
+ /^<([a-z]+)(?=[^\S\n]|>)/,
44
47
  memoize(
45
48
  ([, tag]) =>
46
49
  surround<HTMLParser.TagParser, string>(surround(
47
50
  str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true),
48
- startLoose(some(union([
49
- open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`)), true),
50
- ])), `</${tag}>`),
51
- str(`</${tag}>`), false,
52
- ([as, bs, cs], rest) =>
53
- [[elem(tag, as, bs, cs)], rest]),
51
+ subsequence([
52
+ focus(/^[^\S\n]*\n/, some(inline)),
53
+ some(inline, `</${tag}>`),
54
+ ]),
55
+ str(`</${tag}>`), true,
56
+ ([as, bs = [], cs], rest) =>
57
+ [[elem(tag, as, bs, cs)], rest],
58
+ ([as, bs = []], rest) =>
59
+ [[elem(tag, as, bs, [])], rest]),
54
60
  ([, tag]) => tag,
55
61
  new Cache(10000))),
56
62
  ])))));
@@ -204,8 +210,10 @@ const TAGS = Object.freeze([
204
210
  function elem(tag: string, as: string[], bs: (HTMLElement | string)[], cs: string[]): HTMLElement {
205
211
  assert(as.length > 0);
206
212
  assert(as[0][0] === '<' && as[as.length - 1].slice(-1) === '>');
207
- assert(cs.length === 1);
208
- if (!tags.includes(tag)) return invalid('tag', `Invalid HTML tag <${tag}>`, as, bs, cs);
213
+ if (!tags.includes(tag)) return invalid('tag', `Invalid HTML tag name "${tag}"`, as, bs, cs);
214
+ if (cs.length === 0) return invalid('tag', `Missing the closing HTML tag "</${tag}>"`, as, bs, cs);
215
+ if (bs.length === 0) return invalid('content', `Missing the content`, as, bs, cs);
216
+ if (!isStartLooseNodes(bs)) return invalid('content', `Missing the visible content in the same line`, as, bs, cs);
209
217
  const attrs = attributes('html', [], attrspecs[tag], as.slice(1, -1));
210
218
  return 'data-invalid-syntax' in attrs
211
219
  ? invalid('attribute', 'Invalid HTML attribute', as, bs, cs)
@@ -6,7 +6,7 @@ import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { autolink } from '../autolink';
8
8
  import { str } from '../source';
9
- import { trimBlank, stringify } from '../util';
9
+ import { trimBlankStart, trimNodeEnd, stringify } from '../util';
10
10
  import { html, define, defrag } from 'typed-dom/dom';
11
11
  import { ReadonlyURL } from 'spica/url';
12
12
 
@@ -15,7 +15,7 @@ const optspec = {
15
15
  } as const;
16
16
  Object.setPrototypeOf(optspec, null);
17
17
 
18
- export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], '}', '\n', bind(
18
+ export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], bind(
19
19
  guard(context => context.syntax?.inline?.link ?? true,
20
20
  reverse(tails([
21
21
  context({ syntax: { inline: {
@@ -36,7 +36,7 @@ export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], '}',
36
36
  media: false,
37
37
  autolink: false,
38
38
  }}},
39
- trimBlank(some(inline, ']', /^\\?\n/))),
39
+ trimBlankStart(some(inline, ']', /^\\?\n/))),
40
40
  ']',
41
41
  true),
42
42
  ]))),
@@ -44,6 +44,7 @@ export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], '}',
44
44
  ]))),
45
45
  ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
46
46
  assert(params.every(p => typeof p === 'string'));
47
+ content = trimNodeEnd(content);
47
48
  if (eval(some(autolink)(stringify(content), context))?.some(node => typeof node === 'object')) return;
48
49
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference') || (content[0] as HTMLElement).matches('.media'));
49
50
  const INSECURE_URI = params.shift()!;
@@ -17,7 +17,7 @@ const optspec = {
17
17
  } as const;
18
18
  Object.setPrototypeOf(optspec, null);
19
19
 
20
- export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'], '}', '\n', bind(verify(fmap(open(
20
+ export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'], bind(verify(fmap(open(
21
21
  '!',
22
22
  guard(context => context.syntax?.inline?.media ?? true,
23
23
  tails([
@@ -57,7 +57,7 @@ describe('Unit: parser/inline/reference', () => {
57
57
  assert.deepStrictEqual(inspect(parser('[[^a,]]')), [['<sup class="reference" data-abbr="a,"><span></span></sup>'], '']);
58
58
  assert.deepStrictEqual(inspect(parser('[[^a, ]]')), [['<sup class="reference" data-abbr="a,"><span></span></sup>'], '']);
59
59
  assert.deepStrictEqual(inspect(parser('[[^a ]]')), [['<sup class="reference" data-abbr="a"><span></span></sup>'], '']);
60
- assert.deepStrictEqual(inspect(parser('[[^a ]]')), [['<sup class="invalid"><span>^a </span></sup>'], '']);
60
+ assert.deepStrictEqual(inspect(parser('[[^a ]]')), [['<sup class="invalid"><span>^a</span></sup>'], '']);
61
61
  assert.deepStrictEqual(inspect(parser('[[^a b]]')), [['<sup class="reference" data-abbr="a b"><span></span></sup>'], '']);
62
62
  assert.deepStrictEqual(inspect(parser('[[^a b]]')), [['<sup class="invalid"><span>^a b</span></sup>'], '']);
63
63
  assert.deepStrictEqual(inspect(parser('[[^a|]]')), [['<sup class="reference" data-abbr="a"><span></span></sup>'], '']);
@@ -75,7 +75,7 @@ describe('Unit: parser/inline/reference', () => {
75
75
  assert.deepStrictEqual(inspect(parser('[[^a| ]]')), [['<sup class="reference" data-abbr="a"><span></span></sup>'], '']);
76
76
  assert.deepStrictEqual(inspect(parser('[[^1]]')), [['<sup class="invalid"><span>^1</span></sup>'], '']);
77
77
  assert.deepStrictEqual(inspect(parser('[[^1a]]')), [['<sup class="reference" data-abbr="1a"><span></span></sup>'], '']);
78
- assert.deepStrictEqual(inspect(parser('[[^1 ]]')), [['<sup class="invalid"><span>^1 </span></sup>'], '']);
78
+ assert.deepStrictEqual(inspect(parser('[[^1 ]]')), [['<sup class="invalid"><span>^1</span></sup>'], '']);
79
79
  assert.deepStrictEqual(inspect(parser('[[^1 a]]')), [['<sup class="reference" data-abbr="1 a"><span></span></sup>'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('[[^1|]]')), [['<sup class="invalid"><span>^1|</span></sup>'], '']);
81
81
  assert.deepStrictEqual(inspect(parser('[[^1 |]]')), [['<sup class="invalid"><span>^1 |</span></sup>'], '']);
@@ -86,11 +86,11 @@ describe('Unit: parser/inline/reference', () => {
86
86
  assert.deepStrictEqual(inspect(parser(`[[^A's, Aces']]`)), [[`<sup class="reference" data-abbr="A's, Aces'"><span></span></sup>`], '']);
87
87
  assert.deepStrictEqual(inspect(parser('[[^^]]')), [['<sup class="invalid"><span>^^</span></sup>'], '']);
88
88
  assert.deepStrictEqual(inspect(parser('[[\\^]]')), [['<sup class="reference"><span>^</span></sup>'], '']);
89
- assert.deepStrictEqual(inspect(parser('[[^ ]]')), [['<sup class="invalid"><span>^ </span></sup>'], '']);
89
+ assert.deepStrictEqual(inspect(parser('[[^ ]]')), [['<sup class="invalid"><span>^</span></sup>'], '']);
90
90
  assert.deepStrictEqual(inspect(parser('[[^ a]]')), [['<sup class="invalid"><span>^ a</span></sup>'], '']);
91
91
  assert.deepStrictEqual(inspect(parser('[[^ |]]')), [['<sup class="invalid"><span>^ |</span></sup>'], '']);
92
92
  assert.deepStrictEqual(inspect(parser('[[^ |b]]')), [['<sup class="invalid"><span>^ |b</span></sup>'], '']);
93
- assert.deepStrictEqual(inspect(parser('[[^ | ]]')), [['<sup class="invalid"><span>^ | </span></sup>'], '']);
93
+ assert.deepStrictEqual(inspect(parser('[[^ | ]]')), [['<sup class="invalid"><span>^ |</span></sup>'], '']);
94
94
  assert.deepStrictEqual(inspect(parser('[[^ | b]]')), [['<sup class="invalid"><span>^ | b</span></sup>'], '']);
95
95
  });
96
96
 
@@ -3,10 +3,10 @@ import { ReferenceParser } from '../inline';
3
3
  import { union, subsequence, some, validate, guard, context, creator, surround, open, lazy, fmap, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str, stropt } from '../source';
6
- import { regBlankStart, trimBlank, stringify } from '../util';
6
+ import { regBlankStart, trimBlankStart, trimNodeEnd, stringify } from '../util';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
 
9
- export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]', '\n', fmap(surround(
9
+ export const reference: ReferenceParser = lazy(() => creator(validate('[[', fmap(surround(
10
10
  '[[',
11
11
  guard(context => context.syntax?.inline?.reference ?? true,
12
12
  context({ syntax: { inline: {
@@ -22,10 +22,10 @@ export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]'
22
22
  subsequence([
23
23
  abbr,
24
24
  open(stropt(/^(?=\^)/), some(inline, ']', /^\\?\n/)),
25
- trimBlank(some(inline, ']', /^\\?\n/)),
25
+ trimBlankStart(some(inline, ']', /^\\?\n/)),
26
26
  ]))),
27
27
  ']]'),
28
- ns => [html('sup', attributes(ns), [html('span', defrag(ns))])]))));
28
+ ns => [html('sup', attributes(ns), [html('span', trimNodeEnd(defrag(ns)))])]))));
29
29
 
30
30
  const abbr: ReferenceParser.AbbrParser = creator(bind(surround(
31
31
  '^',
@@ -8,7 +8,7 @@ import { isStartTightNodes } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
  import { unshift, push } from 'spica/array';
10
10
 
11
- export const ruby: RubyParser = lazy(() => creator(validate('[', ')', '\n', bind(verify(
11
+ export const ruby: RubyParser = lazy(() => creator(validate('[', bind(verify(
12
12
  sequence([
13
13
  surround('[', focus(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'),
14
14
  surround('(', focus(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')'),
@@ -158,8 +158,8 @@ describe('Unit: parser/inline', () => {
158
158
  assert.deepStrictEqual(inspect(parser('[~http://host')), [['[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
159
159
  assert.deepStrictEqual(inspect(parser('[~a@b')), [['[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
160
160
  assert.deepStrictEqual(inspect(parser('[~~a~~]')), [['[', '<del>a</del>', ']'], '']);
161
- assert.deepStrictEqual(inspect(parser('[^http://host')), [['[', '^', '<a href="http://host" target="_blank">http://host</a>'], '']);
162
- assert.deepStrictEqual(inspect(parser('[^a@b')), [['[', '^', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
161
+ assert.deepStrictEqual(inspect(parser('[^http://host')), [['[^', '<a href="http://host" target="_blank">http://host</a>'], '']);
162
+ assert.deepStrictEqual(inspect(parser('[^a@b')), [['[^', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
163
163
  assert.deepStrictEqual(inspect(parser('[#a*b\nc*]')), [['[', '<a href="/hashtags/a" class="hashtag">#a</a>', '<em>b<br>c</em>', ']'], '']);
164
164
  assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), [['[', '<em>a<br>b</em>', ']', '<a href="/">/</a>'], '']);
165
165
  assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['[', '[', '<em>a<br>b</em>', ']', ']'], '']);
@@ -101,6 +101,19 @@ const isStartTight = reduce((source: string, context: MarkdownParser.Context, ex
101
101
  return source[0].trimStart() !== '';
102
102
  }
103
103
  }, (source, _, except = '') => `${source}\x1E${except}`);
104
+
105
+ export function isStartLooseNodes(nodes: readonly (HTMLElement | string)[]): boolean {
106
+ if (nodes.length === 0) return true;
107
+ for (let i = 0; i < nodes.length; ++i) {
108
+ const node = nodes[i];
109
+ if (isVisible(node)) return true;
110
+ if (typeof node === 'object') {
111
+ if (node.tagName === 'BR') break;
112
+ if (node.className === 'linebreak') break;
113
+ }
114
+ }
115
+ return false;
116
+ }
104
117
  export function isStartTightNodes(nodes: readonly (HTMLElement | string)[]): boolean {
105
118
  if (nodes.length === 0) return true;
106
119
  return isVisible(nodes[0], 0);
@@ -170,7 +183,7 @@ export function trimBlankEnd<T extends HTMLElement | string>(parser: Parser<T>):
170
183
  // }
171
184
  // return nodes;
172
185
  //}
173
- function trimNodeEnd<T extends HTMLElement | string>(nodes: T[]): T[] {
186
+ export function trimNodeEnd<T extends HTMLElement | string>(nodes: T[]): T[] {
174
187
  const skip = nodes.length > 0 &&
175
188
  typeof nodes[nodes.length - 1] === 'object' &&
176
189
  nodes[nodes.length - 1]['className'] === 'indexer'