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 +12 -0
- package/design.md +4 -0
- package/dist/index.js +129 -85
- package/markdown.d.ts +2 -2
- package/package.json +5 -5
- package/src/combinator/data/parser/context/memo.ts +7 -3
- package/src/combinator/data/parser/context.ts +25 -16
- package/src/combinator/data/parser.ts +0 -1
- package/src/parser/api/bind.ts +2 -1
- package/src/parser/api/parse.ts +2 -1
- package/src/parser/context.ts +8 -7
- package/src/parser/inline/autolink/account.ts +2 -4
- package/src/parser/inline/autolink/anchor.ts +1 -1
- package/src/parser/inline/autolink/email.test.ts +1 -0
- package/src/parser/inline/autolink/email.ts +2 -2
- package/src/parser/inline/autolink/hashtag.ts +1 -1
- package/src/parser/inline/autolink.ts +4 -4
- package/src/parser/inline/extension/index.ts +1 -1
- package/src/parser/inline/extension/placeholder.ts +1 -1
- package/src/parser/inline/link.test.ts +12 -5
- package/src/parser/inline/link.ts +47 -29
- package/src/parser/inline/media.ts +2 -2
- package/src/parser/inline/ruby.ts +10 -6
- package/src/parser/inline/template.ts +1 -1
package/CHANGELOG.md
CHANGED
package/design.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! securemark v0.260.
|
|
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 (
|
|
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
|
-
|
|
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
|
|
2960
|
-
const
|
|
2961
|
-
const cache = syntax && memo.get(position, syntax,
|
|
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 &&
|
|
2968
|
-
cache ?? memo.set(position, syntax,
|
|
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 && !
|
|
2984
|
+
if (result && !stateOuter && memo.length >= position + 2) {
|
|
2972
2985
|
memo.clear(position + 2);
|
|
2973
2986
|
}
|
|
2974
2987
|
|
|
2975
|
-
context.state =
|
|
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
|
|
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
|
|
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 +
|
|
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
|
-
|
|
3620
|
-
|
|
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
|
-
|
|
3992
|
-
|
|
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-
|
|
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,
|
|
5952
|
-
/* State.
|
|
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-
|
|
5955
|
-
(0, source_1.str)(/^@+[0-
|
|
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-
|
|
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)(/^>>(?:[
|
|
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-
|
|
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-
|
|
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)(
|
|
6529
|
+
, false, (0, combinator_1.syntax)(2048
|
|
6506
6530
|
/* Syntax.index */
|
|
6507
|
-
, 2, 1,
|
|
6508
|
-
/* State.
|
|
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)(
|
|
6711
|
-
/* Syntax.
|
|
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,
|
|
6932
|
-
/* State.
|
|
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,
|
|
6954
|
-
/* State.
|
|
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
|
|
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
|
-
|
|
6995
|
-
|
|
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]*[
|
|
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
|
-
|
|
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,
|
|
7164
|
-
/* State.
|
|
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,
|
|
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)(
|
|
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)(
|
|
7327
|
-
/* Syntax.
|
|
7328
|
-
, 2, 1,
|
|
7329
|
-
/* State.
|
|
7330
|
-
, (0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0,
|
|
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,
|
|
7491
|
-
/* State.
|
|
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
|
-
|
|
833
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
41
|
-
"eslint": "^8.
|
|
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": "^
|
|
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
|
|
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
|
|
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 +
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
|
68
|
-
const
|
|
69
|
-
const cache = syntax && memo.get(position, syntax,
|
|
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 &&
|
|
76
|
-
cache ?? memo.set(position, syntax,
|
|
77
|
-
assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax,
|
|
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 && !
|
|
80
|
-
assert(!(
|
|
88
|
+
if (result && !stateOuter && memo.length! >= position + 2) {
|
|
89
|
+
assert(!(stateOuter & memo.targets));
|
|
81
90
|
memo.clear(position + 2);
|
|
82
91
|
}
|
|
83
|
-
context.state =
|
|
92
|
+
context.state = stateOuter;
|
|
84
93
|
return result;
|
|
85
94
|
}));
|
|
86
95
|
}
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -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
|
-
|
|
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();
|
package/src/parser/context.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export const enum Syntax {
|
|
2
|
-
reference = 1 <<
|
|
3
|
-
comment = 1 <<
|
|
4
|
-
index = 1 <<
|
|
5
|
-
placeholder = 1 <<
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
/^>>(?:[
|
|
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-
|
|
10
|
-
([source]) => source.
|
|
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-
|
|
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-
|
|
15
|
+
validate(/^(?:[@#>0-9a-z]|\S[#>])/i,
|
|
16
16
|
constraint(State.autolink, false,
|
|
17
|
-
syntax(Syntax.autolink, 1, 1, State.
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
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('
|
|
29
|
-
assert.deepStrictEqual(inspect(parser('
|
|
30
|
-
assert.deepStrictEqual(inspect(parser('[
|
|
31
|
-
assert.deepStrictEqual(inspect(parser('[
|
|
32
|
-
assert.deepStrictEqual(inspect(parser('[
|
|
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.
|
|
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.
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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]*[
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
12
|
+
export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.ruby, 2, 1, State.all, fmap(verify(fmap(
|
|
13
13
|
sequence([
|
|
14
|
-
surround('[',
|
|
15
|
-
surround('(',
|
|
14
|
+
surround('[', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ']'),
|
|
15
|
+
surround('(', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ')'),
|
|
16
16
|
]),
|
|
17
|
-
([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.
|
|
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([
|