From cdb9292c3756c0e2bbf598f4559d4ffde0eb5c4e Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 08:09:19 +0100 Subject: [PATCH 01/25] core parser: modernize code - simpler function definitions --- src/core/parser.js | 46 +++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/core/parser.js b/src/core/parser.js index ae760d6ca..883bd3fc7 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -1,10 +1,4 @@ -/** - * Patterns parser - Argument parser - * - * Copyright 2012-2013 Florian Friesdorf - * Copyright 2012-2013 Simplon B.V. - Wichert Akkerman - */ - +// Patterns argument parser import $ from "jquery"; import _ from "underscore"; import utils from "./utils.js"; @@ -27,13 +21,13 @@ ArgumentParser.prototype = { named_param_pattern: /^\s*([a-z][a-z0-9\-]*)\s*:(.*)/i, token_pattern: /((["']).*?(?!\\)\2)|\s*(\S+)\s*/g, - _camelCase: function (str) { + _camelCase(str) { return str.replace(/\-([a-z])/g, function (_, p1) { return p1.toUpperCase(); }); }, - addAlias: function argParserAddAlias(alias, original) { + addAlias(alias, original) { /* Add an alias for a previously added parser argument. * * Useful when you want to support both US and UK english argument @@ -52,7 +46,7 @@ ArgumentParser.prototype = { } }, - addGroupToSpec: function argParserAddGroupToSpec(spec) { + addGroupToSpec(spec) { /* Determine wether an argument being parsed can be grouped and * update its specifications object accordingly. * @@ -93,7 +87,7 @@ ArgumentParser.prototype = { return spec; }, - addJSONArgument: function argParserAddJSONArgument(name, default_value) { + addJSONArgument(name, default_value) { /* Add an argument where the value is provided in JSON format. * * This is a different usecase than specifying all arguments to @@ -111,12 +105,7 @@ ArgumentParser.prototype = { }); }, - addArgument: function ArgParserAddArgument( - name, - default_value, - choices, - multiple - ) { + addArgument(name, default_value, choices, multiple) { var spec = { name: name, value: @@ -153,13 +142,13 @@ ArgumentParser.prototype = { this.parameters[name] = this.addGroupToSpec(spec); }, - _typeof: function argParserTypeof(obj) { + _typeof(obj) { var type = typeof obj; if (obj === null) return "null"; return type; }, - _coerce: function argParserCoerce(name, value) { + _coerce(name, value) { var spec = this.parameters[name]; if (typeof value !== spec.type) try { @@ -230,7 +219,7 @@ ArgumentParser.prototype = { return value; }, - _set: function argParserSet(opts, name, value) { + _set(opts, name, value) { if (!(name in this.parameters)) { this.log.debug("Ignoring value for unknown argument " + name); return; @@ -257,7 +246,7 @@ ArgumentParser.prototype = { opts[name] = value; }, - _split: function argParserSplit(text) { + _split(text) { var tokens = []; text.replace(this.token_pattern, function (match, quoted, _, simple) { if (quoted) tokens.push(quoted); @@ -266,7 +255,7 @@ ArgumentParser.prototype = { return tokens; }, - _parseExtendedNotation: function argParserParseExtendedNotation(argstring) { + _parseExtendedNotation(argstring) { var opts = {}; var parts = argstring .replace(/;;/g, "\0x1f") @@ -315,9 +304,7 @@ ArgumentParser.prototype = { return opts; }, - _parseShorthandNotation: function argParserParseShorthandNotation( - parameter - ) { + _parseShorthandNotation(parameter) { var parts = this._split(parameter), opts = {}, positional = true, @@ -359,7 +346,7 @@ ArgumentParser.prototype = { return opts; }, - _parse: function argParser_parse(parameter) { + _parse(parameter) { var opts, extended, sep; if (!parameter) { return {}; @@ -384,7 +371,7 @@ ArgumentParser.prototype = { return opts; }, - _defaults: function argParserDefaults($el) { + _defaults($el) { var result = {}; for (var name in this.parameters) if (typeof this.parameters[name].value === "function") @@ -398,7 +385,7 @@ ArgumentParser.prototype = { return result; }, - _cleanupOptions: function argParserCleanupOptions(options) { + _cleanupOptions(options) { var keys = Object.keys(options), i, spec, @@ -441,11 +428,12 @@ ArgumentParser.prototype = { return options; }, - parse: function argParserParse($el, options, multiple, inherit) { + parse($el, options, multiple, inherit) { if (!$el.jquery) { $el = $($el); } if (typeof options === "boolean" && multiple === undefined) { + // Fix argument order: ``multiple`` passed instead of ``options``. multiple = options; options = {}; } From 91264e4f27bc82c989b52af37aa8bae2943f2b94 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 08:15:25 +0100 Subject: [PATCH 02/25] core parser: modernize code - use arrow functions --- src/core/parser.js | 109 ++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/src/core/parser.js b/src/core/parser.js index 883bd3fc7..e39df69ec 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -22,9 +22,7 @@ ArgumentParser.prototype = { token_pattern: /((["']).*?(?!\\)\2)|\s*(\S+)\s*/g, _camelCase(str) { - return str.replace(/\-([a-z])/g, function (_, p1) { - return p1.toUpperCase(); - }); + return str.replace(/\-([a-z])/g, (__, p1) => p1.toUpperCase()); }, addAlias(alias, original) { @@ -248,9 +246,12 @@ ArgumentParser.prototype = { _split(text) { var tokens = []; - text.replace(this.token_pattern, function (match, quoted, _, simple) { - if (quoted) tokens.push(quoted); - else if (simple) tokens.push(simple); + text.replace(this.token_pattern, (match, quoted, __, simple) => { + if (quoted) { + tokens.push(quoted); + } else if (simple) { + tokens.push(simple); + } }); return tokens; }, @@ -261,46 +262,35 @@ ArgumentParser.prototype = { .replace(/;;/g, "\0x1f") .replace(/&/g, "&\0x1f") .split(/;/) - .map(function (el) { - return el.replace(new RegExp("\0x1f", "g"), ";"); - }); - _.each( - parts, - function (part) { - if (!part) { - return; - } - var matches = part.match(this.named_param_pattern); - if (!matches) { - this.log.warn( - "Invalid parameter: " + part + ": " + argstring - ); - return; - } - var name = matches[1], - value = matches[2].trim(), - arg = _.chain(this.parameters) - .where({ alias: name }) - .value(), - is_alias = arg.length === 1; + .map((el) => el.replace(new RegExp("\0x1f", "g"), ";")); + _.each(parts, (part) => { + if (!part) { + return; + } + var matches = part.match(this.named_param_pattern); + if (!matches) { + this.log.warn("Invalid parameter: " + part + ": " + argstring); + return; + } + var name = matches[1], + value = matches[2].trim(), + arg = _.chain(this.parameters).where({ alias: name }).value(), + is_alias = arg.length === 1; - if (is_alias) { - this._set(opts, arg[0].name, value); - } else if (name in this.parameters) { - this._set(opts, name, value); - } else if (name in this.groups) { - var subopt = this.groups[name]._parseShorthandNotation( - value - ); - for (var field in subopt) { - this._set(opts, name + "-" + field, subopt[field]); - } - } else { - this.log.warn("Unknown named parameter " + matches[1]); - return; + if (is_alias) { + this._set(opts, arg[0].name, value); + } else if (name in this.parameters) { + this._set(opts, name, value); + } else if (name in this.groups) { + var subopt = this.groups[name]._parseShorthandNotation(value); + for (var field in subopt) { + this._set(opts, name + "-" + field, subopt[field]); } - }.bind(this) - ); + } else { + this.log.warn("Unknown named parameter " + matches[1]); + return; + } + }); return opts; }, @@ -463,22 +453,21 @@ ArgumentParser.prototype = { .addBack(); } - _.each( - $possible_config_providers, - function (provider) { - var data, frame, _parse; - data = $(provider).attr(this.attribute); - if (!data) { - return; - } - _parse = this._parse.bind(this); - if (data.match(/&&/)) - frame = data.split(/\s*&&\s*/).map(_parse); - else frame = [_parse(data)]; - final_length = Math.max(frame.length, final_length); - stack.push(frame); - }.bind(this) - ); + _.each($possible_config_providers, (provider) => { + var data, frame, _parse; + data = $(provider).attr(this.attribute); + if (!data) { + return; + } + _parse = this._parse.bind(this); + if (data.match(/&&/)) { + frame = data.split(/\s*&&\s*/).map(_parse); + } else { + frame = [_parse(data)]; + } + final_length = Math.max(frame.length, final_length); + stack.push(frame); + }); if (typeof options === "object") { if (Array.isArray(options)) { From 3c084388fb4da89707dc27918572958b03cb6163 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 08:25:59 +0100 Subject: [PATCH 03/25] core parser: modernize code - let/const instead of var --- src/core/parser.js | 149 ++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/src/core/parser.js b/src/core/parser.js index e39df69ec..76e72e404 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -50,13 +50,13 @@ ArgumentParser.prototype = { * * Internal method used by addArgument and addJSONArgument */ - var m = spec.name.match(this.group_pattern); + const m = spec.name.match(this.group_pattern); if (m) { - var group = m[1], - field = m[2]; + const group = m[1]; + const field = m[2]; if (group in this.possible_groups) { - var first_spec = this.possible_groups[group], - first_name = first_spec.name.match(this.group_pattern)[2]; + const first_spec = this.possible_groups[group]; + const first_name = first_spec.name.match(this.group_pattern)[2]; first_spec.group = group; first_spec.dest = first_name; this.groups[group] = new ArgumentParser(); @@ -104,7 +104,7 @@ ArgumentParser.prototype = { }, addArgument(name, default_value, choices, multiple) { - var spec = { + const spec = { name: name, value: multiple && !Array.isArray(default_value) @@ -117,14 +117,14 @@ ArgumentParser.prototype = { if (choices && Array.isArray(choices) && choices.length) { spec.choices = choices; spec.type = this._typeof(choices[0]); - for (var i = 0; i < choices.length; i++) { - if (this.enum_conflicts.indexOf(choices[i]) !== -1) { + for (const choice of choices) { + if (this.enum_conflicts.indexOf(choice) !== -1) { continue; - } else if (choices[i] in this.enum_values) { - this.enum_conflicts.push(choices[i]); - delete this.enum_values[choices[i]]; + } else if (choice in this.enum_values) { + this.enum_conflicts.push(choice); + delete this.enum_values[choice]; } else { - this.enum_values[choices[i]] = name; + this.enum_values[choice] = name; } } } else if ( @@ -141,13 +141,14 @@ ArgumentParser.prototype = { }, _typeof(obj) { - var type = typeof obj; - if (obj === null) return "null"; - return type; + if (obj === null) { + return "null"; + } + return typeof obj; }, _coerce(name, value) { - var spec = this.parameters[name]; + const spec = this.parameters[name]; if (typeof value !== spec.type) try { switch (spec.type) { @@ -157,7 +158,7 @@ ArgumentParser.prototype = { case "boolean": if (typeof value === "string") { value = value.toLowerCase(); - var num = parseInt(value, 10); + const num = parseInt(value, 10); if (!isNaN(num)) value = !!num; else value = @@ -165,31 +166,35 @@ ArgumentParser.prototype = { value === "y" || value === "yes" || value === "y"; - } else if (typeof value === "number") value = !!value; - else + } else if (typeof value === "number") { + value = !!value; + } else { throw ( "Cannot convert value for " + name + " to boolean" ); + } break; case "number": if (typeof value === "string") { value = parseInt(value, 10); - if (isNaN(value)) + if (isNaN(value)) { throw ( "Cannot convert value for " + name + " to number" ); - } else if (typeof value === "boolean") + } + } else if (typeof value === "boolean") { value = value + 0; - else + } else { throw ( "Cannot convert value for " + name + " to number" ); + } break; case "string": value = value.toString(); @@ -222,10 +227,8 @@ ArgumentParser.prototype = { this.log.debug("Ignoring value for unknown argument " + name); return; } - var spec = this.parameters[name], - parts, - i, - v; + const spec = this.parameters[name]; + let parts; if (spec.multiple) { if (typeof value === "string") { parts = value.split(/,+/); @@ -233,19 +236,23 @@ ArgumentParser.prototype = { parts = value; } value = []; - for (i = 0; i < parts.length; i++) { - v = this._coerce(name, parts[i].trim()); - if (v !== null) value.push(v); + for (const part of parts) { + const v = this._coerce(name, part.trim()); + if (v !== null) { + value.push(v); + } } } else { value = this._coerce(name, value); - if (value === null) return; + if (value === null) { + return; + } } opts[name] = value; }, _split(text) { - var tokens = []; + const tokens = []; text.replace(this.token_pattern, (match, quoted, __, simple) => { if (quoted) { tokens.push(quoted); @@ -257,8 +264,8 @@ ArgumentParser.prototype = { }, _parseExtendedNotation(argstring) { - var opts = {}; - var parts = argstring + const opts = {}; + const parts = argstring .replace(/;;/g, "\0x1f") .replace(/&/g, "&\0x1f") .split(/;/) @@ -267,23 +274,23 @@ ArgumentParser.prototype = { if (!part) { return; } - var matches = part.match(this.named_param_pattern); + const matches = part.match(this.named_param_pattern); if (!matches) { this.log.warn("Invalid parameter: " + part + ": " + argstring); return; } - var name = matches[1], - value = matches[2].trim(), - arg = _.chain(this.parameters).where({ alias: name }).value(), - is_alias = arg.length === 1; + const name = matches[1]; + const value = matches[2].trim(); + const arg = _.chain(this.parameters).where({ alias: name }).value(); + const is_alias = arg.length === 1; if (is_alias) { this._set(opts, arg[0].name, value); } else if (name in this.parameters) { this._set(opts, name, value); } else if (name in this.groups) { - var subopt = this.groups[name]._parseShorthandNotation(value); - for (var field in subopt) { + const subopt = this.groups[name]._parseShorthandNotation(value); + for (const field in subopt) { this._set(opts, name + "-" + field, subopt[field]); } } else { @@ -295,16 +302,15 @@ ArgumentParser.prototype = { }, _parseShorthandNotation(parameter) { - var parts = this._split(parameter), - opts = {}, - positional = true, - i = 0, - part, - flag, - sense; + const parts = this._split(parameter); + const opts = {}; + let i = 0; while (parts.length) { - part = parts.shift().trim(); + const part = parts.shift().trim(); + let sense; + let flag; + let positional = true; if (part.slice(0, 3) === "no-") { sense = false; flag = part.slice(3); @@ -337,7 +343,6 @@ ArgumentParser.prototype = { }, _parse(parameter) { - var opts, extended, sep; if (!parameter) { return {}; } @@ -351,19 +356,21 @@ ArgumentParser.prototype = { if (parameter.match(this.named_param_pattern)) { return this._parseExtendedNotation(parameter); } - sep = parameter.indexOf(";"); + const sep = parameter.indexOf(";"); if (sep === -1) { return this._parseShorthandNotation(parameter); } - opts = this._parseShorthandNotation(parameter.slice(0, sep)); - extended = this._parseExtendedNotation(parameter.slice(sep + 1)); - for (var name in extended) opts[name] = extended[name]; + const opts = this._parseShorthandNotation(parameter.slice(0, sep)); + const extended = this._parseExtendedNotation(parameter.slice(sep + 1)); + for (const name in extended) { + opts[name] = extended[name]; + } return opts; }, _defaults($el) { - var result = {}; - for (var name in this.parameters) + const result = {}; + for (const name in this.parameters) if (typeof this.parameters[name].value === "function") try { result[name] = this.parameters[name].value($el, name); @@ -376,16 +383,9 @@ ArgumentParser.prototype = { }, _cleanupOptions(options) { - var keys = Object.keys(options), - i, - spec, - name, - target; - // Resolve references - for (i = 0; i < keys.length; i++) { - name = keys[i]; - spec = this.parameters[name]; + for (const name of Object.keys(options)) { + const spec = this.parameters[name]; if (spec === undefined) continue; if ( @@ -396,10 +396,9 @@ ArgumentParser.prototype = { options[name] = options[spec.value.slice(1)]; } // Move options into groups and do renames - keys = Object.keys(options); - for (i = 0; i < keys.length; i++) { - name = keys[i]; - spec = this.parameters[name]; + for (const name of Object.keys(options)) { + const spec = this.parameters[name]; + let target; if (spec === undefined) continue; if (spec.group) { @@ -428,9 +427,9 @@ ArgumentParser.prototype = { options = {}; } inherit = inherit !== false; - var stack = inherit ? [[this._defaults($el)]] : [[{}]]; - var $possible_config_providers; - var final_length = 1; + const stack = inherit ? [[this._defaults($el)]] : [[{}]]; + let $possible_config_providers; + let final_length = 1; /* * XXX this is a workaround for: * - https://github.com/Patternslib/Patterns/issues/393 @@ -454,12 +453,12 @@ ArgumentParser.prototype = { } _.each($possible_config_providers, (provider) => { - var data, frame, _parse; - data = $(provider).attr(this.attribute); + let frame; + const data = $(provider).attr(this.attribute); if (!data) { return; } - _parse = this._parse.bind(this); + const _parse = this._parse.bind(this); if (data.match(/&&/)) { frame = data.split(/\s*&&\s*/).map(_parse); } else { @@ -478,7 +477,7 @@ ArgumentParser.prototype = { if (!multiple) { final_length = 1; } - var results = _.map( + const results = _.map( _.compose( utils.removeDuplicateObjects, _.partial(utils.mergeStack, _, final_length) From a54e8ad96fe818f9d2583d9e17326213922fd3b3 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 08:39:09 +0100 Subject: [PATCH 04/25] core parser: modernize code - class based syntax --- src/core/parser.js | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/core/parser.js b/src/core/parser.js index 76e72e404..e5d173ff9 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -4,26 +4,26 @@ import _ from "underscore"; import utils from "./utils.js"; import logging from "./logging"; -function ArgumentParser(name) { - this.order = []; - this.parameters = {}; - this.attribute = "data-pat-" + name; - this.enum_values = {}; - this.enum_conflicts = []; - this.groups = {}; - this.possible_groups = {}; - this.log = logging.getLogger(name + ".parser"); -} +class ArgumentParser { + constructor(name) { + this.order = []; + this.parameters = {}; + this.attribute = "data-pat-" + name; + this.enum_values = {}; + this.enum_conflicts = []; + this.groups = {}; + this.possible_groups = {}; + this.log = logging.getLogger(name + ".parser"); -ArgumentParser.prototype = { - group_pattern: /([a-z][a-z0-9]*)-([A-Z][a-z0-0\-]*)/i, - json_param_pattern: /^\s*{/i, - named_param_pattern: /^\s*([a-z][a-z0-9\-]*)\s*:(.*)/i, - token_pattern: /((["']).*?(?!\\)\2)|\s*(\S+)\s*/g, + this.group_pattern = /([a-z][a-z0-9]*)-([A-Z][a-z0-0\-]*)/i; + this.json_param_pattern = /^\s*{/i; + this.named_param_pattern = /^\s*([a-z][a-z0-9\-]*)\s*:(.*)/i; + this.token_pattern = /((["']).*?(?!\\)\2)|\s*(\S+)\s*/g; + } _camelCase(str) { return str.replace(/\-([a-z])/g, (__, p1) => p1.toUpperCase()); - }, + } addAlias(alias, original) { /* Add an alias for a previously added parser argument. @@ -42,7 +42,7 @@ ArgumentParser.prototype = { '".' ); } - }, + } addGroupToSpec(spec) { /* Determine wether an argument being parsed can be grouped and @@ -83,7 +83,7 @@ ArgumentParser.prototype = { } } return spec; - }, + } addJSONArgument(name, default_value) { /* Add an argument where the value is provided in JSON format. @@ -101,7 +101,7 @@ ArgumentParser.prototype = { group: null, type: "json", }); - }, + } addArgument(name, default_value, choices, multiple) { const spec = { @@ -138,14 +138,14 @@ ArgumentParser.prototype = { } this.order.push(name); this.parameters[name] = this.addGroupToSpec(spec); - }, + } _typeof(obj) { if (obj === null) { return "null"; } return typeof obj; - }, + } _coerce(name, value) { const spec = this.parameters[name]; @@ -220,7 +220,7 @@ ArgumentParser.prototype = { return null; } return value; - }, + } _set(opts, name, value) { if (!(name in this.parameters)) { @@ -249,7 +249,7 @@ ArgumentParser.prototype = { } } opts[name] = value; - }, + } _split(text) { const tokens = []; @@ -261,7 +261,7 @@ ArgumentParser.prototype = { } }); return tokens; - }, + } _parseExtendedNotation(argstring) { const opts = {}; @@ -299,7 +299,7 @@ ArgumentParser.prototype = { } }); return opts; - }, + } _parseShorthandNotation(parameter) { const parts = this._split(parameter); @@ -340,7 +340,7 @@ ArgumentParser.prototype = { if (parts.length) this.log.warn("Ignore extra arguments: " + parts.join(" ")); return opts; - }, + } _parse(parameter) { if (!parameter) { @@ -366,7 +366,7 @@ ArgumentParser.prototype = { opts[name] = extended[name]; } return opts; - }, + } _defaults($el) { const result = {}; @@ -380,7 +380,7 @@ ArgumentParser.prototype = { } else result[name] = this.parameters[name].value; return result; - }, + } _cleanupOptions(options) { // Resolve references @@ -415,7 +415,7 @@ ArgumentParser.prototype = { } } return options; - }, + } parse($el, options, multiple, inherit) { if (!$el.jquery) { @@ -485,8 +485,8 @@ ArgumentParser.prototype = { this._cleanupOptions.bind(this) ); return multiple ? results : results[0]; - }, -}; + } +} // BBB ArgumentParser.prototype.add_argument = ArgumentParser.prototype.addArgument; From dfbb547a2fa71e334269469f6bebc0f7422ce684 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 21 Dec 2020 14:11:46 +0100 Subject: [PATCH 05/25] core parser: Remove usage of underscore. --- src/core/parser.js | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/core/parser.js b/src/core/parser.js index e5d173ff9..d38f77451 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -1,6 +1,5 @@ // Patterns argument parser import $ from "jquery"; -import _ from "underscore"; import utils from "./utils.js"; import logging from "./logging"; @@ -270,18 +269,21 @@ class ArgumentParser { .replace(/&/g, "&\0x1f") .split(/;/) .map((el) => el.replace(new RegExp("\0x1f", "g"), ";")); - _.each(parts, (part) => { + for (const part of parts) { if (!part) { - return; + continue; } const matches = part.match(this.named_param_pattern); if (!matches) { this.log.warn("Invalid parameter: " + part + ": " + argstring); - return; + continue; } const name = matches[1]; const value = matches[2].trim(); - const arg = _.chain(this.parameters).where({ alias: name }).value(); + const arg = Object.values(this.parameters).filter( + (it) => it.alias === name + ); + const is_alias = arg.length === 1; if (is_alias) { @@ -295,9 +297,9 @@ class ArgumentParser { } } else { this.log.warn("Unknown named parameter " + matches[1]); - return; + continue; } - }); + } return opts; } @@ -452,11 +454,11 @@ class ArgumentParser { .addBack(); } - _.each($possible_config_providers, (provider) => { + for (const provider of $possible_config_providers) { let frame; const data = $(provider).attr(this.attribute); if (!data) { - return; + continue; } const _parse = this._parse.bind(this); if (data.match(/&&/)) { @@ -466,7 +468,7 @@ class ArgumentParser { } final_length = Math.max(frame.length, final_length); stack.push(frame); - }); + } if (typeof options === "object") { if (Array.isArray(options)) { @@ -477,13 +479,9 @@ class ArgumentParser { if (!multiple) { final_length = 1; } - const results = _.map( - _.compose( - utils.removeDuplicateObjects, - _.partial(utils.mergeStack, _, final_length) - )(stack), - this._cleanupOptions.bind(this) - ); + const results = utils + .removeDuplicateObjects(utils.mergeStack(stack, final_length)) + .map(this._cleanupOptions.bind(this)); return multiple ? results : results[0]; } } From 2a0e515cfa96b7ffd1a81b473a3fc45b532479aa Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Fri, 18 Dec 2020 09:45:42 +0100 Subject: [PATCH 06/25] core mockup-parser: modernize code --- src/core/mockup-parser.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/mockup-parser.js b/src/core/mockup-parser.js index d09eb8746..65de6478a 100644 --- a/src/core/mockup-parser.js +++ b/src/core/mockup-parser.js @@ -1,7 +1,7 @@ import $ from "jquery"; var parser = { - getOptions: function getOptions($el, patternName, options) { + getOptions($el, patternName, options) { /* This is the Mockup parser. An alternative parser for Patternslib * patterns. * @@ -13,23 +13,23 @@ var parser = { options = options || {}; // get options from parent element first, stop if element tag name is 'body' if ($el.length !== 0 && !$.nodeName($el[0], "body")) { - options = getOptions($el.parent(), patternName, options); + options = this.getOptions($el.parent(), patternName, options); } // collect all options from element - var elOptions = {}; + let elOptions = {}; if ($el.length !== 0) { elOptions = $el.data("pat-" + patternName); if (elOptions) { // parse options if string if (typeof elOptions === "string") { - var tmpOptions = {}; + const tmpOptions = {}; $.each(elOptions.split(";"), function (i, item) { item = item.split(":"); item.reverse(); - var key = item.pop(); + let key = item.pop(); key = key.replace(/^\s+|\s+$/g, ""); // trim item.reverse(); - var value = item.join(":"); + let value = item.join(":"); value = value.replace(/^\s+|\s+$/g, ""); // trim tmpOptions[key] = value; }); From 336f16ddcc1f2f508ef44509573bcb7509a042df Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 10:07:27 +0100 Subject: [PATCH 07/25] core base: modernize code --- src/core/base.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/core/base.js b/src/core/base.js index 648d82840..ea33c1b51 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -17,15 +17,15 @@ import Registry from "./registry"; import logging from "./logging"; import mockupParser from "./mockup-parser"; -var log = logging.getLogger("Patternslib Base"); +const log = logging.getLogger("Patternslib Base"); -var initBasePattern = function ($el, options, trigger) { +const initBasePattern = function ($el, options, trigger) { if (!$el.jquery) { $el = $($el); } - var name = this.prototype.name; - var log = logging.getLogger("pat." + name); - var pattern = $el.data("pattern-" + name); + const name = this.prototype.name; + const plog = logging.getLogger(`pat.${name}`); + let pattern = $el.data(`pattern-${name}`); if (pattern === undefined && Registry.patterns[name]) { try { options = @@ -34,14 +34,14 @@ var initBasePattern = function ($el, options, trigger) { : options; pattern = new Registry.patterns[name]($el, options, trigger); } catch (e) { - log.error("Failed while initializing '" + name + "' pattern.", e); + plog.error(`Failed while initializing ${name} pattern.`, e); } - $el.data("pattern-" + name, pattern); + $el.data(`pattern-${name}`, pattern); } return pattern; }; -var Base = function ($el, options, trigger) { +const Base = function ($el, options, trigger) { if (!$el.jquery) { $el = $($el); } @@ -54,23 +54,23 @@ var Base = function ($el, options, trigger) { Base.prototype = { constructor: Base, - on: function (eventName, eventCallback) { - this.$el.on(eventName + "." + this.name + ".patterns", eventCallback); + on(eventName, eventCallback) { + this.$el.on(`${eventName}.${this.name}.patterns`, eventCallback); }, - emit: function (eventName, args) { + emit(eventName, args) { // args should be a list if (args === undefined) { args = []; } - this.$el.trigger(eventName + "." + this.name + ".patterns", args); + this.$el.trigger(`${eventName}.${this.name}.patterns`, args); }, }; Base.extend = function (patternProps) { /* Helper function to correctly set up the prototype chain for new patterns. */ - var parent = this; - var child; + const parent = this; + let child; // Check that the required configuration properties are given. if (!patternProps) { @@ -120,10 +120,7 @@ Base.extend = function (patternProps) { ); } else if (!patternProps.trigger) { log.warn( - "The pattern '" + - patternProps.name + - "' does not " + - "have a trigger attribute, it will not be registered." + `The pattern ${patternProps.name} does not have a trigger attribute, it will not be registered.` ); } else { Registry.register(child, patternProps.name); From ee09d3174a0545e321cbfc97ce58c8fad6efff55 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 17 Dec 2020 09:56:05 +0100 Subject: [PATCH 08/25] core registry: modernize code --- src/core/registry.js | 55 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/core/registry.js b/src/core/registry.js index 994d3e950..a9a036d84 100644 --- a/src/core/registry.js +++ b/src/core/registry.js @@ -24,12 +24,12 @@ import utils from "./utils"; // below here modules that are only loaded import "./jquery-ext"; -var log = logging.getLogger("registry"), - disable_re = /patterns-disable=([^&]+)/g, - dont_catch_re = /patterns-dont-catch/g, - dont_catch = false, - disabled = {}, - match; +const log = logging.getLogger("registry"); +const disable_re = /patterns-disable=([^&]+)/g; +const dont_catch_re = /patterns-dont-catch/g; +const disabled = {}; +let dont_catch = false; +let match; while ((match = disable_re.exec(window.location.search)) !== null) { disabled[match[1]] = true; @@ -41,14 +41,14 @@ while ((match = dont_catch_re.exec(window.location.search)) !== null) { log.info("I will not catch init exceptions"); } -var registry = { +const registry = { patterns: {}, // as long as the registry is not initialized, pattern // registration just registers a pattern. Once init is called, // the DOM is scanned. After that registering a new pattern // results in rescanning the DOM only for this pattern. initialized: false, - init: function registry_init() { + init() { $(document).ready(function () { log.info( "loaded: " + Object.keys(registry.patterns).sort().join(", ") @@ -59,13 +59,13 @@ var registry = { }); }, - clear: function clearRegistry() { + clear() { // Removes all patterns from the registry. Currently only being // used in tests. this.patterns = {}; }, - transformPattern: function (name, content) { + transformPattern(name, content) { /* Call the transform method on the pattern with the given name, if * it exists. */ @@ -88,14 +88,14 @@ var registry = { } }, - initPattern: function (name, el, trigger) { + initPattern(name, el, trigger) { /* Initialize the pattern with the provided name and in the context * of the passed in DOM element. */ - var $el = $(el); - var pattern = registry.patterns[name]; + const $el = $(el); + const pattern = registry.patterns[name]; if (pattern.init) { - var plog = logging.getLogger("pat." + name); + const plog = logging.getLogger("pat." + name); if ($el.is(pattern.trigger)) { plog.debug("Initialising:", $el); try { @@ -111,7 +111,7 @@ var registry = { } }, - orderPatterns: function (patterns) { + orderPatterns(patterns) { // XXX: Bit of a hack. We need the validation pattern to be // parsed and initiated before the inject pattern. So we make // sure here, that it appears first. Not sure what would be @@ -127,15 +127,15 @@ var registry = { return patterns; }, - scan: function registryScan(content, patterns, trigger) { - var selectors = [], - $match; + scan(content, patterns, trigger) { + const selectors = []; + let $match; patterns = this.orderPatterns( patterns || Object.keys(registry.patterns) ); patterns.forEach(_.partial(this.transformPattern, _, content)); - patterns = _.each(patterns, function (name) { - var pattern = registry.patterns[name]; + patterns = _.each(patterns, (name) => { + const pattern = registry.patterns[name]; if (pattern.trigger) { selectors.unshift(pattern.trigger); } @@ -157,8 +157,7 @@ var registry = { $("body").addClass("patterns-loaded"); }, - register: function registry_register(pattern, name) { - var plugin_name; + register(pattern, name) { name = name || pattern.name; if (!name) { log.error("Pattern lacks a name:", pattern); @@ -174,12 +173,12 @@ var registry = { // register pattern as jquery plugin if (pattern.jquery_plugin) { - plugin_name = ("pat-" + name).replace(/-([a-zA-Z])/g, function ( - match, - p1 - ) { - return p1.toUpperCase(); - }); + const plugin_name = ("pat-" + name).replace( + /-([a-zA-Z])/g, + function (match, p1) { + return p1.toUpperCase(); + } + ); $.fn[plugin_name] = utils.jqueryPlugin(pattern); // BBB 2012-12-10 and also for Mockup patterns. $.fn[plugin_name.replace(/^pat/, "pattern")] = $.fn[plugin_name]; From b50415b8a5b53e1e1849aa966e2775489cc970fc Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sat, 19 Dec 2020 19:36:10 +0100 Subject: [PATCH 09/25] core registry: Modernize code - Remove underscore and jquery-ext, use more regular js --- src/core/registry.js | 48 +++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/core/registry.js b/src/core/registry.js index a9a036d84..d5d304343 100644 --- a/src/core/registry.js +++ b/src/core/registry.js @@ -17,13 +17,10 @@ * - set pattern.jquery_plugin if you want it */ import $ from "jquery"; -import _ from "underscore"; +import dom from "./dom"; import logging from "./logging"; import utils from "./utils"; -// below here modules that are only loaded -import "./jquery-ext"; - const log = logging.getLogger("registry"); const disable_re = /patterns-disable=([^&]+)/g; const dont_catch_re = /patterns-dont-catch/g; @@ -117,10 +114,7 @@ const registry = { // sure here, that it appears first. Not sure what would be // the best solution. Perhaps some kind of way to register // patterns "before" or "after" other patterns. - if ( - _.contains(patterns, "validation") && - _.contains(patterns, "inject") - ) { + if (patterns.includes("validation") && patterns.includes("inject")) { patterns.splice(patterns.indexOf("validation"), 1); patterns.unshift("validation"); } @@ -128,33 +122,41 @@ const registry = { }, scan(content, patterns, trigger) { + if (typeof content === "string") { + content = document.querySelector(content); + } else if (content.jquery) { + content = content[0]; + } + const selectors = []; - let $match; patterns = this.orderPatterns( patterns || Object.keys(registry.patterns) ); - patterns.forEach(_.partial(this.transformPattern, _, content)); - patterns = _.each(patterns, (name) => { + for (const name of patterns) { + this.transformPattern(name, content); const pattern = registry.patterns[name]; if (pattern.trigger) { selectors.unshift(pattern.trigger); } - }); - $match = $(content).findInclusive(selectors.join(",")); // Find all DOM elements belonging to a pattern - $match = $match.filter(function () { + } + + let matches = dom.querySelectorAllAndMe(content, selectors.join(",")); + matches = matches.filter((el) => { // Filter out code examples wrapped in
 elements.
