From 6567843927a3e455dda2bf6fb473a944bca6bda4 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 2 Nov 2023 15:44:29 +0700 Subject: [PATCH 01/14] Make unit tests run faster in Github Actions (#7829) --- bin/ci-test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/ci-test.sh b/bin/ci-test.sh index dd90c4db6..ffcae66b2 100755 --- a/bin/ci-test.sh +++ b/bin/ci-test.sh @@ -2,9 +2,6 @@ # test TiddlyWiki5 for tiddlywiki.com -npm install playwright @playwright/test -npx playwright install chromium firefox --with-deps - node ./tiddlywiki.js \ ./editions/test \ --verbose \ @@ -13,4 +10,7 @@ node ./tiddlywiki.js \ --test \ || exit 1 +npm install playwright @playwright/test +npx playwright install chromium firefox --with-deps + npx playwright test From 758089cbb35e40ac35b8205ab9be243d13725a40 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 7 Nov 2023 04:18:31 +0700 Subject: [PATCH 02/14] Alternate fix for inconsistent list template syntax (#7827) * Alternate fix for inconsistent list template syntax First attempt, which fails on the ListWidget/WithMissingTemplate test. * Make WithMissingTemplate test pass, inefficiently Unfortunately, this ends up being very inefficient, because the clone-and-mutate logic is repeated for every list item. Not ideal. * More efficient way to do it This also makes the failing test pass, but far more efficiently. * Improve performance of list template discovery Since parse tree nodes never change after widget creation (whereas attribute values *can* change), we can safely search for the explicit list templtaes only once, at widget creation time. This saves time as the search doesn't have to be done on each re-render, and also allows us to safely do a clone-and-mutate step to extract the list widget's body (if any) without any `$list-empty` or other items. That, in turn, allows using the list widget's body as the template even if `$list-empty` is specified inside the widget body. --- core/modules/widgets/list.js | 44 ++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/core/modules/widgets/list.js b/core/modules/widgets/list.js index 39c7e1b84..faedf72cc 100755 --- a/core/modules/widgets/list.js +++ b/core/modules/widgets/list.js @@ -28,6 +28,18 @@ Inherit from the base widget class */ ListWidget.prototype = new Widget(); +ListWidget.prototype.initialise = function(parseTreeNode,options) { + // Bail if parseTreeNode is undefined, meaning that the ListWidget constructor was called without any arguments so that it can be subclassed + if(parseTreeNode === undefined) { + return; + } + // First call parent constructor to set everything else up + Widget.prototype.initialise.call(this,parseTreeNode,options); + // Now look for <$list-template> and <$list-empty> widgets as immediate child widgets + // This is safe to do during initialization because parse trees never change after creation + this.findExplicitTemplates(); +} + /* Render this widget into the DOM */ @@ -68,8 +80,6 @@ ListWidget.prototype.execute = function() { this.counterName = this.getAttribute("counter"); this.storyViewName = this.getAttribute("storyview"); this.historyTitle = this.getAttribute("history"); - // Look for <$list-template> and <$list-empty> widgets as immediate child widgets - this.findExplicitTemplates(); // Compose the list elements this.list = this.getTiddlerList(); var members = [], @@ -92,6 +102,7 @@ ListWidget.prototype.findExplicitTemplates = function() { var self = this; this.explicitListTemplate = null; this.explicitEmptyTemplate = null; + this.hasTemplateInBody = false; var searchChildren = function(childNodes) { $tw.utils.each(childNodes,function(node) { if(node.type === "list-template") { @@ -100,6 +111,8 @@ ListWidget.prototype.findExplicitTemplates = function() { self.explicitEmptyTemplate = node.children; } else if(node.type === "element" && node.tag === "p") { searchChildren(node.children); + } else { + self.hasTemplateInBody = true; } }); }; @@ -160,11 +173,11 @@ ListWidget.prototype.makeItemTemplate = function(title,index) { // Check for a <$list-item> widget if(this.explicitListTemplate) { templateTree = this.explicitListTemplate; - } else if (!this.explicitEmptyTemplate) { + } else if(this.hasTemplateInBody) { templateTree = this.parseTreeNode.children; } } - if(!templateTree) { + if(!templateTree || templateTree.length === 0) { // Default template is a link to the title templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ {type: "text", text: title} @@ -414,4 +427,27 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) { exports.listitem = ListItemWidget; +/* +Make <$list-template> and <$list-empty> widgets that do nothing +*/ +var ListTemplateWidget = function(parseTreeNode,options) { + // Main initialisation inherited from widget.js + this.initialise(parseTreeNode,options); +}; +ListTemplateWidget.prototype = new Widget(); +ListTemplateWidget.prototype.render = function() {} +ListTemplateWidget.prototype.refresh = function() { return false; } + +exports["list-template"] = ListTemplateWidget; + +var ListEmptyWidget = function(parseTreeNode,options) { + // Main initialisation inherited from widget.js + this.initialise(parseTreeNode,options); +}; +ListEmptyWidget.prototype = new Widget(); +ListEmptyWidget.prototype.render = function() {} +ListEmptyWidget.prototype.refresh = function() { return false; } + +exports["list-empty"] = ListEmptyWidget; + })(); From 215bd4e015f3069e007c14ab57937d0eade7ba88 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 15 Nov 2023 05:10:58 +0700 Subject: [PATCH 03/14] Avoid skipping extra whitespace in wikiparser.js (#7835) When wikiparser parses text looking for a pragma block, it skips whitespace before looking for the next pragma. If no pragma is found, we should return the parse position to the original location so that the skipped whitespace can be parsed as a text node. This allows the attribute `join=" and "` to parse as " and " rather than "and ". --- core/modules/parsers/wikiparser/wikiparser.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js index 293b7d3d3..1606d982d 100644 --- a/core/modules/parsers/wikiparser/wikiparser.js +++ b/core/modules/parsers/wikiparser/wikiparser.js @@ -194,6 +194,7 @@ Parse any pragmas at the beginning of a block of parse text WikiParser.prototype.parsePragmas = function() { var currentTreeBranch = this.tree; while(true) { + var savedPos = this.pos; // Skip whitespace this.skipWhitespace(); // Check for the end of the text @@ -204,6 +205,7 @@ WikiParser.prototype.parsePragmas = function() { var nextMatch = this.findNextMatch(this.pragmaRules,this.pos); // If not, just exit if(!nextMatch || nextMatch.matchIndex !== this.pos) { + this.pos = savedPos; break; } // Process the pragma rule From e4bf7c5f440cc5eb1fc499cec29eac37382add12 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Sat, 18 Nov 2023 17:13:27 +0100 Subject: [PATCH 04/14] fix the add field button tooltip (#7842) --- core/ui/EditTemplate/fields.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/EditTemplate/fields.tid b/core/ui/EditTemplate/fields.tid index e4381cbe7..0edc33505 100644 --- a/core/ui/EditTemplate/fields.tid +++ b/core/ui/EditTemplate/fields.tid @@ -54,7 +54,7 @@ $:/config/EditTemplateFields/Visibility/$(currentField)$ \whitespace trim <$vars name={{{ [get[text]] }}}> <$reveal type="nomatch" text="" default=<>> -<$button tooltip=<>> +<$button tooltip={{$:/language/EditTemplate/Fields/Add/Button/Hint}}> <$action-sendmessage $message="tm-add-field" $name=<> $value={{{ [subfilterget[text]] }}}/> From bf8b3cff03adee1c76fa03914bdfd22b34e790f4 Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Mon, 20 Nov 2023 09:38:04 +0100 Subject: [PATCH 05/14] Fixes Text Parser being impacted by overrides to codeblock widget (#7844) * fix: overriding codeblock widget should not impact text parser * fix: whitespace changes --- core/modules/parsers/textparser.js | 9 +++++---- ...tomWidget-CodeblockOverride-TextParser.tid | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-CodeblockOverride-TextParser.tid diff --git a/core/modules/parsers/textparser.js b/core/modules/parsers/textparser.js index 06b08f30f..17f9bde10 100644 --- a/core/modules/parsers/textparser.js +++ b/core/modules/parsers/textparser.js @@ -14,10 +14,12 @@ The plain text parser processes blocks of source text into a degenerate parse tr var TextParser = function(type,text,options) { this.tree = [{ - type: "codeblock", + type: "genesis", attributes: { - code: {type: "string", value: text}, - language: {type: "string", value: type} + $type: {name: "$type", type: "string", value: "$codeblock"}, + code: {name: "code", type: "string", value: text}, + language: {name: "language", type: "string", value: type}, + $remappable: {name: "$remappable", type:"string", value: "no"} } }]; this.source = text; @@ -32,4 +34,3 @@ exports["text/css"] = TextParser; exports["application/x-tiddler-dictionary"] = TextParser; })(); - diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-CodeblockOverride-TextParser.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-CodeblockOverride-TextParser.tid new file mode 100644 index 000000000..484f0c4a3 --- /dev/null +++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-CodeblockOverride-TextParser.tid @@ -0,0 +1,20 @@ +title: Transclude/CustomWidget/CodeblockOverride-TextParser +description: Test that overriding codeblock widget does not impact text parser +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim +\widget $codeblock(code) +<$transclude $variable="copy-to-clipboard" src=<>/> +<$genesis $type="$codeblock" $remappable="no" code=<>/> +\end + +\procedure myvariable() hello + +<$transclude $variable="myvariable" $type="text/plain" $output="text/plain"/> ++ +title: ExpectedResult + +

hello

\ No newline at end of file From ab72cc7b097a504fc2ab6edadfb0e987075f1475 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 21 Nov 2023 18:05:13 +0700 Subject: [PATCH 06/14] Allow negative indexes in json operators (#7849) * Add unit tests for negative indexes in json ops * Allow negative indexes in JSON operators Negative indexes will be treated as counting from the end, so -1 means last item of the array, -2 means next-to-last item, and so on. * Add documentation for negative indexes --- core/modules/filters/json-ops.js | 24 +++++++++++++++---- .../test/tiddlers/tests/test-json-filters.js | 21 ++++++++++++++++ .../tw5.com/tiddlers/filters/jsonextract.tid | 8 +++++++ editions/tw5.com/tiddlers/filters/jsonget.tid | 8 +++++++ editions/tw5.com/tiddlers/filters/jsonset.tid | 8 +++++++ .../tw5.com/tiddlers/filters/jsontype.tid | 8 +++++++ 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js index 51e509432..75a34e94a 100644 --- a/core/modules/filters/json-ops.js +++ b/core/modules/filters/json-ops.js @@ -213,6 +213,18 @@ function getDataItemType(data,indexes) { } } +function getItemAtIndex(item,index) { + if($tw.utils.hop(item,index)) { + return item[index]; + } else if($tw.utils.isArray(item)) { + index = $tw.utils.parseInt(index); + if(index < 0) { index = index + item.length }; + return item[index]; // Will be undefined if index was out-of-bounds + } else { + return undefined; + } +} + /* Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid */ @@ -225,7 +237,7 @@ function getDataItem(data,indexes) { for(var i=0; i> operator uses multiple operands to specify the indexes o [jsonextract[d],[g]] --> {"x":"max","y":"may","z":"maize"} ``` +<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on: + +``` +[jsonextract[d],[f],[-1]] --> null +[jsonextract[d],[f],[-2]] --> false +[jsonextract[d],[f],[-4]] --> "six" +``` + Indexes can be dynamically composed from variables and transclusions: ``` diff --git a/editions/tw5.com/tiddlers/filters/jsonget.tid b/editions/tw5.com/tiddlers/filters/jsonget.tid index d9caa680e..c50cbd6f2 100644 --- a/editions/tw5.com/tiddlers/filters/jsonget.tid +++ b/editions/tw5.com/tiddlers/filters/jsonget.tid @@ -51,6 +51,14 @@ The <<.op jsonget>> operator uses multiple operands to specify the indexes of th [jsonget[d],[f],[0]] --> "five" ``` +<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on: + +``` +[jsonget[d],[f],[-1]] --> null +[jsonget[d],[f],[-2]] --> false +[jsonget[d],[f],[-4]] --> "six" +``` + Indexes can be dynamically composed from variables and transclusions: ``` diff --git a/editions/tw5.com/tiddlers/filters/jsonset.tid b/editions/tw5.com/tiddlers/filters/jsonset.tid index 9f70f6eb4..81552c7a1 100644 --- a/editions/tw5.com/tiddlers/filters/jsonset.tid +++ b/editions/tw5.com/tiddlers/filters/jsonset.tid @@ -51,6 +51,14 @@ The <<.op jsonset>> operator uses multiple operands to specify the indexes of th [jsonset[d],[f],[Panther]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": "Panther","g": {"x": "max","y": "may","z": "maize"}}"} ``` +Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on: + +``` +[jsonset[d],[f],[-1],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","six",true,false,"Elephant"],"g": {"x": "max","y": "may","z": "maize"}}"} +[jsonset[d],[f],[-2],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","six",true,"Elephant",null],"g": {"x": "max","y": "may","z": "maize"}}"} +[jsonset[d],[f],[-4],[Elephant]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five","Elephant",true,false,null],"g": {"x": "max","y": "may","z": "maize"}}"} +``` + Indexes can be dynamically composed from variables and transclusions: ``` diff --git a/editions/tw5.com/tiddlers/filters/jsontype.tid b/editions/tw5.com/tiddlers/filters/jsontype.tid index b88f865dd..6bff01914 100644 --- a/editions/tw5.com/tiddlers/filters/jsontype.tid +++ b/editions/tw5.com/tiddlers/filters/jsontype.tid @@ -61,6 +61,14 @@ The <<.op jsontype>> operator uses multiple operands to specify the indexes of t [jsontype[d],[f],[2]] --> "boolean" ``` +<<.from-version "5.3.2">> Negative indexes into an array are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on: + +``` +[jsontype[d],[f],[-1]] --> "null" +[jsontype[d],[f],[-2]] --> "boolean" +[jsontype[d],[f],[-4]] --> "string" +``` + Indexes can be dynamically composed from variables and transclusions: ``` From 9012d36859d33db23183d93d14d2bbc351dd235f Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 21 Nov 2023 11:23:10 +0000 Subject: [PATCH 07/14] Allow scrollable widget to bind scroll position to a tiddler (#7649) * Initial Commit * Update version number --- core/modules/widgets/scrollable.js | 40 +++++++++++++++++++ .../system/temp-my-scroll-position.tid | 3 ++ .../tiddlers/widgets/ScrollableWidget.tid | 29 +++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 editions/prerelease/tiddlers/system/temp-my-scroll-position.tid diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js index 15b61e0c8..c95820ef3 100644 --- a/core/modules/widgets/scrollable.js +++ b/core/modules/widgets/scrollable.js @@ -171,6 +171,42 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) { parent.insertBefore(this.outerDomNode,nextSibling); this.renderChildren(this.innerDomNode,null); this.domNodes.push(this.outerDomNode); + // If the scroll position is bound to a tiddler + if(this.scrollableBind) { + // After a delay for rendering, scroll to the bound position + setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50); + // Save scroll position on DOM scroll event + this.outerDomNode.addEventListener("scroll",function(event) { + var existingTiddler = self.wiki.getTiddler(self.scrollableBind), + newTiddlerFields = { + title: self.scrollableBind, + "scroll-left": self.outerDomNode.scrollLeft.toString(), + "scroll-top": self.outerDomNode.scrollTop.toString() + }; + if(!existingTiddler || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) { + self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields)); + } + }); + } +}; + +ScrollableWidget.prototype.updateScrollPositionFromBoundTiddler = function() { + var tiddler = this.wiki.getTiddler(this.scrollableBind); + if(tiddler) { + var scrollLeftTo = this.outerDomNode.scrollLeft; + if(parseFloat(tiddler.fields["scroll-left"]).toString() === tiddler.fields["scroll-left"]) { + scrollLeftTo = parseFloat(tiddler.fields["scroll-left"]); + } + var scrollTopTo = this.outerDomNode.scrollTop; + if(parseFloat(tiddler.fields["scroll-top"]).toString() === tiddler.fields["scroll-top"]) { + scrollTopTo = parseFloat(tiddler.fields["scroll-top"]); + } + this.outerDomNode.scrollTo({ + top: scrollTopTo, + left: scrollLeftTo, + behavior: "instant" + }) + } }; /* @@ -178,6 +214,7 @@ Compute the internal state of the widget */ ScrollableWidget.prototype.execute = function() { // Get attributes + this.scrollableBind = this.getAttribute("bind"); this.fallthrough = this.getAttribute("fallthrough","yes"); this["class"] = this.getAttribute("class"); // Make child widgets @@ -193,6 +230,9 @@ ScrollableWidget.prototype.refresh = function(changedTiddlers) { this.refreshSelf(); return true; } + if(changedAttributes.bind || changedTiddlers[this.getAttribute("bind")]) { + this.updateScrollPositionFromBoundTiddler(); + } return this.refreshChildren(changedTiddlers); }; diff --git a/editions/prerelease/tiddlers/system/temp-my-scroll-position.tid b/editions/prerelease/tiddlers/system/temp-my-scroll-position.tid new file mode 100644 index 000000000..c4a164070 --- /dev/null +++ b/editions/prerelease/tiddlers/system/temp-my-scroll-position.tid @@ -0,0 +1,3 @@ +title: $:/my-scroll-position +scroll-left: 0 +scroll-top: 100 diff --git a/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid b/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid index 6fda3a974..d31eb6e31 100644 --- a/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid @@ -1,6 +1,6 @@ caption: scrollable created: 20140324223413403 -modified: 20220620115347910 +modified: 20230731100903977 tags: Widgets title: ScrollableWidget type: text/vnd.tiddlywiki @@ -16,12 +16,15 @@ The content of the `<$scrollable>` widget is displayed within a pair of wrapper |!Attribute |!Description | |class |The CSS class(es) to be applied to the outer DIV | |fallthrough |See below | +|bind |<<.from-version "5.3.2">> Optional title of tiddler to which the scroll position should be bound | + +Binding the scroll position to a tiddler automatically copies the scroll coordinates into the `scroll-left` and `scroll-top` fields as scrolling occurs. Conversely, setting those field values will automatically cause the scrollable to scroll if it can. <$macrocall $name=".note" _="""If a scrollable widget can't handle the `tm-scroll` message because the inner DIV fits within the outer DIV, then by default the message falls through to the parent widget. Setting the ''fallthrough'' attribute to `no` prevents this behaviour."""/> ! Examples -This example requires the following CSS definitions from [[$:/_tw5.com-styles]]: +These examples require the following CSS definitions from [[$:/_tw5.com-styles]]: ``` .tc-scrollable-demo { @@ -33,6 +36,8 @@ This example requires the following CSS definitions from [[$:/_tw5.com-styles]]: } ``` +!! Simple Usage + This wiki text shows how to display a list within the scrollable widget: < @@ -46,3 +51,23 @@ This wiki text shows how to display a list within the scrollable widget: ">> +!! Binding scroll position to a tiddler + +[[Current scroll position|$:/my-scroll-position]]: {{$:/my-scroll-position!!scroll-left}}, {{$:/my-scroll-position!!scroll-top}} + +<$button> +<$action-setfield $tiddler="$:/my-scroll-position" scroll-left="100" scroll-top="100"/> +Set current scroll position to 100,100 + + +< +<$list filter='[tag[Reference]]'> + +<$view field='title'/>: <$list filter='[all[current]links[]sort[title]]' storyview='pop'> +<$link><$view field='title'/> + + + + +">> + From 145a8d699253fcfadfc0b46658fdd13680d9a0b0 Mon Sep 17 00:00:00 2001 From: Scott Sauyet Date: Tue, 21 Nov 2023 06:24:17 -0500 Subject: [PATCH 08/14] Simplify Permalink/Permaview URLs (#7729) * Simplify Permalink/Permaview URLs * Fix lint warnings by removing arrow functions * Remove commented sample code * Remove post-ES5 code * Add many more allowable non-percent-encodedcharacters * Fix more ES6+ stuff, add end-of-sentence padding character. * Fix to match standards * Move the new code from boot to util * Change from custom map/filter to $tw.utils.each * Make `each` blocks multi-line * Move the permalink handling to its own file * Remove auto-navigation * Revert "Remove auto-navigation" This reverts commit ca1e5cf387922d8dd2883cb37f8d85e187158c90. --- core/modules/startup/story.js | 14 +- core/modules/utils/twuri-encoding.js | 128 ++++++++++++++++++ .../tw5.com/tiddlers/concepts/PermaLinks.tid | 16 +++ 3 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 core/modules/utils/twuri-encoding.js diff --git a/core/modules/startup/story.js b/core/modules/startup/story.js index 734f6ae76..da2df6542 100644 --- a/core/modules/startup/story.js +++ b/core/modules/startup/story.js @@ -122,10 +122,10 @@ function openStartupTiddlers(options) { var hash = $tw.locationHash.substr(1), split = hash.indexOf(":"); if(split === -1) { - target = $tw.utils.decodeURIComponentSafe(hash.trim()); + target = $tw.utils.decodeTWURITarget(hash.trim()); } else { - target = $tw.utils.decodeURIComponentSafe(hash.substr(0,split).trim()); - storyFilter = $tw.utils.decodeURIComponentSafe(hash.substr(split + 1).trim()); + target = $tw.utils.decodeTWURITarget(hash.substr(0,split).trim()); + storyFilter = $tw.utils.decodeTWURIList(hash.substr(split + 1).trim()); } } // If the story wasn't specified use the current tiddlers or a blank story @@ -198,19 +198,19 @@ function updateLocationHash(options) { // Assemble the location hash switch(options.updateAddressBar) { case "permalink": - $tw.locationHash = "#" + encodeURIComponent(targetTiddler); + $tw.locationHash = "#" + $tw.utils.encodeTiddlerTitle(targetTiddler); break; case "permaview": - $tw.locationHash = "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList)); + $tw.locationHash = "#" + $tw.utils.encodeTiddlerTitle(targetTiddler) + ":" + $tw.utils.encodeFilterPath($tw.utils.stringifyList(storyList)); break; } // Copy URL to the clipboard switch(options.copyToClipboard) { case "permalink": - $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler)); + $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + $tw.utils.encodeTiddlerTitle(targetTiddler)); break; case "permaview": - $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList))); + $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + $tw.utils.encodeTiddlerTitle(targetTiddler) + ":" + $tw.utils.encodeFilterPath($tw.utils.stringifyList(storyList))); break; } // Only change the location hash if we must, thus avoiding unnecessary onhashchange events diff --git a/core/modules/utils/twuri-encoding.js b/core/modules/utils/twuri-encoding.js new file mode 100644 index 000000000..f4a5bd947 --- /dev/null +++ b/core/modules/utils/twuri-encoding.js @@ -0,0 +1,128 @@ +/*\ +title: $:/core/modules/utils/twuri-encoding.js +type: application/javascript +module-type: utils + +Utility functions related to permalink/permaview encoding/decoding. + +\*/ +(function(){ + + // The character that will substitute for a space in the URL + var SPACE_SUBSTITUTE = "_"; + + // The character added to the end to avoid ending with `.`, `?`, `!` or the like + var TRAILER = "_"; + + // The character that will separate out the list elements in the URL + var CONJUNCTION = ";"; + + // Those of the allowed url characters claimed by TW + var CLAIMED = [SPACE_SUBSTITUTE, ":", CONJUNCTION]; + + // Non-alphanumeric characters allowed in a URL fragment + // More information at https://www.rfc-editor.org/rfc/rfc3986#appendix-A + var VALID_IN_URL_FRAGMENT = "-._~!$&'()*+,;=:@/?".split(""); + + // The subset of the pchars we will not percent-encode in permalinks/permaviews + var SUBSTITUTES = [] + $tw.utils.each(VALID_IN_URL_FRAGMENT, function(c) { + if (CLAIMED.indexOf(c) === -1) { + SUBSTITUTES.push(c) + } + }); + + // A regex to match the percent-encoded characters we will want to replace. + // Something similar to the following, depending on SPACE and CONJUNCTION + // /(%2D|%2E|%7E|%21|%24|%26|%27|%28|%29|%2A|%2B|%3B|%3D|%40|%2F|%3F)/g + + var CHAR_MATCH_STR = [] + $tw.utils.each(SUBSTITUTES, function(c) { + CHAR_MATCH_STR.push("%" + c.charCodeAt(0).toString(16).toUpperCase()) + }) + var CHAR_MATCH = new RegExp("(" + CHAR_MATCH_STR.join("|") + ")", "g"); + + // A regex to match the SPACE_SUBSTITUTE character + var SPACE_MATCH = new RegExp("(\\" + SPACE_SUBSTITUTE + ")", "g"); + + // A regex to match URLs ending with sentence-ending punctuation + var SENTENCE_ENDING = new RegExp("(\\.|\\!|\\?|\\" + TRAILER + ")$", "g"); + + // A regex to match URLs ending with sentence-ending punctuation plus the TRAILER + var SENTENCE_TRAILING = new RegExp("(\\.|\\!|\\?|\\" + TRAILER + ")\\" + TRAILER + "$", "g"); + + // An object mapping the percent encodings back to their source characters + var PCT_CHAR_MAP = SUBSTITUTES.reduce(function (a, c) { + a["%" + c.charCodeAt(0).toString(16).toUpperCase()] = c + return a + }, {}); + + // Convert a URI List Component encoded string (with the `SPACE_SUBSTITUTE` + // value as an allowed replacement for the space character) to a string + exports.decodeTWURIList = function(s) { + var parts = s.replace(SENTENCE_TRAILING, "$1").split(CONJUNCTION); + var withSpaces = [] + $tw.utils.each(parts, function(s) { + withSpaces.push(s.replace(SPACE_MATCH, " ")) + }); + var withBrackets = [] + $tw.utils.each(withSpaces, function(s) { + withBrackets .push(s.indexOf(" ") >= 0 ? "[[" + s + "]]" : s) + }); + return $tw.utils.decodeURIComponentSafe(withBrackets.join(" ")); + }; + + // Convert a URI Target Component encoded string (with the `SPACE_SUBSTITUTE` + // value as an allowed replacement for the space character) to a string + exports.decodeTWURITarget = function(s) { + return $tw.utils.decodeURIComponentSafe( + s.replace(SENTENCE_TRAILING, "$1").replace(SPACE_MATCH, " ") + ) + }; + + // Convert a URIComponent encoded title string (with the `SPACE_SUBSTITUTE` + // value as an allowed replacement for the space character) to a string + exports.encodeTiddlerTitle = function(s) { + var extended = s.replace(SENTENCE_ENDING, "$1" + TRAILER) + var encoded = encodeURIComponent(extended); + var substituted = encoded.replace(/\%20/g, SPACE_SUBSTITUTE); + return substituted.replace(CHAR_MATCH, function(_, c) { + return PCT_CHAR_MAP[c]; + }); + }; + + // Convert a URIComponent encoded filter string (with the `SPACE_SUBSTITUTE` + // value as an allowed replacement for the space character) to a string + exports.encodeFilterPath = function(s) { + var parts = s.replace(SENTENCE_ENDING, "$1" + TRAILER) + .replace(/\[\[(.+?)\]\]/g, function (_, t) {return t.replace(/ /g, SPACE_SUBSTITUTE )}) + .split(" "); + var nonEmptyParts = [] + $tw.utils.each(parts, function(p) { + if (p) { + nonEmptyParts.push (p) + } + }); + var trimmed = []; + $tw.utils.each(nonEmptyParts, function(s) { + trimmed.push(s.trim()) + }); + var encoded = []; + $tw.utils.each(trimmed, function(s) { + encoded.push(encodeURIComponent(s)) + }); + var substituted = []; + $tw.utils.each(encoded, function(s) { + substituted.push(s.replace(/\%20/g, SPACE_SUBSTITUTE)) + }); + var replaced = [] + $tw.utils.each(substituted, function(s) { + replaced.push(s.replace(CHAR_MATCH, function(_, c) { + return PCT_CHAR_MAP[c]; + })) + }); + return replaced.join(CONJUNCTION); + }; + +})(); + \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/concepts/PermaLinks.tid b/editions/tw5.com/tiddlers/concepts/PermaLinks.tid index 40c7a1925..1b15460fa 100644 --- a/editions/tw5.com/tiddlers/concepts/PermaLinks.tid +++ b/editions/tw5.com/tiddlers/concepts/PermaLinks.tid @@ -40,6 +40,22 @@ There are technical restrictions on the legal characters in an URL fragment. To Both the target tiddler title and the story filter should be URL encoded (but not the separating colon). TiddlyWiki generates properly encoded URLs which can look quite ugly. However, in practice browsers will usually perfectly happily process arbitrary characters in URL fragments. Thus when creating permalinks manually you can choose to ignore URL encoding. +!! Simpler URLS + +<<.from-version "5.3.2">> The URLs generated are simplified from the hard-to-read percent encoding when feasible. Spaces are replaced with underscores (`_`), many punctuation characters are allowed to remain unencoded, and permaview filters receive a simpler encoding. For example the tiddler "Hard Linebreaks with CSS - Example", which percent-encoded would look like + +> @@font-family:monospace;#Hard%20Linebreaks%20with%20CSS%20-%20Example@@ + +instead looks like + +> @@font-family:monospace;#Hard_Linebreaks_with_CSS_-_Example@@ + +Existing story filter URLs like + +> @@font-family:monospace;#:[tag[Features]]%20+[limit[5]]@@ + +will continue to work. + ! Permalink Behaviour Two important aspects of TiddlyWiki's behaviour with permalinks can be controlled via options in the [[control panel|$:/ControlPanel]] <<.icon $:/core/images/options-button>> ''Settings'' tab: From a4850ba3d9864317979fee308888b01584dd092b Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Tue, 21 Nov 2023 12:30:05 +0100 Subject: [PATCH 09/14] make all list-* macros readable for easier future improvements (#7551) * make all list-* widgets readable for easier future improvements * remove whitespace on closing braces --- core/wiki/macros/list.tid | 173 +++++++++++++++++++++----------------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/core/wiki/macros/list.tid b/core/wiki/macros/list.tid index 5464ecad1..c9dd2ad71 100644 --- a/core/wiki/macros/list.tid +++ b/core/wiki/macros/list.tid @@ -4,17 +4,17 @@ tags: $:/tags/Macro \define list-links(filter,type:"ul",subtype:"li",class:"",emptyMessage,field:"caption") \whitespace trim <$genesis $type=<<__type__>> class=<<__class__>>> -<$list filter=<<__filter__>> emptyMessage=<<__emptyMessage__>>> -<$genesis $type=<<__subtype__>>> -<$link to={{!!title}}> -<$let tv-wikilinks="no"> -<$transclude field=<<__field__>>> -<$view field="title"/> - - - - - + <$list filter=<<__filter__>> emptyMessage=<<__emptyMessage__>>> + <$genesis $type=<<__subtype__>>> + <$link to={{!!title}}> + <$let tv-wikilinks="no"> + <$transclude field=<<__field__>>> + <$view field="title"/> + + + + + \end @@ -25,34 +25,42 @@ tags: $:/tags/Macro \define list-links-draggable(tiddler,field:"list",emptyMessage,type:"ul",subtype:"li",class:"",itemTemplate) \whitespace trim -<$vars targetTiddler="""$tiddler$""" targetField="""$field$"""> -<$genesis $type=<<__type__>> class="$class$"> -<$list filter="[list[$tiddler$!!$field$]]" emptyMessage=<<__emptyMessage__>>> -<$droppable actions=<> tag="""$subtype$""" enable=<>> -
-
-<$transclude tiddler="""$itemTemplate$"""> -<$link to={{!!title}}> -<$let tv-wikilinks="no"> -<$transclude field="caption"> -<$view field="title"/> - - - - -
- - -<$tiddler tiddler=""> -<$droppable actions=<> tag="div" enable=<>> -
-{{$:/core/images/blank}} -
-
- - - - + <$vars targetTiddler="""$tiddler$""" targetField="""$field$"""> + <$genesis $type=<<__type__>> class="$class$"> + <$list filter="[list[$tiddler$!!$field$]]" emptyMessage=<<__emptyMessage__>>> + <$droppable + actions=<> + tag="""$subtype$""" + enable=<> + > +
+
+ <$transclude tiddler="""$itemTemplate$"""> + <$link to={{!!title}}> + <$let tv-wikilinks="no"> + <$transclude field="caption"> + <$view field="title"/> + + + + +
+ + + <$tiddler tiddler=""> + <$droppable + actions=<> + tag="div" + enable=<> + > +
+ {{$:/core/images/blank}} +
+
+ + + + \end @@ -60,50 +68,59 @@ tags: $:/tags/Macro \whitespace trim <$set name="order" filter="[<__tag__>tagging[]]"> - -<$list filter="[<__tag__>tagging[]]"> -<$action-deletefield $field="list-before"/> -<$action-deletefield $field="list-after"/> - - -<$action-listops $tiddler=<<__tag__>> $field="list" $filter="+[enlist] +[insertbefore,]"/> - - - - -<$list filter="[!contains:tags<__tag__>]"> -<$fieldmangler tiddler=<>> -<$action-sendmessage $message="tm-add-tag" $param=<<__tag__>>/> - - + + <$list filter="[<__tag__>tagging[]]"> + <$action-deletefield $field="list-before"/> + <$action-deletefield $field="list-after"/> + + + <$action-listops $tiddler=<<__tag__>> $field="list" $filter="+[enlist] +[insertbefore,]"/> + + + + + <$list filter="[!contains:tags<__tag__>]"> + <$fieldmangler tiddler=<>> + <$action-sendmessage $message="tm-add-tag" $param=<<__tag__>>/> + + \end \define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"") \whitespace trim -<$set name="tag" value=<<__tag__>>> -<$list filter="[<__tag__>tagging[]$subFilter$]" emptyMessage=<<__emptyMessage__>> storyview=<<__storyview__>>> -<$genesis $type=<<__elementTag__>> class="tc-menu-list-item"> -<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<>> -<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/> -<$genesis $type=<<__elementTag__>>> -<$transclude tiddler="""$itemTemplate$"""> -<$link to={{!!title}}> -<$view field="title"/> - - - - - - -<$tiddler tiddler=""> -<$droppable actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" enable=<>> -<$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/> -<$genesis $type=<<__elementTag__>> style="height:0.5em;"> - - - - + <$set name="tag" value=<<__tag__>>> + <$list + filter="[<__tag__>tagging[]$subFilter$]" + emptyMessage=<<__emptyMessage__>> + storyview=<<__storyview__>> + > + <$genesis $type=<<__elementTag__>> class="tc-menu-list-item"> + <$droppable + actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" + enable=<> + > + <$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/> + <$genesis $type=<<__elementTag__>>> + <$transclude tiddler="""$itemTemplate$"""> + <$link to={{!!title}}> + <$view field="title"/> + + + + + + + <$tiddler tiddler=""> + <$droppable + actions="""<$macrocall $name="list-tagged-draggable-drop-actions" tag=<<__tag__>>/>""" + enable=<> + > + <$genesis $type=<<__elementTag__>> class="tc-droppable-placeholder"/> + <$genesis $type=<<__elementTag__>> style="height:0.5em;"/> + + + \end From 37c625384af79deba0934e3071bcfc5ebe9f4349 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Tue, 21 Nov 2023 12:42:17 +0100 Subject: [PATCH 10/14] add archive HTML wikis, add index.html + css (#7776) * add archive HTML wikis, add index.html + css * only build archive for release version * tested and add more docs * fix indent * fix spacing * add $TW5_BUILD_OUTPUT_ARCHIVE env variable for testing * use $TW5_BUILD_OUTPUT_ARCHIVE to check if archive should be built * use TW5_BUILD_ARCHIVE as requested --- .github/workflows/ci.yml | 1 + bin/build-site.sh | 20 +++++++++++++++++++- editions/tw5.com/tiddlywiki.info | 7 ++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a146d15a8..737d523ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,7 @@ jobs: TW5_BUILD_TIDDLYWIKI: "./node_modules/tiddlywiki/tiddlywiki.js" TW5_BUILD_MAIN_EDITION: "./editions/tw5.com" TW5_BUILD_OUTPUT: "./output" + TW5_BUILD_ARCHIVE: "./output" steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 diff --git a/bin/build-site.sh b/bin/build-site.sh index aa8a29f63..5b36de4e1 100755 --- a/bin/build-site.sh +++ b/bin/build-site.sh @@ -84,10 +84,27 @@ echo -e -n "title: $:/build\ncommit: $TW5_BUILD_COMMIT\n\n$TW5_BUILD_DETAILS\n" ###################################################### # -# Core distribution +# Core distributions # ###################################################### +# Conditionally build archive if $TW5_BUILD_ARCHIVE variable is set, otherwise do nothing +# +# /archive/Empty-TiddlyWiki-.html Empty archived version +# /archive/TiddlyWiki-.html Full archived version + +if [ -n "$TW5_BUILD_ARCHIVE" ]; then + +node $TW5_BUILD_TIDDLYWIKI \ + $TW5_BUILD_MAIN_EDITION \ + --verbose \ + --version \ + --load $TW5_BUILD_OUTPUT/build.tid \ + --output $TW5_BUILD_ARCHIVE \ + --build archive \ + || exit 1 +fi + # /index.html Main site # /favicon.ico Favicon for main site # /static.html Static rendering of default tiddlers @@ -95,6 +112,7 @@ echo -e -n "title: $:/build\ncommit: $TW5_BUILD_COMMIT\n\n$TW5_BUILD_DETAILS\n" # /static/* Static single tiddlers # /static/static.css Static stylesheet # /static/favicon.ico Favicon for static pages + node $TW5_BUILD_TIDDLYWIKI \ $TW5_BUILD_MAIN_EDITION \ --verbose \ diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 2065260bc..5ce9a2f1b 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -54,7 +54,12 @@ "--render","$:/core/templates/static.template.css","static/static.css","text/plain"], "external-js": [ "--render","$:/core/save/offline-external-js","[[external-]addsuffixaddsuffix[.html]]","text/plain", - "--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffixaddsuffix[.js]]","text/plain"] + "--render","$:/core/templates/tiddlywiki5.js","[[tiddlywikicore-]addsuffixaddsuffix[.js]]","text/plain"], + "archive":[ + "--render","$:/core/save/all","[[archive/TiddlyWiki-]addsuffixaddsuffix[.html]]","text/plain", + "--render","$:/editions/tw5.com/download-empty","[[archive/Empty-TiddlyWiki-]addsuffixaddsuffix[.html]]","text/plain", + "--render","[[TiddlyWiki Archive]]","archive/index.html","text/plain","$:/core/templates/static.tiddler.html", + "--render","$:/core/templates/static.template.css","archive/static.css","text/plain"] }, "config": { "retain-original-tiddler-path": true From 0cd3c9a8ac42c0c9ac7d4fe80379a96583c1fd33 Mon Sep 17 00:00:00 2001 From: Eric Haberstroh Date: Tue, 21 Nov 2023 12:44:39 +0100 Subject: [PATCH 11/14] Add usemap attribute to image widget (#7634) * Add usemap attribute to image macro Allow for a usemap attribute on the $image macro call which is passed through to the resulting img tag. This makes the use of HTML image maps [1] possible. [1]: * Document new usemap attribute in ImageWidget * Update version docs --------- Co-authored-by: Jeremy Ruston --- core/modules/widgets/image.js | 6 +++++- editions/tw5.com/tiddlers/widgets/ImageWidget.tid | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/modules/widgets/image.js b/core/modules/widgets/image.js index 533b657cc..52496fd74 100644 --- a/core/modules/widgets/image.js +++ b/core/modules/widgets/image.js @@ -100,6 +100,9 @@ ImageWidget.prototype.render = function(parent,nextSibling) { if(this.imageClass) { domNode.setAttribute("class",this.imageClass); } + if(this.imageUsemap) { + domNode.setAttribute("usemap",this.imageUsemap); + } if(this.imageWidth) { domNode.setAttribute("width",this.imageWidth); } @@ -139,6 +142,7 @@ ImageWidget.prototype.execute = function() { this.imageWidth = this.getAttribute("width"); this.imageHeight = this.getAttribute("height"); this.imageClass = this.getAttribute("class"); + this.imageUsemap = this.getAttribute("usemap"); this.imageTooltip = this.getAttribute("tooltip"); this.imageAlt = this.getAttribute("alt"); this.lazyLoading = this.getAttribute("loading"); @@ -149,7 +153,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of */ ImageWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.tooltip || changedTiddlers[this.imageSource]) { + if(changedAttributes.source || changedAttributes.width || changedAttributes.height || changedAttributes["class"] || changedAttributes.usemap || changedAttributes.tooltip || changedTiddlers[this.imageSource]) { this.refreshSelf(); return true; } else { diff --git a/editions/tw5.com/tiddlers/widgets/ImageWidget.tid b/editions/tw5.com/tiddlers/widgets/ImageWidget.tid index c888c3a31..0f4bd9012 100644 --- a/editions/tw5.com/tiddlers/widgets/ImageWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ImageWidget.tid @@ -1,6 +1,6 @@ caption: image created: 20140416160234142 -modified: 20220721102303815 +modified: 20231121114351165 tags: Widgets title: ImageWidget type: text/vnd.tiddlywiki @@ -21,6 +21,7 @@ Any content of the `<$image>` widget is ignored. |alt |The alternative text to be associated with the image | |class |CSS classes to be assigned to the `` element | |loading|<<.from-version "5.2.3">>Optional. Set to `lazy` to enable lazy loading of images loaded from an external URI | +|usemap|<<.from-version "5.3.2">>Optional usemap attribute to be assigned to the `` element for use with HTML image maps | The width and the height can be specified as pixel values (eg "23" or "23px") or percentages (eg "23%"). They are both optional; if not provided the browser will use CSS rules to size the image. From 06b1cc4bca54f8b9a2d205d7cf194fb0caa79a81 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 21 Nov 2023 11:49:34 +0000 Subject: [PATCH 12/14] Scrollable widget: Fix crash in CI --- core/modules/widgets/scrollable.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js index c95820ef3..58597461b 100644 --- a/core/modules/widgets/scrollable.js +++ b/core/modules/widgets/scrollable.js @@ -191,6 +191,10 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) { }; ScrollableWidget.prototype.updateScrollPositionFromBoundTiddler = function() { + // Bail if we're running on the fakedom + if(!this.outerDomNode.scrollTo) { + return; + } var tiddler = this.wiki.getTiddler(this.scrollableBind); if(tiddler) { var scrollLeftTo = this.outerDomNode.scrollLeft; From de6b866f22a8262c8c531e8fbace999563148aa2 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 21 Nov 2023 11:54:37 +0000 Subject: [PATCH 13/14] Fix detection of DOM properties Alternate fix for #7714 --- core/modules/utils/dom/dom.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/modules/utils/dom/dom.js b/core/modules/utils/dom/dom.js index 9c06fc8b0..338d96280 100644 --- a/core/modules/utils/dom/dom.js +++ b/core/modules/utils/dom/dom.js @@ -313,7 +313,7 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { variables["dom-" + attribute.name] = attribute.value.toString(); }); - if(selectedNode.offsetLeft) { + if("offsetLeft" in selectedNode) { // Add variables with a (relative and absolute) popup coordinate string for the selected node var nodeRect = { left: selectedNode.offsetLeft, @@ -338,12 +338,12 @@ exports.collectDOMVariables = function(selectedNode,domNode,event) { } } - if(domNode && domNode.offsetWidth) { + if(domNode && ("offsetWidth" in domNode)) { variables["tv-widgetnode-width"] = domNode.offsetWidth.toString(); variables["tv-widgetnode-height"] = domNode.offsetHeight.toString(); } - if(event && event.clientX && event.clientY) { + if(event && ("clientX" in event) && ("clientY" in event)) { if(selectedNode) { // Add variables for event X and Y position relative to selected node selectedNodeRect = selectedNode.getBoundingClientRect(); From 4c2979286b6836f4331b31649e3d17b7b074ca6f Mon Sep 17 00:00:00 2001 From: yaisog Date: Tue, 21 Nov 2023 12:58:12 +0100 Subject: [PATCH 14/14] Change separators to match doc (#7303) --- core/modules/wiki.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 3eae3902d..430c46466 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -1287,7 +1287,7 @@ exports.search = function(text,options) { console.log("Regexp error parsing /(" + text + ")/" + flags + ": ",e); } } else if(options.some) { - terms = text.trim().split(/ +/); + terms = text.trim().split(/[^\S\xA0]+/); if(terms.length === 1 && terms[0] === "") { searchTermsRegExps = null; } else { @@ -1298,7 +1298,7 @@ exports.search = function(text,options) { searchTermsRegExps.push(new RegExp("(" + regExpStr + ")",flags)); } } else { // default: words - terms = text.split(/ +/); + terms = text.split(/[^\S\xA0]+/); if(terms.length === 1 && terms[0] === "") { searchTermsRegExps = null; } else {