securemark 0.260.2 → 0.260.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.260.5
4
+
5
+ - Refactoring.
6
+
7
+ ## 0.260.4
8
+
9
+ - Refactoring.
10
+
11
+ ## 0.260.3
12
+
13
+ - Refactoring.
14
+
3
15
  ## 0.260.2
4
16
 
5
17
  - Refactoring.
package/design.md CHANGED
@@ -332,3 +332,7 @@ Data URIは保存および転送容量削減ならびにユーザーおよび管
332
332
  - \<small>: 法的表記を縮小表示すべきでないため削除。
333
333
  - \<sub>: \<small>の代わりに使用されないよう削除。他の構文との相性も悪い。
334
334
  - \<sup>: 同上。
335
+
336
+ ### _ emphasis
337
+
338
+ オートリンクと非常に相性が悪いため不採用。
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.260.2 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.260.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("DOMPurify"), require("Prism"));
@@ -2888,12 +2888,16 @@ const parser_1 = __webpack_require__(6728);
2888
2888
  const memo_1 = __webpack_require__(1090);
2889
2889
 
2890
2890
  function reset(base, parser) {
2891
+ if (!('memo' in base)) {
2892
+ base.memo = global_1.undefined;
2893
+ }
2894
+
2891
2895
  const changes = global_1.Object.entries(base);
2892
2896
  const values = (0, global_1.Array)(changes.length);
2893
2897
  return ({
2894
2898
  source,
2895
2899
  context
2896
- }) => apply(parser, source, (0, alias_1.ObjectCreate)(context), changes, values);
2900
+ }) => apply(parser, source, (0, alias_1.ObjectCreate)(context), changes, values, true);
2897
2901
  }
2898
2902
 
2899
2903
  exports.reset = reset;