-            return $(this).parents("pre").length === 0;
+            // Also filter special class ``.cant-touch-this``
+            return (
+                dom.find_parents(el, "pre").length === 0 &&
+                !el.matches(".cant-touch-this")
+            );
         });
-        $match = $match.filter(":not(.cant-touch-this)");
 
         // walk list backwards and initialize patterns inside-out.
-        $match.toArray().reduceRight(
-            function registryInitPattern(acc, el) {
-                patterns.forEach(_.partial(this.initPattern, _, el, trigger));
-            }.bind(this),
-            null
-        );
-        $("body").addClass("patterns-loaded");
+        for (const el of matches.reverse()) {
+            for (const name of patterns) {
+                this.initPattern(name, el, trigger);
+            }
+        }
+        document.body.classList.add("patterns-loaded");
     },
 
     register(pattern, name) {

From ce904bf9d69d8e94cfd062809d133cd9d8dd44cb Mon Sep 17 00:00:00 2001
From: Johannes Raggam 
Date: Tue, 22 Dec 2020 13:45:51 +0100
Subject: [PATCH 10/25] core registry scan: Clean up selectors before querying.

---
 src/core/registry.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/core/registry.js b/src/core/registry.js
index d5d304343..e584af682 100644
--- a/src/core/registry.js
+++ b/src/core/registry.js
@@ -140,7 +140,10 @@ const registry = {
             }
         }
 