@@ -2909,18 +2913,25 @@ function context(base, parser) {
2909
2913
 
2910
2914
  exports.context = context;
2911
2915
 
2912
- function apply(parser, source, context, changes, values) {
2916
+ function apply(parser, source, context, changes, values, reset = false) {
2913
2917
  if (context) for (let i = 0; i < changes.length; ++i) {
2914
2918
  const change = changes[i];
2915
2919
  const prop = change[0];
2916
2920
 
2917
2921
  switch (prop) {
2918
2922
  case 'resources':
2919
- if (prop in context && !(0, alias_1.hasOwnProperty)(context, prop)) break; // @ts-expect-error
2920
-
2923
+ if (!reset) break;
2924
+ if (prop in context && !(0, alias_1.hasOwnProperty)(context, prop)) break;
2921
2925
  context[prop] = (0, alias_1.ObjectCreate)(change[1]);
2922
2926
  break;
2923
2927
 
2928
+ case 'memo':
2929
+ if (!reset) break;
2930
+ context.memo = new memo_1.Memo({
2931
+ targets: context.memo?.targets
2932
+ });
2933
+ break;
2934
+
2924
2935
  default:
2925
2936
  values[i] = context[prop];
2926
2937
  context[prop] = change[1];
@@ -2935,8 +2946,11 @@ function apply(parser, source, context, changes, values) {
2935
2946
  const prop = change[0];
2936
2947
 
2937
2948
  switch (prop) {
2938
- case 'resources':
2939
- break;
2949
+ case 'resources': // @ts-expect-error
2950
+
2951
+ case 'memo':
2952
+ if (!reset) break;
2953
+ // fallthrough
2940
2954
 
2941
2955
  default:
2942
2956
  context[prop] = values[i];
@@ -2953,26 +2967,25 @@ function syntax(syntax, prec, cost, state, parser) {
2953
2967
  }) => {
2954
2968
  if (source === '') return;
2955
2969
  const memo = context.memo ??= new memo_1.Memo();
2956
- context.memorable ??= ~0;
2957
2970
  context.offset ??= 0;
2958
2971
  const position = source.length + context.offset;
2959
- const st0 = context.state ?? 0;
2960
- const st1 = context.state = st0 | state;
2961
- const cache = syntax && memo.get(position, syntax, st1);
2972
+ const stateOuter = context.state ?? 0;
2973
+ const stateInner = context.state = stateOuter | state;
2974
+ const cache = syntax && stateInner & memo.targets && memo.get(position, syntax, stateInner);
2962
2975
  const result = cache ? cache.length === 0 ? global_1.undefined : [cache[0], source.slice(cache[1])] : parser({
2963
2976
  source,
2964
2977
  context
2965
2978
  });
2966
2979
 
2967
- if (syntax && st0 & context.memorable) {
2968
- cache ?? memo.set(position, syntax, st1, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
2980
+ if (syntax && stateOuter & memo.targets) {
2981
+ cache ?? memo.set(position, syntax, stateInner, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
2969
2982
  }
2970
2983
 
2971
- if (result && !st0 && memo.length >= position + 2) {
2984
+ if (result && !stateOuter && memo.length >= position + 2) {
2972
2985
  memo.clear(position + 2);
2973
2986
  }
2974
2987
 
2975
- context.state = st0;
2988
+ context.state = stateOuter;
2976
2989
  return result;
2977
2990
  }));
2978
2991
  }
@@ -3200,8 +3213,11 @@ Object.defineProperty(exports, "__esModule", ({
3200
3213
  exports.Memo = void 0;
3201
3214
 
3202
3215
  class Memo {
3203
- constructor() {
3216
+ constructor({
3217
+ targets = ~0
3218
+ } = {}) {
3204
3219
  this.memory = [];
3220
+ this.targets = targets;
3205
3221
  }
3206
3222
 
3207
3223
  get length() {
@@ -3209,14 +3225,14 @@ class Memo {
3209
3225
  }
3210
3226
 
3211
3227
  get(position, syntax, state) {
3212
- //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
3228
+ //console.log('get', position, syntax, state, this.memory[position - 1]?.[`${syntax}:${state}`]);;
3213
3229
  const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
3214
3230
  return cache?.length === 2 ? [cache[0].slice(), cache[1]] : cache;
3215
3231
  }
3216
3232
 
3217
3233
  set(position, syntax, state, nodes, offset) {
3218
3234
  const record = this.memory[position - 1] ??= {};
3219
- record[`${syntax}:${state}`] = nodes ? [nodes.slice(), offset] : []; //console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
3235
+ record[`${syntax}:${state}`] = nodes ? [nodes.slice(), offset] : []; //console.log('set', position, syntax, state, record[`${syntax}:${state}`]);
3220
3236
  }
3221
3237
 
3222
3238
  clear(position) {
@@ -3224,7 +3240,7 @@ class Memo {
3224
3240
 
3225
3241
  for (let i = position, len = memory.length; i < len; ++i) {
3226
3242
  memory.pop();
3227
- } //console.log('clear', position + this.offset + 1);
3243
+ } //console.log('clear', position + 1);
3228
3244
 
3229
3245
  }
3230
3246
 
@@ -3595,6 +3611,8 @@ const global_1 = __webpack_require__(4128);
3595
3611
 
3596
3612
  const parser_1 = __webpack_require__(6728);
3597
3613
 
3614
+ const memo_1 = __webpack_require__(1090);
3615
+
3598
3616
  const segment_1 = __webpack_require__(9002);
3599
3617
 
3600
3618
  const header_1 = __webpack_require__(5702);
@@ -3616,9 +3634,11 @@ const array_1 = __webpack_require__(8112);
3616
3634
  function bind(target, settings) {
3617
3635
  let context = { ...settings,
3618
3636
  host: settings.host ?? new url_1.ReadonlyURL(global_1.location.pathname, global_1.location.origin),
3619
- memorable: 236
3620
- /* State.backtrackable */
3637
+ memo: new memo_1.Memo({
3638
+ targets: 236
3639
+ /* State.backtrackers */
3621
3640
 
3641
+ })
3622
3642
  };
3623
3643
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3624
3644
  const blocks = [];
@@ -3958,6 +3978,8 @@ const global_1 = __webpack_require__(4128);
3958
3978
 
3959
3979
  const parser_1 = __webpack_require__(6728);
3960
3980
 
3981
+ const memo_1 = __webpack_require__(1090);
3982
+
3961
3983
  const segment_1 = __webpack_require__(9002);
3962
3984
 
3963
3985
  const header_1 = __webpack_require__(5702);
@@ -3988,9 +4010,11 @@ function parse(source, opts = {}, context) {
3988
4010
  ...(context?.resources && {
3989
4011
  resources: context.resources
3990
4012
  }),
3991
- memorable: 236
3992
- /* State.backtrackable */
4013
+ memo: new memo_1.Memo({
4014
+ targets: 236
4015
+ /* State.backtrackers */
3993
4016
 
4017
+ })
3994
4018
  };
3995
4019
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3996
4020
  const node = (0, dom_1.frag)();
@@ -5944,15 +5968,15 @@ const source_1 = __webpack_require__(6743);
5944
5968
 
5945
5969
  const util_1 = __webpack_require__(9437);
5946
5970
 
5947
- exports.autolink = (0, combinator_1.fmap)((0, combinator_1.validate)(/^(?:[@#>0-9A-Za-z]|\S[#>])/, (0, combinator_1.constraint)(2
5971
+ exports.autolink = (0, combinator_1.fmap)((0, combinator_1.validate)(/^(?:[@#>0-9a-z]|\S[#>])/i, (0, combinator_1.constraint)(2
5948
5972
  /* State.autolink */
5949
5973
  , false, (0, combinator_1.syntax)(2
5950
5974
  /* Syntax.autolink */
5951
- , 1, 1, 0
5952
- /* State.none */
5975
+ , 1, 1, ~1
5976
+ /* State.shortcut */
5953
5977
  , (0, combinator_1.some)((0, combinator_1.union)([url_1.url, email_1.email, // Escape unmatched email-like strings.
5954
- (0, source_1.str)(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*(?:@(?:[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?)*/), channel_1.channel, account_1.account, // Escape unmatched account-like strings.
5955
- (0, source_1.str)(/^@+[0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/), // Escape invalid leading characters.
5978
+ (0, source_1.str)(/^[0-9a-z]+(?:[.+_-][0-9a-z]+)*(?:@(?:[0-9a-z]+(?:[.-][0-9a-z]+)*)?)*/i), channel_1.channel, account_1.account, // Escape unmatched account-like strings.
5979
+ (0, source_1.str)(/^@+[0-9a-z]*(?:-[0-9a-z]+)*/i), // Escape invalid leading characters.
5956
5980
  (0, source_1.str)(new RegExp(/^(?:[^\p{C}\p{S}\p{P}\s]|emoji|['_])(?=#)/u.source.replace('emoji', hashtag_1.emoji), 'u')), hashtag_1.hashtag, hashnum_1.hashnum, // Escape unmatched hashtag-like strings.
5957
5981
  (0, source_1.str)(new RegExp(/^#+(?:[^\p{C}\p{S}\p{P}\s]|emoji|['_])*/u.source.replace('emoji', hashtag_1.emoji), 'u')), anchor_1.anchor]))))), ns => ns.length === 1 ? ns : [(0, util_1.stringify)(ns)]);
5958
5982
 
@@ -5980,7 +6004,7 @@ const dom_1 = __webpack_require__(3252); // https://example/@user must be a user
5980
6004
 
5981
6005
  exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1
5982
6006
  /* State.shortcut */
5983
- , false, (0, combinator_1.open)('@', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/), ([source]) => source.length <= 64)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
6007
+ , false, (0, combinator_1.open)('@', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i), ([source]) => source.length <= 253 + 1), (0, source_1.str)(/^[a-z](?:-(?=[0-9a-z])|[0-9a-z]){0,63}/i)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
5984
6008
  class: 'account'
5985
6009
  })]));
5986
6010
 
@@ -6011,7 +6035,7 @@ const dom_1 = __webpack_require__(3252); // Timeline(pseudonym): user/tid
6011
6035
 
6012
6036
  exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('>>', (0, combinator_1.fmap)((0, combinator_1.constraint)(1
6013
6037
  /* State.shortcut */
6014
- , false, (0, combinator_1.focus)(/^>>(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*\/)?[0-9A-Za-z]+(?:-[0-9A-Za-z]+)*(?![0-9A-Za-z@#:])/, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline/')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink])))), ([el]) => [(0, dom_1.define)(el, {
6038
+ , false, (0, combinator_1.focus)(/^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i, (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline/')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink])))), ([el]) => [(0, dom_1.define)(el, {
6015
6039
  class: 'anchor'
6016
6040
  })])));
6017
6041
 
@@ -6070,7 +6094,7 @@ const source_1 = __webpack_require__(6743);
6070
6094
  const dom_1 = __webpack_require__(3252); // https://html.spec.whatwg.org/multipage/input.html
6071
6095
 
6072
6096
 
6073
- exports.email = (0, combinator_1.creation)((0, combinator_1.rewrite)((0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*(?![0-9A-Za-z])/), ([source]) => source.indexOf('@') <= 64 && source.length <= 255), ({
6097
+ exports.email = (0, combinator_1.creation)((0, combinator_1.rewrite)((0, combinator_1.verify)((0, source_1.str)(/^[0-9a-z](?:[.+_-](?=[^\W_])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*(?![0-9a-z])/i), ([source]) => source.length <= 255), ({
6074
6098
  source
6075
6099
  }) => [[(0, dom_1.html)('a', {
6076
6100
  class: 'email',
@@ -6133,7 +6157,7 @@ const dom_1 = __webpack_require__(3252); // https://example/hashtags/a must be a
6133
6157
  exports.emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
6134
6158
  exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.fmap)((0, combinator_1.rewrite)((0, combinator_1.constraint)(1
6135
6159
  /* State.shortcut */
6136
- , false, (0, combinator_1.open)('#', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(new RegExp([/^(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|_(?=[^\p{C}\p{S}\p{P}\s]|emoji)){1,128}/u.source, /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji)|')/u.source].join('').replace(/emoji/g, exports.emoji), 'u')), ([source]) => source.length <= 128)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/hashtags/')}` : `/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
6160
+ , false, (0, combinator_1.open)('#', (0, combinator_1.tails)([(0, combinator_1.verify)((0, source_1.str)(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i), ([source]) => source.length <= 253 + 1), (0, combinator_1.verify)((0, source_1.str)(new RegExp([/^(?=[0-9]{0,127}_?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji))/u.source, /(?:[^\p{C}\p{S}\p{P}\s]|emoji|_(?=[^\p{C}\p{S}\p{P}\s]|emoji)){1,128}/u.source, /(?!_?(?:[^\p{C}\p{S}\p{P}\s]|emoji)|')/u.source].join('').replace(/emoji/g, exports.emoji), 'u')), ([source]) => source.length <= 128)]))), (0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/hashtags/')}` : `/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]))), ([el]) => [(0, dom_1.define)(el, {
6137
6161
  class: 'hashtag'
6138
6162
  }, el.innerText)]));
6139
6163
 
@@ -6502,10 +6526,12 @@ const dom_1 = __webpack_require__(3252);
6502
6526
 
6503
6527
  exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[#', (0, combinator_1.fmap)((0, indexee_1.indexee)((0, combinator_1.surround)('[#', (0, combinator_1.constraint)(32
6504
6528
  /* State.index */
6505
- , false, (0, combinator_1.syntax)(1024
6529
+ , false, (0, combinator_1.syntax)(2048
6506
6530
  /* Syntax.index */
6507
- , 2, 1, 254
6508
- /* State.linkable */
6531
+ , 2, 1, 250
6532
+ /* State.linkers */
6533
+ | 4
6534
+ /* State.media */
6509
6535
  , (0, visibility_1.startTight)((0, combinator_1.open)((0, source_1.stropt)(/^\|?/), (0, visibility_1.trimBlankEnd)((0, combinator_1.some)((0, combinator_1.union)([signature, inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))), ']', false, ([, ns], rest) => [[(0, dom_1.html)('a', (0, dom_1.defrag)(ns))], rest])), ([el]) => [(0, dom_1.define)(el, {
6510
6536
  id: el.id ? null : global_1.undefined,
6511
6537
  class: 'index',
@@ -6707,8 +6733,8 @@ const array_1 = __webpack_require__(8112); // Don't use the symbols already used
6707
6733
  // All syntax surrounded by square brackets shouldn't contain line breaks.
6708
6734
 
6709
6735
 
6710
- exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[:', '[^'], (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, combinator_1.syntax)(0
6711
- /* Syntax.none */
6736
+ exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[:', '[^'], (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, combinator_1.syntax)(1024
6737
+ /* Syntax.placeholder */
6712
6738
  , 2, 1, 0
6713
6739
  /* State.none */
6714
6740
  , (0, visibility_1.startTight)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]]))), (0, source_1.str)(']'), false, ([as, bs], rest) => [[(0, dom_1.html)('span', {
@@ -6928,20 +6954,11 @@ const textlink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(8
6928
6954
  /* State.link */
6929
6955
  , false, (0, combinator_1.syntax)(256
6930
6956
  /* Syntax.link */
6931
- , 2, 10, 254
6932
- /* State.linkable */
6957
+ , 2, 10, 250
6958
+ /* State.linkers */
6959
+ | 4
6960
+ /* State.media */
6933
6961
  , (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]]), ']', 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) => {
6934
- if (content.length !== 0 && (0, visibility_1.trimNode)(content).length === 0) return;
6935
-
6936
- for (let source = (0, util_1.stringify)(content); source;) {
6937
- const result = autolink({
6938
- source,
6939
- context
6940
- });
6941
- if (typeof (0, parser_1.eval)(result, [])[0] === 'object') return;
6942
- source = (0, parser_1.exec)(result, '');
6943
- }
6944
-
6945
6962
  return parse(content, params, rest, context);
6946
6963
  }))));
6947
6964
  const medialink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(8
@@ -6950,10 +6967,8 @@ const medialink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(8
6950
6967
  /* State.media */
6951
6968
  , false, (0, combinator_1.syntax)(256
6952
6969
  /* Syntax.link */
6953
- , 2, 10, 254
6954
- /* State.linkable */
6955
- ^ 4
6956
- /* State.media */
6970
+ , 2, 10, 250
6971
+ /* State.linkers */
6957
6972
  , (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.sequence)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.union)([inline_1.media, inline_1.shortmedia]), ']')), (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) => parse(content, params, rest, context)))));
6958
6973
  exports.unsafelink = (0, combinator_1.lazy)(() => (0, combinator_1.creation)(10, (0, combinator_1.precedence)(2, (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([source_1.unescsource]), ']'), ']')), (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) => parse(content, params, rest, context)))));
6959
6974
  exports.uri = (0, combinator_1.union)([(0, combinator_1.open)(/^[^\S\n]+/, (0, source_1.str)(/^\S+/)), (0, source_1.str)(/^[^\s{}]+/)]);
@@ -6965,8 +6980,37 @@ const autolink = (0, combinator_1.state)(2
6965
6980
  , autolink_1.autolink));
6966
6981
 
6967
6982
  function parse(content, params, rest, context) {
6983
+ if (content.length !== 0 && (0, visibility_1.trimNode)(content).length === 0) return;
6984
+ content = (0, dom_1.defrag)(content);
6985
+
6986
+ for (let source = (0, util_1.stringify)(content); source;) {
6987
+ if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(source)) return;
6988
+ const result = autolink({
6989
+ source,
6990
+ context
6991
+ });
6992
+ if (typeof (0, parser_1.eval)(result, [])[0] === 'object') return;
6993
+ source = (0, parser_1.exec)(result, '');
6994
+ }
6995
+
6968
6996
  const INSECURE_URI = params.shift();
6969
- 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);
6997
+ const uri = 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);
6998
+
6999
+ switch (uri.protocol) {
7000
+ case 'tel:':
7001
+ {
7002
+ const tel = content.length === 0 ? INSECURE_URI : content[0];
7003
+ const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
7004
+
7005
+ if (content.length <= 1 && typeof tel === 'string' && pattern.test(tel) && pattern.test(INSECURE_URI) && tel.replace(/[^+\d]/g, '') === INSECURE_URI.replace(/[^+\d]/g, '')) {
7006
+ break;
7007
+ }
7008
+
7009
+ return;
7010
+ }
7011
+ }
7012
+
7013
+ const el = elem(INSECURE_URI, content, uri, context.host?.origin || global_1.location.origin);
6970
7014
  if (el.className === 'invalid') return [[el], rest];
6971
7015
  return [[(0, dom_1.define)(el, (0, html_1.attributes)('link', [], optspec, params))], rest];
6972
7016
  }
@@ -6991,23 +7035,10 @@ function elem(INSECURE_URI, content, uri, origin) {
6991
7035
  }, content.length === 0 ? decode(INSECURE_URI) : content);
6992
7036
 
6993
7037
  case 'tel:':
6994
- if (content.length === 0) {
6995
- content = [INSECURE_URI];
6996
- }
6997
-
6998
- const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
6999
-
7000
- switch (true) {
7001
- case content.length === 1 && typeof content[0] === 'string' && pattern.test(INSECURE_URI) && pattern.test(content[0]) && INSECURE_URI.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
7002
- return (0, dom_1.html)('a', {
7003
- class: 'tel',
7004
- href: uri.source
7005
- }, content);
7006
- }
7007
-
7008
- type = 'content';
7009
- message = 'Invalid phone number';
7010
- break;
7038
+ return (0, dom_1.html)('a', {
7039
+ class: 'tel',
7040
+ href: uri.source
7041
+ }, content.length === 0 ? [INSECURE_URI] : content);
7011
7042
  }
7012
7043
 
7013
7044
  return (0, dom_1.html)('a', {
@@ -7023,7 +7054,7 @@ function resolve(uri, host, source) {
7023
7054
  case uri.slice(0, 2) === '^/':
7024
7055
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
7025
7056
  return last.includes('.') // isFile
7026
- && /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1)) ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}` : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
7057
+ && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1)) ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}` : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
7027
7058
 
7028
7059
  case host.origin === source.origin && host.pathname === source.pathname:
7029
7060
  case uri.slice(0, 2) === '//':
@@ -7039,9 +7070,16 @@ exports.resolve = resolve;
7039
7070
 
7040
7071
  function decode(uri) {
7041
7072
  if (!uri.includes('%')) return uri;
7073
+ const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i)?.[0] ?? '';
7042
7074
 
7043
7075
  try {
7044
- uri = (0, global_1.decodeURI)(uri);
7076
+ let path = (0, global_1.decodeURI)(uri.slice(origin.length));
7077
+
7078
+ if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(path)) {
7079
+ path = uri.slice(origin.length);
7080
+ }
7081
+
7082
+ uri = origin + path;
7045
7083
  } finally {
7046
7084
  return uri.replace(/\s+/g, global_1.encodeURI);
7047
7085
  }
@@ -7160,8 +7198,8 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '
7160
7198
  /* State.media */
7161
7199
  , false, (0, combinator_1.syntax)(64
7162
7200
  /* Syntax.media */
7163
- , 2, 10, 0
7164
- /* State.none */
7201
+ , 2, 10, ~8
7202
+ /* State.link */
7165
7203
  , (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', [[/^\\?\n/, 9]]), ']', 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) => {
7166
7204
  const INSECURE_URI = params.shift();
7167
7205
  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);
@@ -7183,7 +7221,7 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '
7183
7221
  if (context.state & 8
7184
7222
  /* State.link */
7185
7223
  ) return [[el], rest];
7186
- if (cache && cache.tagName !== 'IMG') return (0, combinator_1.creation)(10, (..._) => [[el], rest])({
7224
+ if (cache && cache.tagName !== 'IMG') return (0, combinator_1.creation)(10, _ => [[el], rest])({
7187
7225
  source: '!',
7188
7226
  context
7189
7227
  });
@@ -7267,7 +7305,7 @@ const dom_1 = __webpack_require__(3252);
7267
7305
 
7268
7306
  exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('[[', (0, combinator_1.constraint)(64
7269
7307
  /* State.reference */
7270
- , false, (0, combinator_1.syntax)(4096
7308
+ , false, (0, combinator_1.syntax)(8192
7271
7309
  /* Syntax.reference */
7272
7310
  , 6, 1, 128
7273
7311
  /* State.annotation */
@@ -7323,11 +7361,17 @@ const dom_1 = __webpack_require__(3252);
7323
7361
 
7324
7362
  const array_1 = __webpack_require__(8112);
7325
7363
 
7326
- exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[', (0, combinator_1.syntax)(0
7327
- /* Syntax.none */
7328
- , 2, 1, 0
7329
- /* State.none */
7330
- , (0, combinator_1.fmap)((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, visibility_1.isStartTightNodes)(texts)), ([texts, rubies]) => {
7364
+ exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[', (0, combinator_1.syntax)(512
7365
+ /* Syntax.ruby */
7366
+ , 2, 1, -1
7367
+ /* State.all */
7368
+ , (0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0, source_1.str)(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ']'), (0, combinator_1.surround)('(', (0, source_1.str)(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ')')]), ([texts, rubies], _, context) => [(0, parser_1.eval)(text({
7369
+ source: texts,
7370
+ context
7371
+ }), [])[0] ?? '', (0, parser_1.eval)(text({
7372
+ source: rubies,
7373
+ context
7374
+ }), [])[0] ?? '']), ([texts, rubies]) => texts && rubies && (0, visibility_1.isStartTightNodes)(texts)), ([texts, rubies]) => {
7331
7375
  texts[texts.length - 1] === '' && texts.pop();
7332
7376
 
7333
7377
  switch (true) {
@@ -7487,8 +7531,8 @@ const array_1 = __webpack_require__(8112);
7487
7531
 
7488
7532
  exports.template = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('{{', (0, combinator_1.syntax)(0
7489
7533
  /* Syntax.none */
7490
- , 2, 1, 0
7491
- /* State.none */
7534
+ , 2, 1, -1
7535
+ /* State.all */
7492
7536
  , (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}')), '}}', true, ([, ns = []], rest) => [[(0, dom_1.html)('span', {
7493
7537
  class: 'template'
7494
7538
  }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
package/markdown.d.ts CHANGED
@@ -829,8 +829,8 @@ export namespace MarkdownParser {
829
829
  // [AB](a b)
830
830
  Inline<'ruby'>,
831
831
  Parser<HTMLElement, Context, [
832
- RubyParser.TextParser,
833
- RubyParser.TextParser,
832
+ SourceParser.StrParser,
833
+ SourceParser.StrParser,
834
834
  ]> {
835
835
  }
836
836
  export namespace RubyParser {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.260.2",
3
+ "version": "0.260.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",
@@ -34,11 +34,11 @@
34
34
  "@types/mocha": "9.1.1",
35
35
  "@types/power-assert": "1.5.8",
36
36
  "@types/prismjs": "1.26.0",
37
- "@typescript-eslint/parser": "^5.29.0",
37
+ "@typescript-eslint/parser": "^5.30.7",
38
38
  "babel-loader": "^8.2.5",
39
39
  "babel-plugin-unassert": "^3.2.0",
40
- "concurrently": "^7.2.2",
41
- "eslint": "^8.18.0",
40
+ "concurrently": "^7.3.0",
41
+ "eslint": "^8.20.0",
42
42
  "eslint-plugin-redos": "^4.4.1",
43
43
  "eslint-webpack-plugin": "^3.2.0",
44
44
  "glob": "^8.0.3",
@@ -49,7 +49,7 @@
49
49
  "karma-mocha": "^2.0.1",
50
50
  "karma-power-assert": "^1.0.0",
51
51
  "mocha": "^10.0.0",
52
- "npm-check-updates": "^14.1.1",
52
+ "npm-check-updates": "^15.3.4",
53
53
  "semver": "^7.3.7",
54
54
  "spica": "0.0.573",
55
55
  "ts-loader": "^9.3.1",
@@ -1,4 +1,8 @@
1
1
  export class Memo {
2
+ constructor({ targets = ~0 } = {}) {
3
+ this.targets = targets;
4
+ }
5
+ public readonly targets: number;
2
6
  private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
3
7
  public get length(): number {
4
8
  return this.memory.length;
@@ -8,7 +12,7 @@ export class Memo {
8
12
  syntax: number,
9
13
  state: number,
10
14
  ): readonly [any[], number] | readonly [] | undefined {
11
- //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
15
+ //console.log('get', position, syntax, state, this.memory[position - 1]?.[`${syntax}:${state}`]);;
12
16
  const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
13
17
  return cache?.length === 2
14
18
  ? [cache[0].slice(), cache[1]]
@@ -26,13 +30,13 @@ export class Memo {
26
30
  record[`${syntax}:${state}`] = nodes
27
31
  ? [nodes.slice(), offset]
28
32
  : [];
29
- //console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
33
+ //console.log('set', position, syntax, state, record[`${syntax}:${state}`]);
30
34
  }
31
35
  public clear(position: number): void {
32
36
  const memory = this.memory;
33
37
  for (let i = position, len = memory.length; i < len; ++i) {
34
38
  memory.pop();
35
39
  }
36
- //console.log('clear', position + this.offset + 1);
40
+ //console.log('clear', position + 1);
37
41
  }
38
42
  }
@@ -5,12 +5,15 @@ import { Memo } from './context/memo';
5
5
 
6
6
  export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
7
7
  export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
8
+ if (!('memo' in base)) {
9
+ base.memo = undefined;
10
+ }
8
11
  assert(Object.getPrototypeOf(base) === Object.prototype);
9
12
  assert(Object.freeze(base));
10
13
  const changes = Object.entries(base);
11
14
  const values = Array(changes.length);
12
15
  return ({ source, context }) =>
13
- apply(parser, source, ObjectCreate(context), changes, values);
16
+ apply(parser, source, ObjectCreate(context), changes, values, true);
14
17
  }
15
18
 
16
19
  export function context<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
@@ -23,18 +26,22 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
23
26
  apply(parser, source, context, changes, values);
24
27
  }
25
28
 
26
- function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[]): Result<Tree<P>>;
27
- function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[]): Result<T> {
29
+ function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[], reset?: boolean): Result<Tree<P>>;
30
+ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[], reset = false): Result<T> {
28
31
  if (context) for (let i = 0; i < changes.length; ++i) {
29
32
  const change = changes[i];
30
33
  const prop = change[0];
31
34
  switch (prop) {
32
35
  case 'resources':
36
+ if (!reset) break;
33
37
  assert(typeof change[1] === 'object');
34
38
  assert(context[prop] || !(prop in context));
35
39
  if (prop in context && !hasOwnProperty(context, prop)) break;
36
- // @ts-expect-error
37
- context[prop] = ObjectCreate(change[1]);
40
+ context[prop as string] = ObjectCreate(change[1]);
41
+ break;
42
+ case 'memo':
43
+ if (!reset) break;
44
+ context.memo = new Memo({ targets: context.memo?.targets });
38
45
  break;
39
46
  default:
40
47
  values[i] = context[prop];
@@ -47,7 +54,10 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
47
54
  const prop = change[0];
48
55
  switch (prop) {
49
56
  case 'resources':
50
- break;
57
+ // @ts-expect-error
58
+ case 'memo':
59
+ if (!reset) break;
60
+ // fallthrough
51
61
  default:
52
62
  context[prop] = values[i];
53
63
  values[i] = undefined;
@@ -61,26 +71,25 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
61
71
  return creation(cost, precedence(prec, ({ source, context }) => {
62
72
  if (source === '') return;
63
73
  const memo = context.memo ??= new Memo();
64
- context.memorable ??= ~0;
65
74
  context.offset ??= 0;
66
75
  const position = source.length + context.offset!;
67
- const st0 = context.state ?? 0;
68
- const st1 = context.state = st0 | state;
69
- const cache = syntax && memo.get(position, syntax, st1);
76
+ const stateOuter = context.state ?? 0;
77
+ const stateInner = context.state = stateOuter | state;
78
+ const cache = syntax && stateInner & memo.targets && memo.get(position, syntax, stateInner);
70
79
  const result: Result<T> = cache
71
80
  ? cache.length === 0
72
81
  ? undefined
73
82
  : [cache[0], source.slice(cache[1])]
74
83
  : parser!({ source, context });
75
- if (syntax && st0 & context.memorable!) {
76
- cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
77
- assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
84
+ if (syntax && stateOuter & memo.targets) {
85
+ cache ?? memo.set(position, syntax, stateInner, eval(result), source.length - exec(result, '').length);
86
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, stateInner));
78
87
  }
79
- if (result && !st0 && memo.length! >= position + 2) {
80
- assert(!(st0 & context.memorable!));
88
+ if (result && !stateOuter && memo.length! >= position + 2) {
89
+ assert(!(stateOuter & memo.targets));
81
90
  memo.clear(position + 2);
82
91
  }
83
- context.state = st0;
92
+ context.state = stateOuter;
84
93
  return result;
85
94
  }));
86
95
  }
@@ -20,7 +20,6 @@ export interface Ctx {
20
20
  precedence?: number;
21
21
  delimiters?: Delimiters;
22
22
  state?: number;
23
- memorable?: number;
24
23
  memo?: Memo;
25
24
  }
26
25
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
@@ -2,6 +2,7 @@ import { undefined, location } from 'spica/global';
2
2
  import { ParserSettings, Progress } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { Memo } from '../../combinator/data/parser/context/memo';
5
6
  import { segment, validate, MAX_INPUT_SIZE } from '../segment';
6
7
  import { header } from '../header';
7
8
  import { block } from '../block';
@@ -24,7 +25,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
24
25
  let context: MarkdownParser.Context = {
25
26
  ...settings,
26
27
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
- memorable: State.backtrackable,
28
+ memo: new Memo({ targets: State.backtrackers }),
28
29
  };
29
30
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
30
31
  assert(!settings.id);
@@ -2,6 +2,7 @@ import { location } from 'spica/global';
2
2
  import { ParserOptions } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { Memo } from '../../combinator/data/parser/context/memo';
5
6
  import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
6
7
  import { header } from '../header';
7
8
  import { block } from '../block';
@@ -30,7 +31,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
30
31
  ...context?.resources && {
31
32
  resources: context.resources,
32
33
  },
33
- memorable: State.backtrackable,
34
+ memo: new Memo({ targets: State.backtrackers }),
34
35
  };
35
36
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
36
37
  const node = frag();
@@ -1,8 +1,9 @@
1
1
  export const enum Syntax {
2
- reference = 1 << 12,
3
- comment = 1 << 11,
4
- index = 1 << 10,
5
- placeholder = 1 << 9,
2
+ reference = 1 << 13,
3
+ comment = 1 << 12,
4
+ index = 1 << 11,
5
+ placeholder = 1 << 10,
6
+ ruby = 1 << 9,
6
7
  link = 1 << 8,
7
8
  bracket = 1 << 7,
8
9
  media = 1 << 6,
@@ -25,15 +26,15 @@ export const enum State {
25
26
  autolink = 1 << 1,
26
27
  shortcut = 1 << 0,
27
28
  none = 0,
28
- linkable = 0
29
+ all = ~0,
30
+ linkers = 0
29
31
  | State.annotation
30
32
  | State.reference
31
33
  | State.index
32
34
  | State.label
33
35
  | State.link
34
- | State.media
35
36
  | State.autolink,
36
- backtrackable = 0
37
+ backtrackers = 0
37
38
  | State.annotation
38
39
  | State.reference
39
40
  | State.index
@@ -13,11 +13,9 @@ export const account: AutolinkParser.AccountParser = lazy(() => fmap(rewrite(
13
13
  '@',
14
14
  tails([
15
15
  verify(
16
- str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
16
+ str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
17
17
  ([source]) => source.length <= 253 + 1),
18
- verify(
19
- str(/^[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
20
- ([source]) => source.length <= 64),
18
+ str(/^[a-z](?:-(?=[0-9a-z])|[0-9a-z]){0,63}/i),
21
19
  ]))),
22
20
  convert(
23
21
  source =>
@@ -16,7 +16,7 @@ import { define } from 'typed-dom/dom';
16
16
  export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>', fmap(
17
17
  constraint(State.shortcut, false,
18
18
  focus(
19
- /^>>(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*\/)?[0-9A-Za-z]+(?:-[0-9A-Za-z]+)*(?![0-9A-Za-z@#:])/,
19
+ /^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i,
20
20
  convert(
21
21
  source =>
22
22
  `[${source}]{ ${
@@ -21,6 +21,7 @@ describe('Unit: parser/inline/autolink/email', () => {
21
21
  assert.deepStrictEqual(inspect(parser('a@@')), [['a@@'], '']);
22
22
  assert.deepStrictEqual(inspect(parser('a@@b')), [['a@@b'], '']);
23
23
  assert.deepStrictEqual(inspect(parser('a+@b')), [['a'], '+@b']);
24
+ assert.deepStrictEqual(inspect(parser('a__b@c')), [['a'], '__b@c']);
24
25
  assert.deepStrictEqual(inspect(parser('a..b@c')), [['a'], '..b@c']);
25
26
  assert.deepStrictEqual(inspect(parser('a++b@c')), [['a'], '++b@c']);
26
27
  assert.deepStrictEqual(inspect(parser(`a@${'b'.repeat(64)}`)), [[`a@${'b'.repeat(64)}`], '']);
@@ -6,6 +6,6 @@ import { html } from 'typed-dom/dom';
6
6
  // https://html.spec.whatwg.org/multipage/input.html
7
7
 
8
8
  export const email: AutolinkParser.EmailParser = creation(rewrite(verify(
9
- str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*@[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*(?![0-9A-Za-z])/),
10
- ([source]) => source.indexOf('@') <= 64 && source.length <= 255),
9
+ str(/^[0-9a-z](?:[.+_-](?=[^\W_])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*(?![0-9a-z])/i),
10
+ ([source]) => source.length <= 255),
11
11
  ({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], '']));
@@ -16,7 +16,7 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => fmap(rewrite(
16
16
  '#',
17
17
  tails([
18
18
  verify(
19
- str(/^[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-(?=\w)){0,61}[0-9A-Za-z])?)*\//),
19
+ str(/^[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=\w)){0,61}[0-9a-z])?)*\//i),
20
20
  ([source]) => source.length <= 253 + 1),
21
21
  verify(
22
22
  str(new RegExp([
@@ -12,18 +12,18 @@ import { Syntax, State } from '../context';
12
12
  import { stringify } from '../util';
13
13
 
14
14
  export const autolink: AutolinkParser = fmap(
15
- validate(/^(?:[@#>0-9A-Za-z]|\S[#>])/,
15
+ validate(/^(?:[@#>0-9a-z]|\S[#>])/i,
16
16
  constraint(State.autolink, false,
17
- syntax(Syntax.autolink, 1, 1, State.none,
17
+ syntax(Syntax.autolink, 1, 1, ~State.shortcut,
18
18
  some(union([
19
19
  url,
20
20
  email,
21
21
  // Escape unmatched email-like strings.
22
- str(/^[0-9A-Za-z]+(?:[.+_-][0-9A-Za-z]+)*(?:@(?:[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?)*/),
22
+ str(/^[0-9a-z]+(?:[.+_-][0-9a-z]+)*(?:@(?:[0-9a-z]+(?:[.-][0-9a-z]+)*)?)*/i),
23
23
  channel,
24
24
  account,
25
25
  // Escape unmatched account-like strings.
26
- str(/^@+[0-9A-Za-z]*(?:-[0-9A-Za-z]+)*/),
26
+ str(/^@+[0-9a-z]*(?:-[0-9a-z]+)*/i),
27
27
  // Escape invalid leading characters.
28
28
  str(new RegExp(/^(?:[^\p{C}\p{S}\p{P}\s]|emoji|['_])(?=#)/u.source.replace('emoji', emoji), 'u')),
29
29
  hashtag,
@@ -13,7 +13,7 @@ import IndexParser = ExtensionParser.IndexParser;
13
13
  export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surround(
14
14
  '[#',
15
15
  constraint(State.index, false,
16
- syntax(Syntax.index, 2, 1, State.linkable,
16
+ syntax(Syntax.index, 2, 1, State.linkers | State.media,
17
17
  startTight(
18
18
  open(stropt(/^\|?/), trimBlankEnd(some(union([
19
19
  signature,
@@ -13,7 +13,7 @@ import { unshift } from 'spica/array';
13
13
 
14
14
  export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate(['[:', '[^'], surround(
15
15
  str(/^\[[:^]/),
16
- syntax(Syntax.none, 2, 1, State.none,
16
+ syntax(Syntax.placeholder, 2, 1, State.none,
17
17
  startTight(some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]]))),
18
18
  str(']'), false,
19
19
  ([as, bs], rest) => [[
@@ -25,11 +25,18 @@ describe('Unit: parser/inline/link', () => {
25
25
  assert.deepStrictEqual(inspect(parser('[https://host]{http://host}')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('[[]{http://host}.com]{http://host}')), undefined);
27
27
  assert.deepStrictEqual(inspect(parser('[[]{http://host/a}b]{http://host/ab}')), undefined);
28
- assert.deepStrictEqual(inspect(parser('[0987654321]{tel:1234567890}')), [['<a class="invalid">0987654321</a>'], '']);
29
- assert.deepStrictEqual(inspect(parser('[1234567890-]{tel:1234567890}')), [[`<a class="invalid">1234567890-</a>`], '']);
30
- assert.deepStrictEqual(inspect(parser('[-1234567890]{tel:1234567890}')), [[`<a class="invalid">-1234567890</a>`], '']);
31
- assert.deepStrictEqual(inspect(parser('[123456789a]{tel:1234567890}')), [['<a class="invalid">123456789a</a>'], '']);
32
- assert.deepStrictEqual(inspect(parser('[1234567890]{tel:ttel:1234567890}')), [['<a class="invalid">1234567890</a>'], '']);
28
+ assert.deepStrictEqual(inspect(parser('{http%73://host}')), [['<a class="url" href="http%73://host">http%73://host</a>'], '']);
29
+ assert.deepStrictEqual(inspect(parser('{http://a%C3%A1}')), [['<a class="url" href="http://a%C3%A1" target="_blank">http://a%C3%A1</a>'], '']);
30
+ assert.deepStrictEqual(inspect(parser('[http://á]{http://evil}')), undefined);
31
+ assert.deepStrictEqual(inspect(parser('[xxx://á]{http://evil}')), undefined);
32
+ assert.deepStrictEqual(inspect(parser('[mailto:á]{http://evil}')), undefined);
33
+ assert.deepStrictEqual(inspect(parser('[file:///]{http://evil}')), undefined);
34
+ assert.deepStrictEqual(inspect(parser('[.http://á]{http://evil}')), undefined);
35
+ assert.deepStrictEqual(inspect(parser('[0987654321]{tel:1234567890}')), undefined);
36
+ assert.deepStrictEqual(inspect(parser('[1234567890-]{tel:1234567890}')), undefined);
37
+ assert.deepStrictEqual(inspect(parser('[-1234567890]{tel:1234567890}')), undefined);
38
+ assert.deepStrictEqual(inspect(parser('[123456789a]{tel:1234567890}')), undefined);
39
+ assert.deepStrictEqual(inspect(parser('[1234567890]{tel:ttel:1234567890}')), undefined);
33
40
  //assert.deepStrictEqual(inspect(parser('[#a]{b}')), undefined);
34
41
  //assert.deepStrictEqual(inspect(parser('[\\#a]{b}')), undefined);
35
42
  //assert.deepStrictEqual(inspect(parser('[c #a]{b}')), undefined);
@@ -25,7 +25,7 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], union([
25
25
 
26
26
  const textlink: LinkParser.TextLinkParser = lazy(() =>
27
27
  constraint(State.link, false,
28
- syntax(Syntax.link, 2, 10, State.linkable,
28
+ syntax(Syntax.link, 2, 10, State.linkers | State.media,
29
29
  bind(reverse(tails([
30
30
  dup(surround(
31
31
  '[',
@@ -36,18 +36,12 @@ const textlink: LinkParser.TextLinkParser = lazy(() =>
36
36
  ])),
37
37
  ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
38
38
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
39
- if (content.length !== 0 && trimNode(content).length === 0) return;
40
- for (let source = stringify(content); source;) {
41
- const result = autolink({ source, context });
42
- if (typeof eval(result, [])[0] === 'object') return;
43
- source = exec(result, '');
44
- }
45
39
  return parse(content, params, rest, context);
46
40
  }))));
47
41
 
48
42
  const medialink: LinkParser.MediaLinkParser = lazy(() =>
49
43
  constraint(State.link | State.media, false,
50
- syntax(Syntax.link, 2, 10, State.linkable ^ State.media,
44
+ syntax(Syntax.link, 2, 10, State.linkers,
51
45
  bind(reverse(sequence([
52
46
  dup(surround(
53
47
  '[',
@@ -91,15 +85,40 @@ function parse(
91
85
  ): Result<HTMLAnchorElement, MarkdownParser.Context> {
92
86
  assert(params.length > 0);
93
87
  assert(params.every(p => typeof p === 'string'));
88
+ if (content.length !== 0 && trimNode(content).length === 0) return;
89
+ content = defrag(content);
90
+ for (let source = stringify(content); source;) {
91
+ if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(source)) return;
92
+ const result = autolink({ source, context });
93
+ if (typeof eval(result, [])[0] === 'object') return;
94
+ source = exec(result, '');
95
+ }
94
96
  const INSECURE_URI = params.shift()!;
95
97
  assert(INSECURE_URI === INSECURE_URI.trim());
96
98
  assert(!INSECURE_URI.match(/\s/));
99
+ const uri = new ReadonlyURL(
100
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
101
+ context.host?.href || location.href);
102
+ switch (uri.protocol) {
103
+ case 'tel:': {
104
+ const tel = content.length === 0
105
+ ? INSECURE_URI
106
+ : content[0];
107
+ const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
108
+ if (content.length <= 1 &&
109
+ typeof tel === 'string' &&
110
+ pattern.test(tel) &&
111
+ pattern.test(INSECURE_URI) &&
112
+ tel.replace(/[^+\d]/g, '') === INSECURE_URI.replace(/[^+\d]/g, '')) {
113
+ break;
114
+ }
115
+ return;
116
+ }
117
+ }
97
118
  const el = elem(
98
119
  INSECURE_URI,
99
- defrag(content),
100
- new ReadonlyURL(
101
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
102
- context.host?.href || location.href),
120
+ content,
121
+ uri,
103
122
  context.host?.origin || location.origin);
104
123
  if (el.className === 'invalid') return [[el], rest];
105
124
  return [[define(el, attributes('link', [], optspec, params))], rest];
@@ -137,21 +156,15 @@ function elem(
137
156
  ? decode(INSECURE_URI)
138
157
  : content);
139
158
  case 'tel:':
140
- if (content.length === 0) {
141
- content = [INSECURE_URI];
142
- }
143
- const pattern = /^(?:tel:)?(?:\+(?!0))?\d+(?:-\d+)*$/i;
144
- switch (true) {
145
- case content.length === 1
146
- && typeof content[0] === 'string'
147
- && pattern.test(INSECURE_URI)
148
- && pattern.test(content[0])
149
- && INSECURE_URI.replace(/[^+\d]/g, '') === content[0].replace(/[^+\d]/g, ''):
150
- return html('a', { class: 'tel', href: uri.source }, content);
151
- }
152
- type = 'content';
153
- message = 'Invalid phone number';
154
- break;
159
+ assert(content.length <= 1);
160
+ return html('a',
161
+ {
162
+ class: 'tel',
163
+ href: uri.source,
164
+ },
165
+ content.length === 0
166
+ ? [INSECURE_URI]
167
+ : content);
155
168
  }
156
169
  return html('a',
157
170
  {
@@ -172,7 +185,7 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
172
185
  case uri.slice(0, 2) === '^/':
173
186
  const last = host.pathname.slice(host.pathname.lastIndexOf('/') + 1);
174
187
  return last.includes('.') // isFile
175
- && /^[0-9]*[A-Za-z][0-9A-Za-z]*$/.test(last.slice(last.lastIndexOf('.') + 1))
188
+ && /^[0-9]*[a-z][0-9a-z]*$/i.test(last.slice(last.lastIndexOf('.') + 1))
176
189
  ? `${host.pathname.slice(0, -last.length)}${uri.slice(2)}`
177
190
  : `${host.pathname.replace(/\/?$/, '/')}${uri.slice(2)}`;
178
191
  case host.origin === source.origin
@@ -189,8 +202,13 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
189
202
 
190
203
  function decode(uri: string): string {
191
204
  if (!uri.includes('%')) return uri;
205
+ const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i)?.[0] ?? '';
192
206
  try {
193
- uri = decodeURI(uri);
207
+ let path = decodeURI(uri.slice(origin.length));
208
+ if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(path)) {
209
+ path = uri.slice(origin.length);
210
+ }
211
+ uri = origin + path;
194
212
  }
195
213
  finally {
196
214
  return uri.replace(/\s+/g, encodeURI);
@@ -21,7 +21,7 @@ Object.setPrototypeOf(optspec, null);
21
21
  export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
22
22
  '!',
23
23
  constraint(State.media, false,
24
- syntax(Syntax.media, 2, 10, State.none,
24
+ syntax(Syntax.media, 2, 10, ~State.link,
25
25
  bind(verify(fmap(tails([
26
26
  dup(surround(
27
27
  '[',
@@ -55,7 +55,7 @@ export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
55
55
  el.style.aspectRatio = el.getAttribute('aspect-ratio')!;
56
56
  }
57
57
  if (context.state! & State.link) return [[el], rest];
58
- if (cache && cache.tagName !== 'IMG') return creation(10, (..._) => [[el!], rest])({ source: '!', context });
58
+ if (cache && cache.tagName !== 'IMG') return creation(10, _ => [[el!], rest])({ source: '!', context });
59
59
  return fmap(
60
60
  unsafelink as MediaParser,
61
61
  ([link]) => [define(link, { class: null, target: '_blank' }, [el])])
@@ -1,20 +1,24 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { RubyParser } from '../inline';
3
3
  import { eval, exec } from '../../combinator/data/parser';
4
- import { sequence, syntax, creation, validate, verify, focus, surround, lazy, fmap } from '../../combinator';
4
+ import { sequence, syntax, creation, validate, verify, surround, lazy, fmap } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
- import { text as txt } from '../source';
6
+ import { text as txt, str } from '../source';
7
7
  import { Syntax, State } from '../context';
8
8
  import { isStartTightNodes } from '../visibility';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
  import { unshift, push } from 'spica/array';
11
11
 
12
- export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.none, 2, 1, State.none, fmap(verify(
12
+ export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.ruby, 2, 1, State.all, fmap(verify(fmap(
13
13
  sequence([
14
- surround('[', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=]\()/, text), ']'),
15
- surround('(', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=\))/, text), ')'),
14
+ surround('[', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ']'),
15
+ surround('(', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ')'),
16
16
  ]),
17
- ([texts]) => isStartTightNodes(texts)),
17
+ ([texts, rubies], _, context) => [
18
+ eval(text({ source: texts, context }), [])[0] ?? '',
19
+ eval(text({ source: rubies, context }), [])[0] ?? '',
20
+ ]),
21
+ ([texts, rubies]) => texts && rubies && isStartTightNodes(texts)),
18
22
  ([texts, rubies]) => {
19
23
  texts[texts.length - 1] === '' && texts.pop();
20
24
  switch (true) {
@@ -7,7 +7,7 @@ import { html } from 'typed-dom/dom';
7
7
  import { unshift } from 'spica/array';
8
8
 
9
9
  export const template: TemplateParser = lazy(() => surround(
10
- '{{', syntax(Syntax.none, 2, 1, State.none, some(union([bracket, escsource]), '}')), '}}', true,
10
+ '{{', syntax(Syntax.none, 2, 1, State.all, some(union([bracket, escsource]), '}')), '}}', true,
11
11
  ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
12
12
 
13
13
  const bracket: TemplateParser.BracketParser = lazy(() => creation(union([