-        let matches = dom.querySelectorAllAndMe(content, selectors.join(","));
+        let matches = dom.querySelectorAllAndMe(
+            content,
+            selectors.map((it) => it.trim().replace(/,$/, "")).join(",")
+        );
         matches = matches.filter((el) => {
             // Filter out code examples wrapped in 
 elements.
             // Also filter special class ``.cant-touch-this``

From b2ecbec99a037a4cd3c30338089511bf82054309 Mon Sep 17 00:00:00 2001
From: Johannes Raggam 
Date: Sat, 19 Dec 2020 22:20:46 +0100
Subject: [PATCH 11/25] code dom: Make find_parents more robust against
 DocumentFragments without a parent.

---
 src/core/dom.js      |  3 ++-
 src/core/dom.test.js | 18 ++++++++++++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/core/dom.js b/src/core/dom.js
index 66f0c9052..6c8b2b2c4 100644
--- a/src/core/dom.js
+++ b/src/core/dom.js
@@ -57,7 +57,8 @@ const find_parents = (el, selector) => {
     // This matches against all parents but not the element itself.
     // The order of elements is from the search starting point up to higher
     // DOM levels.
-    let parent = el.parentNode?.closest(selector) || null;
+    let parent =
+        (el?.parentNode?.closest && el.parentNode.closest(selector)) || null;
     const ret = [];
     while (parent) {
         ret.push(parent);
diff --git a/src/core/dom.test.js b/src/core/dom.test.js
index b0f178262..70e09f37b 100644
--- a/src/core/dom.test.js
+++ b/src/core/dom.test.js
@@ -173,6 +173,24 @@ describe("core.dom tests", () => {
             expect(res[0]).toEqual(document.querySelector(".level3")); // inner dom levels first // prettier-ignore
             expect(res[1]).toEqual(document.querySelector(".level1"));
 
+            done();
+        });
+        it("don't break with no element.", (done) => {
+            const res = dom.find_parents(null, ".findme");
+            expect(res.length).toEqual(0);
+
+            done();
+        });
+        it("don't break with DocumentFragment without a parent.", (done) => {
+            const el = new DocumentFragment();
+            el.innerHTML = `
`; + console.log(el.parentNode); + const res = dom.find_parents( + el.querySelector(".starthere"), + ".findme" + ); + expect(res.length).toEqual(0); + done(); }); }); From d97881a62af1ae98500c20f41e1626b918774f52 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 21 Dec 2020 09:41:09 +0100 Subject: [PATCH 12/25] core utils: Add jqToNode to return a DOM node if a jQuery node was passed. --- CHANGES.md | 1 + src/core/utils.js | 9 +++++++++ src/core/utils.test.js | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 341b927bc..ea0d9e145 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -55,6 +55,7 @@ - pat tabs: When clicking on the ``extra-tabs`` element, toggle between ``open`` and ``closed`` classes to allow opening/closing an extra-tabs menu via CSS. - pat autofocus: Do not autofocus in iframes. Fixes: #761. - pat inject: Allow configurable error pages. Can be disabled by adding ``pat-inject-errorhandler.off`` to the URL's query string. +- core utils: Add ``jqToNode`` to return a DOM node if a jQuery node was passed. ### Technical diff --git a/src/core/utils.js b/src/core/utils.js index 02c0c238c..d152bec48 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -548,6 +548,14 @@ const isIE = () => { return /*@cc_on!@*/ false || !!document.documentMode; }; +const jqToNode = (el) => { + // Return a DOM node if a jQuery node was passed. + if (el.jquery) { + el = el[0]; + } + return el; +}; + var utils = { // pattern pimping - own module? jqueryPlugin: jqueryPlugin, @@ -572,6 +580,7 @@ var utils = { timeout: timeout, debounce: debounce, isIE: isIE, + jqToNode: jqToNode, }; export default utils; diff --git a/src/core/utils.test.js b/src/core/utils.test.js index fe1e1bd4d..d8367cd40 100644 --- a/src/core/utils.test.js +++ b/src/core/utils.test.js @@ -601,3 +601,17 @@ describe("getCSSValue", function () { expect(utils.getCSSValue(el2, "margin-bottom", true)).toBe(24.0); }); }); + +describe("core.utils tests", () => { + describe("jqToNode tests", () => { + it("always returns a bare DOM node no matter if a jQuery or bare DOM node was passed.", (done) => { + const el = document.createElement("div"); + const $el = $(el); + + expect(utils.jqToNode($el)).toBe(el); + expect(utils.jqToNode(el)).toBe(el); + + done(); + }); + }); +}); From 06c1728525ba0ab426601efff634b8ac9b704967 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 21 Dec 2020 09:49:29 +0100 Subject: [PATCH 13/25] Fix tests: Add missing import for jquery-ext where it's needed. --- src/pat/auto-submit/auto-submit.js | 5 +++-- src/pat/auto-suggest/auto-suggest.js | 1 + src/pat/bumper/bumper.js | 3 ++- src/pat/legend/legend.js | 13 ++++++++++--- src/pat/scroll/scroll.js | 5 +++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/pat/auto-submit/auto-submit.js b/src/pat/auto-submit/auto-submit.js index 99d58976b..393e4870d 100644 --- a/src/pat/auto-submit/auto-submit.js +++ b/src/pat/auto-submit/auto-submit.js @@ -1,8 +1,9 @@ +import "../../core/jquery-ext"; import $ from "jquery"; -import logging from "../../core/logging"; import Base from "../../core/base"; -import Parser from "../../core/parser"; import input_change_events from "../../lib/input-change-events"; +import logging from "../../core/logging"; +import Parser from "../../core/parser"; import utils from "../../core/utils"; const log = logging.getLogger("autosubmit"); diff --git a/src/pat/auto-suggest/auto-suggest.js b/src/pat/auto-suggest/auto-suggest.js index 28444d356..d76f02906 100644 --- a/src/pat/auto-suggest/auto-suggest.js +++ b/src/pat/auto-suggest/auto-suggest.js @@ -1,4 +1,5 @@ import "regenerator-runtime/runtime"; // needed for ``await`` support +import "../../core/jquery-ext"; import $ from "jquery"; import Base from "../../core/base"; import logging from "../../core/logging"; diff --git a/src/pat/bumper/bumper.js b/src/pat/bumper/bumper.js index 9d9dfd7ea..8c4b0317f 100644 --- a/src/pat/bumper/bumper.js +++ b/src/pat/bumper/bumper.js @@ -6,10 +6,11 @@ * Copyright 2013-2014 Simplon B.V. - Wichert Akkerman */ +import "../../core/jquery-ext"; import $ from "jquery"; import _ from "underscore"; -import Parser from "../../core/parser"; import Base from "../../core/base"; +import Parser from "../../core/parser"; import utils from "../../core/utils"; const parser = new Parser("bumper"); diff --git a/src/pat/legend/legend.js b/src/pat/legend/legend.js index ed6304900..db8600d19 100644 --- a/src/pat/legend/legend.js +++ b/src/pat/legend/legend.js @@ -1,5 +1,7 @@ import $ from "jquery"; import registry from "../../core/registry"; +import utils from "../../core/utils"; +import dom from "../../core/dom"; var legend = { name: "legend", @@ -21,9 +23,14 @@ var legend = { }, transform: function ($root) { - $root.findInclusive("legend:not(.cant-touch-this)").each(function () { - $(this).replaceWith("

" + $(this).html() + "

"); - }); + const root = utils.jqToNode($root); + const all = dom.querySelectorAllAndMe( + root, + "legend:not(.cant-touch-this)" + ); + for (const el of all) { + $(el).replaceWith("

" + $(el).html() + "

"); + } }, }; registry.register(legend); diff --git a/src/pat/scroll/scroll.js b/src/pat/scroll/scroll.js index d9fd032a1..3c2da5583 100644 --- a/src/pat/scroll/scroll.js +++ b/src/pat/scroll/scroll.js @@ -1,9 +1,10 @@ import "regenerator-runtime/runtime"; // needed for ``await`` support +import "../../core/jquery-ext"; import $ from "jquery"; +import _ from "underscore"; import Base from "../../core/base"; -import utils from "../../core/utils"; import Parser from "../../core/parser"; -import _ from "underscore"; +import utils from "../../core/utils"; // Lazy loading modules. let ImagesLoaded; From 6de0d64cf33d97141b0bead160fc9135961a63a9 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Tue, 22 Dec 2020 13:46:25 +0100 Subject: [PATCH 14/25] pat autofocus: Avoid jQuery extended CSS selector syntax. --- src/pat/autofocus/autofocus.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pat/autofocus/autofocus.js b/src/pat/autofocus/autofocus.js index a82a6118d..5c7d2c795 100644 --- a/src/pat/autofocus/autofocus.js +++ b/src/pat/autofocus/autofocus.js @@ -6,7 +6,16 @@ let registered_event_handler = false; export default Base.extend({ name: "autofocus", - trigger: ":input.pat-autofocus,:input[autofocus]", + trigger: ` + input.pat-autofocus, + input[autofocus], + select.pat-autofocus, + select[autofocus], + textarea.pat-autofocus, + textarea[autofocus], + button.pat-autofocus, + button[autofocus] + `, init() { if (window.self !== window.top) { From 0a3fd549e1555dd8a4dca6eb971d85695b2eebe6 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Tue, 22 Dec 2020 13:46:33 +0100 Subject: [PATCH 15/25] pat slides: Avoid jQuery extended CSS selector syntax. --- src/pat/slides/slides.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pat/slides/slides.js b/src/pat/slides/slides.js index 91bc3dab3..bf46c3129 100644 --- a/src/pat/slides/slides.js +++ b/src/pat/slides/slides.js @@ -12,13 +12,17 @@ import "../../core/remove"; var slides = { name: "slides", - trigger: ".pat-slides:has(.slide)", + trigger: ".pat-slides", setup: function () { $(document).on("patterns-injected", utils.debounce(slides._reset, 100)); }, async init($el) { + if (!this.el.querySelector(".slide")) { + // no slides, nothing to do. + return; + } await import("slides/src/slides"); // loads ``Presentation`` globally. var parameters = url.parameters(); From e3036e93410ba2afb42968cc0a3558b689e1bb5d Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Tue, 22 Dec 2020 13:47:18 +0100 Subject: [PATCH 16/25] pat inject: Rebase URLs in pattern configuration attributes. This avoids URLs in pattern configuration to point to unreachable paths in the context where the result is injected into. --- CHANGES.md | 1 + src/pat/inject/index.html | 20 ++++++++++++++++++++ src/pat/inject/inject.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ea0d9e145..a65624ca0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -56,6 +56,7 @@ - pat autofocus: Do not autofocus in iframes. Fixes: #761. - pat inject: Allow configurable error pages. Can be disabled by adding ``pat-inject-errorhandler.off`` to the URL's query string. - core utils: Add ``jqToNode`` to return a DOM node if a jQuery node was passed. +- pat inject: Rebase URLs in pattern configuration attributes. This avoids URLs in pattern configuration to point to unreachable paths in the context where the result is injected into. ### Technical diff --git a/src/pat/inject/index.html b/src/pat/inject/index.html index 1ef25f6f0..7e37f9f01 100644 --- a/src/pat/inject/index.html +++ b/src/pat/inject/index.html @@ -413,6 +413,18 @@

Shows a fallback message on error

+
+
+

Inject and fix URLs of options

+ + injection happens here! + +
+
+ @@ -667,6 +679,14 @@

transform: scale(1); } } + + #rebase-url-demo a { + cursor: pointer; + } + #rebase-url-demo div div { + margin-left: 2em; + border: 1px solid black; + } diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 10b1c79a7..0ad038417 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -3,6 +3,7 @@ import "regenerator-runtime/runtime"; // needed for ``await`` support import $ from "jquery"; import _ from "underscore"; import ajax from "../ajax/ajax"; +import dom from "../../core/dom"; import logging from "../../core/logging"; import Parser from "../../core/parser"; import registry from "../../core/registry"; @@ -867,6 +868,14 @@ const inject = { VIDEO: "data-pat-inject-rebase-src", }, + _rebaseOptions: { + "data-pat-inject": ["url"], + "data-pat-calendar": ["url"], + "data-pat-date-picker": ["i18n"], + "data-pat-datetime-picker": ["i18n"], + "data-pat-collapsible": ["load-content"], + }, + _rebaseHTML(base, html) { if (html === "") { // Special case, source is none @@ -901,6 +910,29 @@ const inject = { $el_.attr(attrName, value); } }); + + for (const [attr, opts] of Object.entries(this._rebaseOptions)) { + for (const el_ of dom.querySelectorAllAndMe( + $page[0], + `[${attr}]` + )) { + const val = el_.getAttribute(attr, false); + if (val) { + let options = parser._parse(val); + let changed = false; + for (const opt of opts) { + if (options[opt]) { + options[opt] = utils.rebaseURL(base, options[opt]); + changed = true; + } + } + if (changed) { + el_.setAttribute(attr, JSON.stringify(options)); + } + } + } + } + // XXX: IE8 changes the order of attributes in html. The following // lines move data-pat-inject-rebase-src to src. $page.find("[data-pat-inject-rebase-src]").each((id, el_) => { From 9c8ef3be6d5afdb6b5ecc4fdec0ae9b21cdb2c14 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 14:59:52 +0100 Subject: [PATCH 17/25] core base: Add the parser instance to pattern attributes if available. --- CHANGES.md | 3 ++- src/core/base.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a65624ca0..620694f82 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -84,7 +84,8 @@ Configure ``style_loader`` to insert CSS at the TOP of the html ```` Provide a webpack-helpers module with a ``top_head_insert`` function which can be reused in depending projects. - Build infra: Switch the CI system to GitHub Actions and drop Travis CI. - +- core base: Add the parser instance to pattern attributes if available. + We can then reuse the parser from registered patterns. This is used in the ``_rebaseHTML`` method of pat-inject to URL-rebase the pattern configuration. ### Fixes diff --git a/src/core/base.js b/src/core/base.js index ea33c1b51..52a0f1047 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -97,6 +97,7 @@ Base.extend = function (patternProps) { child.init = initBasePattern; child.jquery_plugin = true; child.trigger = patternProps.trigger; + child.parser = patternProps?.parser || null; // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. From a2b5f885536e670e40fc5da5e3d7c085d14f127b Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:00:39 +0100 Subject: [PATCH 18/25] pat inject: Rebase URLs in pattern configuration attributes /2 We need to use the pattern's parser to be able to correctly parse the pattern configuration. --- src/pat/inject/inject.js | 46 +++++++++++++++++++++++++---------- src/pat/inject/inject.test.js | 39 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 0ad038417..d650cbf86 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -53,6 +53,8 @@ const inject = { name: "inject", trigger: ".raptor-ui .ui-button.pat-inject, a.pat-inject, form.pat-inject, .pat-subform.pat-inject", + parser: parser, + init($el, opts) { const cfgs = this.extractConfig($el, opts); if ( @@ -869,11 +871,11 @@ const inject = { }, _rebaseOptions: { - "data-pat-inject": ["url"], - "data-pat-calendar": ["url"], - "data-pat-date-picker": ["i18n"], - "data-pat-datetime-picker": ["i18n"], - "data-pat-collapsible": ["load-content"], + "calendar": ["url", "event-sources"], + "collapsible": ["load-content"], + "date-picker": ["i18n"], + "datetime-picker": ["i18n"], + "inject": ["url"], }, _rebaseHTML(base, html) { @@ -911,23 +913,41 @@ const inject = { } }); - for (const [attr, opts] of Object.entries(this._rebaseOptions)) { + for (const [pattern_name, opts] of Object.entries( + this._rebaseOptions + )) { for (const el_ of dom.querySelectorAllAndMe( $page[0], - `[${attr}]` + `[data-pat-${pattern_name}]` )) { - const val = el_.getAttribute(attr, false); + const val = el_.getAttribute(`data-pat-${pattern_name}`, false); if (val) { - let options = parser._parse(val); + const pattern = registry.patterns[pattern_name]; + const pattern_parser = pattern?.parser; + if (!pattern_parser) { + continue; + } + let options = pattern_parser._parse(val); let changed = false; for (const opt of opts) { - if (options[opt]) { - options[opt] = utils.rebaseURL(base, options[opt]); - changed = true; + const val = options[opt]; + if (typeof val === "undefined") { + continue; + } + changed = true; + if (Array.isArray(val)) { + options[opt] = val.map((it) => + utils.rebaseURL(base, it) + ); + } else { + options[opt] = utils.rebaseURL(base, val); } } if (changed) { - el_.setAttribute(attr, JSON.stringify(options)); + el_.setAttribute( + `data-pat-${pattern_name}`, + JSON.stringify(options) + ); } } } diff --git a/src/pat/inject/inject.test.js b/src/pat/inject/inject.test.js index e8720b5c9..f35d73c74 100644 --- a/src/pat/inject/inject.test.js +++ b/src/pat/inject/inject.test.js @@ -308,6 +308,45 @@ describe("pat-inject", function () { "

This string has src = \"foo\" , src= bar , and SrC='foo'

" ); }); + + it("rebase pattern configuration", async (done) => { + await import("../calendar/calendar"); + await import("../collapsible/collapsible"); + await import("../date-picker/date-picker"); + await import("../datetime-picker/datetime-picker"); + + const res = pattern._rebaseHTML( + "https://example.com/test/", + `
+
+
` + ); + console.log(res); + + const el = document.createElement("div"); + el.innerHTML = res; + + const test1_config = JSON.parse(el.querySelector(".test1").getAttribute("data-pat-inject")); // prettier-ignore + expect(test1_config.url).toEqual("https://example.com/test/./index.html"); // prettier-ignore + + const test2_config = JSON.parse(el.querySelector(".test2").getAttribute("data-pat-calendar")); // prettier-ignore + expect(test2_config.url).toEqual("https://example.com/test/./calendar.html"); // prettier-ignore + expect(test2_config["event-sources"][0]).toEqual("https://example.com/test/../calendar2.json"); // prettier-ignore + expect(test2_config["event-sources"][1]).toEqual("https://example.com/test/./test/calendar3.json"); // prettier-ignore + + const test3a_config = JSON.parse(el.querySelector(".test3").getAttribute("data-pat-date-picker")); // prettier-ignore + expect(test3a_config.i18n).toEqual("https://example.com/test/./i18n"); // prettier-ignore + const test3b_config = JSON.parse(el.querySelector(".test3").getAttribute("data-pat-datetime-picker")); // prettier-ignore + expect(test3b_config.i18n).toEqual("https://example.com/test/./path/to/i18n"); // prettier-ignore + const test3c_config = JSON.parse(el.querySelector(".test3").getAttribute("data-pat-collapsible")); // prettier-ignore + expect(test3c_config["load-content"]).toEqual("https://example.com/test/../load/content/from/here"); // prettier-ignore + + done(); + }); }); describe("parseRawHtml", function () { From 1cff029632d0c1e0f207e1e4e944b2fe6ada4bfd Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:02:16 +0100 Subject: [PATCH 19/25] pat calendar: Add the parser to the pattern attributes. --- src/pat/calendar/calendar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pat/calendar/calendar.js b/src/pat/calendar/calendar.js index 156064293..851c7e048 100644 --- a/src/pat/calendar/calendar.js +++ b/src/pat/calendar/calendar.js @@ -85,6 +85,7 @@ export default Base.extend({ }, dayNames: ["su", "mo", "tu", "we", "th", "fr", "sa"], active_categories: null, + parser: parser, async init($el, opts) { const el = this.el; From 766ba679e9d7aec9cf900430e5454ed259ef420a Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:02:33 +0100 Subject: [PATCH 20/25] pat collapsible: Add the parser to the pattern attributes. --- src/pat/collapsible/collapsible.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pat/collapsible/collapsible.js b/src/pat/collapsible/collapsible.js index cb2257749..9e24296f0 100644 --- a/src/pat/collapsible/collapsible.js +++ b/src/pat/collapsible/collapsible.js @@ -39,6 +39,7 @@ export default Base.extend({ name: "collapsible", trigger: ".pat-collapsible", jquery_plugin: true, + parser: parser, transitions: { "none": { closed: "hide", open: "show" }, From a2d6f99c74d17f716392989949a8f943be125afe Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:02:51 +0100 Subject: [PATCH 21/25] pat date-picker: Add the parser to the pattern attributes. --- src/pat/date-picker/date-picker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pat/date-picker/date-picker.js b/src/pat/date-picker/date-picker.js index cb06bb298..5e71f91ec 100644 --- a/src/pat/date-picker/date-picker.js +++ b/src/pat/date-picker/date-picker.js @@ -29,6 +29,7 @@ parser.addAlias("behaviour", "behavior"); export default Base.extend({ name: "date-picker", trigger: ".pat-date-picker", + parser: parser, async init() { const el = this.el; From 3a9d5e05fbffc9df726c7ffb3f84eb445727052e Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:03:10 +0100 Subject: [PATCH 22/25] pat datetime-picker: Add the parser to the pattern attributes. --- src/pat/datetime-picker/datetime-picker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pat/datetime-picker/datetime-picker.js b/src/pat/datetime-picker/datetime-picker.js index 6ed08104c..e5dd5161f 100644 --- a/src/pat/datetime-picker/datetime-picker.js +++ b/src/pat/datetime-picker/datetime-picker.js @@ -20,6 +20,7 @@ parser.addArgument("first-day", 0); export default Base.extend({ name: "datetime-picker", trigger: ".pat-datetime-picker", + parser: parser, async init() { const el = this.el; From 470883040a6e4e9dac0bdc21843b4adb1d84c6ae Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:45:51 +0100 Subject: [PATCH 23/25] pat calendar demo: Fix url. --- src/pat/calendar/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pat/calendar/index.html b/src/pat/calendar/index.html index 1cc22e67d..1bbbe7351 100644 --- a/src/pat/calendar/index.html +++ b/src/pat/calendar/index.html @@ -15,9 +15,9 @@
From 4ae77ecf223e5561e972ca9f8ed9faeef126878e Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 23 Dec 2020 15:46:19 +0100 Subject: [PATCH 25/25] pat collapsible demo: Fix url. --- src/pat/collapsible/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pat/collapsible/index.html b/src/pat/collapsible/index.html index 93e58fc81..125212a1f 100644 --- a/src/pat/collapsible/index.html +++ b/src/pat/collapsible/index.html @@ -12,9 +12,9 @@
- -
Click me - I explicitly start out closed and will fade in

+ data-pat-collapsible="load-content: ./collapsible-sources.html#panel1; close-trigger: #close; open-trigger: #open">

Click me! - I will load my content when opened