Date: Fri, 24 Nov 2023 10:48:48 +0000
Subject: [PATCH 06/31] Core macros don't allow pragmas in action strings
(#7855)
* First commit
* Transclude preferred over macrocall
---
core/wiki/macros/colour-picker.tid | 12 ++++++------
core/wiki/macros/image-picker.tid | 10 +++++-----
core/wiki/macros/tag-picker.tid | 4 ++--
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/core/wiki/macros/colour-picker.tid b/core/wiki/macros/colour-picker.tid
index 5c92af9df..1ff1e7b90 100644
--- a/core/wiki/macros/colour-picker.tid
+++ b/core/wiki/macros/colour-picker.tid
@@ -13,7 +13,7 @@ tags: $:/tags/Macro
$(colour-picker-update-recent)$
-$actions$
+<$transclude $variable="__actions__"/>
@@ -23,7 +23,7 @@ $actions$
\define colour-picker-recent-inner(actions)
\whitespace trim
<$set name="colour-picker-value" value="$(recentColour)$">
-<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
+<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
$set>
\end
@@ -31,7 +31,7 @@ $actions$
\whitespace trim
{{$:/language/ColourPicker/Recent}}<$list filter="[list[$:/config/ColourPicker/Recent]]" variable="recentColour">
-<$macrocall $name="colour-picker-recent-inner" actions="""$actions$"""/>
+<$macrocall $name="colour-picker-recent-inner" actions=<<__actions__>>/>
$list>
\end
@@ -39,13 +39,13 @@ $actions$
\whitespace trim
-<$macrocall $name="colour-picker-recent" actions="""$actions$"""/>
+<$macrocall $name="colour-picker-recent" actions=<<__actions__>>/>
---
<$list filter="LightPink Pink Crimson LavenderBlush PaleVioletRed HotPink DeepPink MediumVioletRed Orchid Thistle Plum Violet Magenta Fuchsia DarkMagenta Purple MediumOrchid DarkViolet DarkOrchid Indigo BlueViolet MediumPurple MediumSlateBlue SlateBlue DarkSlateBlue Lavender GhostWhite Blue MediumBlue MidnightBlue DarkBlue Navy RoyalBlue CornflowerBlue LightSteelBlue LightSlateGrey SlateGrey DodgerBlue AliceBlue SteelBlue LightSkyBlue SkyBlue DeepSkyBlue LightBlue PowderBlue CadetBlue Azure LightCyan PaleTurquoise Cyan Aqua DarkTurquoise DarkSlateGrey DarkCyan Teal MediumTurquoise LightSeaGreen Turquoise Aquamarine MediumAquamarine MediumSpringGreen MintCream SpringGreen MediumSeaGreen SeaGreen Honeydew LightGreen PaleGreen DarkSeaGreen LimeGreen Lime ForestGreen Green DarkGreen Chartreuse LawnGreen GreenYellow DarkOliveGreen YellowGreen OliveDrab Beige LightGoldenrodYellow Ivory LightYellow Yellow Olive DarkKhaki LemonChiffon PaleGoldenrod Khaki Gold Cornsilk Goldenrod DarkGoldenrod FloralWhite OldLace Wheat Moccasin Orange PapayaWhip BlanchedAlmond NavajoWhite AntiqueWhite Tan BurlyWood Bisque DarkOrange Linen Peru PeachPuff SandyBrown Chocolate SaddleBrown Seashell Sienna LightSalmon Coral OrangeRed DarkSalmon Tomato MistyRose Salmon Snow LightCoral RosyBrown IndianRed Red Brown FireBrick DarkRed Maroon White WhiteSmoke Gainsboro LightGrey Silver DarkGrey Grey DimGrey Black" variable="colour-picker-value">
-<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
+<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
$list>
---
@@ -54,7 +54,7 @@ $actions$
<$edit-text tiddler="$:/config/ColourPicker/New" type="color" tag="input"/>
<$set name="colour-picker-value" value={{$:/config/ColourPicker/New}}>
-<$macrocall $name="colour-picker-inner" actions="""$actions$"""/>
+<$macrocall $name="colour-picker-inner" actions=<<__actions__>>/>
$set>
diff --git a/core/wiki/macros/image-picker.tid b/core/wiki/macros/image-picker.tid
index 464a53e7b..5f09ced0d 100644
--- a/core/wiki/macros/image-picker.tid
+++ b/core/wiki/macros/image-picker.tid
@@ -5,13 +5,13 @@ title: $:/core/macros/image-picker
type: text/vnd.tiddlywiki
\define image-picker-thumbnail(actions)
-<$button tag="a" tooltip="""$(imageTitle)$""">$actions$<$transclude tiddler=<>/>$button>
+<$button tag="a" tooltip="""$(imageTitle)$"""><$transclude $variable="__actions__"/><$transclude tiddler=<>/>$button>
\end
\define image-picker-list(filter,actions)
\whitespace trim
<$list filter="""$filter$""" variable="imageTitle">
-<$macrocall $name="image-picker-thumbnail" actions="""$actions$"""/>
+<$macrocall $name="image-picker-thumbnail" actions=<<__actions__>>/>
$list>
\end
@@ -25,15 +25,15 @@ type: text/vnd.tiddlywiki
{{$:/language/SystemTiddlers/Include/Prompt}}
$checkbox>
<$reveal state=<> type="match" text="hide" default="hide" tag="div">
-<$macrocall $name="image-picker-list" filter="""$filter$ +[!is[system]]""" actions="""$actions$"""/>
+<$macrocall $name="image-picker-list" filter="""$filter$ +[!is[system]]""" actions=<<__actions__>>/>
$reveal>
<$reveal state=<> type="nomatch" text="hide" default="hide" tag="div">
-<$macrocall $name="image-picker-list" filter="""$filter$""" actions="""$actions$"""/>
+<$macrocall $name="image-picker-list" filter="""$filter$""" actions=<<__actions__>>/>
$reveal>
$vars>
\end
\define image-picker-include-tagged-images(actions)
-<$macrocall $name="image-picker" filter="[all[shadows+tiddlers]is[image]] [all[shadows+tiddlers]tag[$:/tags/Image]] -[type[application/pdf]] +[!has[draft.of]sort[title]]" actions="""$actions$"""/>
+<$macrocall $name="image-picker" filter="[all[shadows+tiddlers]is[image]] [all[shadows+tiddlers]tag[$:/tags/Image]] -[type[application/pdf]] +[!has[draft.of]sort[title]]" actions=<<__actions__>>/>
\end
diff --git a/core/wiki/macros/tag-picker.tid b/core/wiki/macros/tag-picker.tid
index e1b1a7139..ede53ec26 100644
--- a/core/wiki/macros/tag-picker.tid
+++ b/core/wiki/macros/tag-picker.tid
@@ -16,7 +16,7 @@ second-search-filter: [tags[]is[system]search:titlesort[]]
emptyMessage="<$action-listops $tiddler=<> $field=<<__tagField__>> $subfilter='-[]'/>"
>
<$action-listops $tiddler=<> $field=<<__tagField__>> $subfilter="[trim[]]"/>
- $actions$
+ <$transclude $variable="__actions__"/>
$list>
$set>
<>
@@ -102,7 +102,7 @@ second-search-filter: [tags[]is[system]search:titlesort[]]
<$set name="tag" value={{{ [get[text]] }}}>
<$button set=<> setTo="" class="">
<$action-listops $tiddler=<> $field=<<__tagField__>> $subfilter="[trim[]]"/>
- $actions$
+ <$transclude $variable="__actions__"/>
<$set name="currentTiddlerCSSEscaped" value={{{ [escapecss[]] }}}>
<><$action-sendmessage $message="tm-focus-selector" $param=<>/>
$set>
From 1cb607249e50344829dcbc9a4ed49335842e5058 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Fri, 24 Nov 2023 13:02:09 +0000
Subject: [PATCH 07/31] Fix syncer race condition (#7843)
* Initial commit
* Log task choosing
* A tiny bit more logging
* Typo
* Restructure syncer to use a single state machine
---
core/modules/syncer.js | 286 +++++++++++++++++++++++------------------
1 file changed, 164 insertions(+), 122 deletions(-)
diff --git a/core/modules/syncer.js b/core/modules/syncer.js
index c06fcb143..58b087b8d 100644
--- a/core/modules/syncer.js
+++ b/core/modules/syncer.js
@@ -24,7 +24,7 @@ Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
Syncer.prototype.titleSyncThrottleInterval = "$:/config/SyncThrottleInterval";
-Syncer.prototype.taskTimerInterval = 1 * 1000; // Interval for sync timer
+Syncer.prototype.taskTimerInterval = 0.25 * 1000; // Interval for sync timer
Syncer.prototype.throttleInterval = 1 * 1000; // Defer saving tiddlers if they've changed in the last 1s...
Syncer.prototype.errorRetryInterval = 5 * 1000; // Interval to retry after an error
Syncer.prototype.fallbackInterval = 10 * 1000; // Unless the task is older than 10s
@@ -74,9 +74,11 @@ function Syncer(options) {
this.titlesHaveBeenLazyLoaded = {}; // Hashmap of titles of tiddlers that have already been lazily loaded from the server
// Timers
this.taskTimerId = null; // Timer for task dispatch
- this.pollTimerId = null; // Timer for polling server
// Number of outstanding requests
this.numTasksInProgress = 0;
+ // True when we want to force an immediate sync from the server
+ this.forceSyncFromServer = false;
+ this.timestampLastSyncFromServer = new Date();
// Listen out for changes to tiddlers
this.wiki.addEventListener("change",function(changes) {
// Filter the changes to just include ones that are being synced
@@ -187,6 +189,7 @@ Syncer.prototype.readTiddlerInfo = function() {
// Record information for known tiddlers
var self = this,
tiddlers = this.getSyncedTiddlers();
+ this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers");
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.wiki.getTiddler(title);
if(tiddler) {
@@ -203,33 +206,38 @@ Syncer.prototype.readTiddlerInfo = function() {
Checks whether the wiki is dirty (ie the window shouldn't be closed)
*/
Syncer.prototype.isDirty = function() {
- this.logger.log("Checking dirty status");
- // Check tiddlers that are in the store and included in the filter function
- var titles = this.getSyncedTiddlers();
- for(var index=0; index tiddlerInfo.changeCount) {
+ var self = this;
+ function checkIsDirty() {
+ // Check tiddlers that are in the store and included in the filter function
+ var titles = self.getSyncedTiddlers();
+ for(var index=0; index tiddlerInfo.changeCount) {
+ return true;
+ }
+ } else {
+ // If the tiddler isn't known on the server then it needs to be saved to the server
return true;
}
- } else {
- // If the tiddler isn't known on the server then it needs to be saved to the server
+ }
+ }
+ // Check tiddlers that are known from the server but not currently in the store
+ titles = Object.keys(self.tiddlerInfo);
+ for(index=0; index 0 || updates.deletions.length > 0) {
- self.processTaskQueue();
- }
- }
- });
- } else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
- this.logger.log("Retrieving skinny tiddler list");
- cancelNextSync();
- this.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
- triggerNextSync();
- // Check for errors
- if(err) {
- self.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
- return;
- }
- // Keep track of which tiddlers we already know about have been reported this time
- var previousTitles = Object.keys(self.tiddlerInfo);
- // Process each incoming tiddler
- for(var t=0; t= (this.timestampLastSyncFromServer.valueOf() + this.pollTimerInterval)))) {
+ return new SyncFromServerTask(this);
+ }
+ // Third, we check tiddlers that are known from the server but not currently in the store, and so need deleting on the server
titles = Object.keys(this.tiddlerInfo);
for(index=0; index
Date: Fri, 24 Nov 2023 15:11:21 +0000
Subject: [PATCH 08/31] Update release note
---
.../prerelease/tiddlers/Release 5.3.2.tid | 35 ++++++++++++++++---
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index 17058d731..ced4957f2 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -49,7 +49,7 @@ Note that the <<.attr "emptyMessage">> and <<.attr "template">> attributes take
<<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7746">> QR Code plugin to be able to read QR codes and a number of other bar code formats
-! Translation improvement
+! Translation improvements
Improvements to the following translations:
@@ -61,10 +61,13 @@ Improvements to the following translations:
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/1be8f0a9336952d4745d2bd4f2327e353580a272">> Comments Plugin to use predefined palette colours
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7785">> Evernote Importer Plugin to support images and other attachments
+* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7790">> `$floating` attribute to Dynannotate Plugin to support popups that do not disappear when another part of the screen is clicked. Instead they have to dismissed manually
! Widget Improvements
-*
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7734">> ImageWidget encoding for more image types
+* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7634">> ImageWidget to add a "usemap" attribute
+* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7649">> the ScrollableWidget to allow the scroll position to be bound to a tiddler, so that changes to the tiddler affect the scroll position, and vice versa
! Usability Improvements
@@ -73,8 +76,10 @@ Improvements to the following translations:
! Hackability Improvements
-* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7737">> an automatic build of the external core TiddlyWiki at https://tiddlywiki.com/empty-external-core.html
+* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7769">> all the relevant core widgets to allow arbitrary `data-*` attributes and `style.*` attributes to be applied to the generated DOM nodes. This is useful for passing data to the EventCatcherWidget
+* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7849">> [[jsonextract Operator]], [[jsonget Operator]], [[jsonset Operator]] and [[jsontype Operator]] to allow negative indexes into arrays to be counted from the end of the array
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7690">> the default page layout to better support CSS grid and flexbox layouts
+* <<.link-bage-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7787">> the editor to use grid layout, simplifying customisation
! Bug Fixes
@@ -87,10 +92,20 @@ Improvements to the following translations:
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/6088">> upgrade download link in Firefox
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7698">> refreshing of transcluded functions
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7789">> resizing of height of textareas in control panel
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7683">> [[encodebase64 Operator]] and [[decodebase64 Operator]] to work properly with binary data
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7708">> [[WidgetMessage: tm-open-window]] when opening an existing window to bring it to the front and focus it
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7809">> behaviour of [[last Operator]] when zero items selected
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7806">> incorrectly setting focus on field name input field when deleting field using the delete field button
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7802">> [[Table-of-Contents Macros]] to not show expander icon for a sublist that has all children excluded
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7794">> overflow of [[CodeMirror Plugin]] editor within grid container
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7835">> wikitest parser removing whitespace when parsing pragmas
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7842">> tooltip for editor add field button
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7844">> plain text parser being susceptible to the CodeBlockWidget being redefined
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7855">> pragmas not working within the action string of several core macros
! Node.js Improvements
-*
+* <> a significant flaw in the synchronisation algorithm used by the client-server configuration. The flaw could lead to tiddlers temporarily disappearing from the browser
! Performance Improvements
@@ -100,6 +115,12 @@ Improvements to the following translations:
! Developer Improvements
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7751">> global hook handling to support removing hooks
+* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7539">> some useful npm scripts to `package.json`
+
+! Infrastructure Improvements
+
+* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7820">> Continuous Integration tests to use Playwright to run our browser-based tests
+* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7737">> an automatic build of the external core TiddlyWiki at https://tiddlywiki.com/empty-external-core.html
! Acknowledgements
@@ -110,13 +131,19 @@ AnthonyMuscio
BramChen
BuckarooBanzay
BurningTreeC
+CrossEye
EvidentlyCube
+Gk0Wk
joebordes
kookma
linonetwo
mateuszwilczek
+oflig
+pille1842
pmario
rmunn
+saqimtiaz
simonbaird
T1mL3arn
+yaisog
""">>
From 64812f5c062e3eaeaa8ef158851ffcece4babb13 Mon Sep 17 00:00:00 2001
From: Robin Munn
Date: Sat, 25 Nov 2023 16:35:05 +0700
Subject: [PATCH 09/31] Add join attribute to list widget (#7694)
* Add join attribute to list widget
* Use new join attribute in HTML saving templates
This simplifies the logic involved in saving tiddlers in JSON format
into TW html files, and should also slightly speed up the saving process
depending on how often that list widget gets refreshed.
* Unit tests for list widget's new join attribute
* Add `<$list-join>` widget
Allows specifying complicated join text more easily than an attribute
---
core/modules/widgets/list.js | 90 ++++++++++++++++---
core/templates/html-json-skinny-tiddler.tid | 1 -
core/templates/html-json-tiddler.tid | 2 +-
core/templates/store.area.template.html.tid | 8 +-
core/ui/ViewTemplate/subtitle.tid | 7 +-
.../prerelease/tiddlers/Release 5.3.2.tid | 27 ++++++
.../data/list-widget/WithJoinTemplate.tid | 30 +++++++
.../WithJoinTemplateInBlockMode.tid | 32 +++++++
editions/test/tiddlers/tests/test-widget.js | 39 ++++++++
.../tw5.com/tiddlers/widgets/ListWidget.tid | 22 ++++-
.../tiddlyweb/html-json-skinny-tiddler.tid | 1 -
.../tiddlyweb/html-json-tiddler.tid | 3 +-
12 files changed, 237 insertions(+), 25 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/list-widget/WithJoinTemplate.tid
create mode 100644 editions/test/tiddlers/tests/data/list-widget/WithJoinTemplateInBlockMode.tid
diff --git a/core/modules/widgets/list.js b/core/modules/widgets/list.js
index faedf72cc..78976f69a 100755
--- a/core/modules/widgets/list.js
+++ b/core/modules/widgets/list.js
@@ -50,8 +50,8 @@ ListWidget.prototype.render = function(parent,nextSibling) {
$tw.modules.applyMethods("storyview",this.storyViews);
}
this.parentDomNode = parent;
- this.computeAttributes();
- this.execute();
+ var changedAttributes = this.computeAttributes();
+ this.execute(changedAttributes);
this.renderChildren(parent,nextSibling);
// Construct the storyview
var StoryView = this.storyViews[this.storyViewName];
@@ -71,7 +71,7 @@ ListWidget.prototype.render = function(parent,nextSibling) {
/*
Compute the internal state of the widget
*/
-ListWidget.prototype.execute = function() {
+ListWidget.prototype.execute = function(changedAttributes) {
var self = this;
// Get our attributes
this.template = this.getAttribute("template");
@@ -80,6 +80,10 @@ ListWidget.prototype.execute = function() {
this.counterName = this.getAttribute("counter");
this.storyViewName = this.getAttribute("storyview");
this.historyTitle = this.getAttribute("history");
+ // Create join template only if needed
+ if(this.join === undefined || (changedAttributes && changedAttributes.join)) {
+ this.join = this.makeJoinTemplate();
+ }
// Compose the list elements
this.list = this.getTiddlerList();
var members = [],
@@ -102,6 +106,7 @@ ListWidget.prototype.findExplicitTemplates = function() {
var self = this;
this.explicitListTemplate = null;
this.explicitEmptyTemplate = null;
+ this.explicitJoinTemplate = null;
this.hasTemplateInBody = false;
var searchChildren = function(childNodes) {
$tw.utils.each(childNodes,function(node) {
@@ -109,6 +114,8 @@ ListWidget.prototype.findExplicitTemplates = function() {
self.explicitListTemplate = node.children;
} else if(node.type === "list-empty") {
self.explicitEmptyTemplate = node.children;
+ } else if(node.type === "list-join") {
+ self.explicitJoinTemplate = node.children;
} else if(node.type === "element" && node.tag === "p") {
searchChildren(node.children);
} else {
@@ -152,6 +159,24 @@ ListWidget.prototype.getEmptyMessage = function() {
}
};
+/*
+Compose the template for a join between list items
+*/
+ListWidget.prototype.makeJoinTemplate = function() {
+ var parser,
+ join = this.getAttribute("join","");
+ if(join) {
+ parser = this.wiki.parseText("text/vnd.tiddlywiki",join,{parseAsInline:true})
+ if(parser) {
+ return parser.tree;
+ } else {
+ return [];
+ }
+ } else {
+ return this.explicitJoinTemplate; // May be null, and that's fine
+ }
+};
+
/*
Compose the template for a list item
*/
@@ -160,6 +185,7 @@ ListWidget.prototype.makeItemTemplate = function(title,index) {
var tiddler = this.wiki.getTiddler(title),
isDraft = tiddler && tiddler.hasField("draft.of"),
template = this.template,
+ join = this.join,
templateTree;
if(isDraft && this.editTemplate) {
template = this.editTemplate;
@@ -185,12 +211,12 @@ ListWidget.prototype.makeItemTemplate = function(title,index) {
}
}
// Return the list item
- var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree};
+ var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree, join: join};
+ parseTreeNode.isLast = index === this.list.length - 1;
if(this.counterName) {
parseTreeNode.counter = (index + 1).toString();
parseTreeNode.counterName = this.counterName;
parseTreeNode.isFirst = index === 0;
- parseTreeNode.isLast = index === this.list.length - 1;
}
return parseTreeNode;
};
@@ -206,7 +232,7 @@ ListWidget.prototype.refresh = function(changedTiddlers) {
this.storyview.refreshStart(changedTiddlers,changedAttributes);
}
// Completely refresh if any of our attributes have changed
- if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
+ if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.join || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
this.refreshSelf();
result = true;
} else {
@@ -310,10 +336,29 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
}
} else {
// Cycle through the list, inserting and removing list items as needed
+ var mustRecreateLastItem = false;
+ if(this.join && this.join.length) {
+ if(this.children.length !== this.list.length) {
+ mustRecreateLastItem = true;
+ } else if(prevList[prevList.length-1] !== this.list[this.list.length-1]) {
+ mustRecreateLastItem = true;
+ }
+ }
+ var isLast = false, wasLast = false;
for(t=0; t0) {
+ // First re-create previosly-last item that will no longer be last
+ this.removeListItem(t-1);
+ this.insertListItem(t-1,this.list[t-1]);
+ }
this.insertListItem(t,this.list[t]);
hasRefreshed = true;
} else {
@@ -322,9 +367,15 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
this.removeListItem(n);
hasRefreshed = true;
}
- // Refresh the item we're reusing
- var refreshed = this.children[t].refresh(changedTiddlers);
- hasRefreshed = hasRefreshed || refreshed;
+ // Refresh the item we're reusing, or recreate if necessary
+ if(mustRecreateLastItem && (isLast || wasLast)) {
+ this.removeListItem(t);
+ this.insertListItem(t,this.list[t]);
+ hasRefreshed = true;
+ } else {
+ var refreshed = this.children[t].refresh(changedTiddlers);
+ hasRefreshed = hasRefreshed || refreshed;
+ }
}
}
}
@@ -414,8 +465,17 @@ ListItemWidget.prototype.execute = function() {
this.setVariable(this.parseTreeNode.counterName + "-first",this.parseTreeNode.isFirst ? "yes" : "no");
this.setVariable(this.parseTreeNode.counterName + "-last",this.parseTreeNode.isLast ? "yes" : "no");
}
+ // Add join if needed
+ var children = this.parseTreeNode.children,
+ join = this.parseTreeNode.join;
+ if(join && join.length && !this.parseTreeNode.isLast) {
+ children = children.slice(0);
+ $tw.utils.each(join,function(joinNode) {
+ children.push(joinNode);
+ })
+ }
// Construct the child widgets
- this.makeChildWidgets();
+ this.makeChildWidgets(children);
};
/*
@@ -450,4 +510,14 @@ ListEmptyWidget.prototype.refresh = function() { return false; }
exports["list-empty"] = ListEmptyWidget;
+var ListJoinWidget = function(parseTreeNode,options) {
+ // Main initialisation inherited from widget.js
+ this.initialise(parseTreeNode,options);
+};
+ListJoinWidget.prototype = new Widget();
+ListJoinWidget.prototype.render = function() {}
+ListJoinWidget.prototype.refresh = function() { return false; }
+
+exports["list-join"] = ListJoinWidget;
+
})();
diff --git a/core/templates/html-json-skinny-tiddler.tid b/core/templates/html-json-skinny-tiddler.tid
index 1e3c032f3..6f5b7ff35 100644
--- a/core/templates/html-json-skinny-tiddler.tid
+++ b/core/templates/html-json-skinny-tiddler.tid
@@ -1,4 +1,3 @@
title: $:/core/templates/html-json-skinny-tiddler
-<$list filter="[compare:number:gteq[1]] ~[!match[1]]">`,`<$text text=<>/>$list>
<$jsontiddler tiddler=<> exclude="text" escapeUnsafeScriptChars="yes"/>
diff --git a/core/templates/html-json-tiddler.tid b/core/templates/html-json-tiddler.tid
index 6b62b4ac9..2e12290a7 100644
--- a/core/templates/html-json-tiddler.tid
+++ b/core/templates/html-json-tiddler.tid
@@ -1,3 +1,3 @@
title: $:/core/templates/html-json-tiddler
-<$list filter="[!match[1]]">`,`<$text text=<>/>$list><$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes"/>
\ No newline at end of file
+<$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes"/>
\ No newline at end of file
diff --git a/core/templates/store.area.template.html.tid b/core/templates/store.area.template.html.tid
index 84dd0c432..b148a2ff3 100644
--- a/core/templates/store.area.template.html.tid
+++ b/core/templates/store.area.template.html.tid
@@ -6,14 +6,14 @@ title: $:/core/templates/store.area.template.html
<$list filter="[[storeAreaFormat]is[variable]getvariable[]else[json]match[json]]">
``
``
`
`
diff --git a/core/ui/ViewTemplate/subtitle.tid b/core/ui/ViewTemplate/subtitle.tid
index a0436b095..a7c010287 100644
--- a/core/ui/ViewTemplate/subtitle.tid
+++ b/core/ui/ViewTemplate/subtitle.tid
@@ -4,11 +4,8 @@ tags: $:/tags/ViewTemplate
\whitespace trim
<$reveal type="nomatch" stateTitle=<> text="hide" tag="div" retain="yes" animate="yes">
-<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate/Subtitle]!has[draft.of]]" variable="subtitleTiddler" counter="indexSubtitleTiddler">
-<$list filter="[match[no]]" variable="ignore">
-
-$list>
-<$transclude tiddler=<> mode="inline"/>
+<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate/Subtitle]!has[draft.of]]" variable="subtitleTiddler">
+<$transclude tiddler=<> mode="inline"/><$list-join> $list-join>
$list>
$reveal>
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index ced4957f2..adcff9e67 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -41,6 +41,33 @@ description: Under development
Note that the <<.attr "emptyMessage">> and <<.attr "template">> attributes take precedence if they are present.
+!! Joiners for the ListWidget
+
+<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7694">> a <<.attr "join">> attribute to the <<.wid "ListWidget">> widget to insert a short piece of text between list items. This is both easier to use and faster than using the <<.attr "counter">> attribute for the same purpose. So if your list looked like this:
+
+```
+<$list filter=<> counter="counter" variable="item">
+<$text text=<- >/><$list filter="[match[no]]" variable="ignore"><$text text=", "/>$list>
+$list>
+```
+
+You can replace it with:
+
+```
+<$list filter=<> variable="item" join=", ">
+<$text text=<
- >/>
+$list>
+```
+
+If the joiner text that you need is long and awkward to write in an attribute, you can use the new `<$list-join>` widget. Like `<$list-template>` and `<$list-empty>`, it must be an immediate child of the <<.wid "ListWidget">>:
+
+```
+<$list filter=<> variable="item">
+<$text text=<
- >/>
+<$list-join>, and also let's not forget $list-join>
+$list>
+```
+
!! jsonset operator
<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7742">> [[jsonset Operator]] for setting values within JSON objects
diff --git a/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplate.tid b/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplate.tid
new file mode 100644
index 000000000..f1b6f25e9
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplate.tid
@@ -0,0 +1,30 @@
+title: ListWidget/WithJoinTemplate
+description: List widget with join template and $list-empty
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
++
+title: Output
+
+\whitespace trim
+
+\procedure test(filter)
+<$list filter=<>>
+ Item:<>
+
+ <$list-empty>
+ None!
+ $list-empty>
+
+ <$list-join>,$list-join>
+$list>
+\end
+
+<>
+
+<>
+
++
+title: ExpectedResult
+
+
Item:1,Item:2,Item:3
None!
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplateInBlockMode.tid b/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplateInBlockMode.tid
new file mode 100644
index 000000000..c12f4c801
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/list-widget/WithJoinTemplateInBlockMode.tid
@@ -0,0 +1,32 @@
+title: ListWidget/WithJoinTemplateInBlockMode
+description: List widget with join template and $list-empty in block mode
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
++
+title: Output
+
+\whitespace trim
+
+\procedure test(filter)
+<$list filter=<>>
+
+ Item:<>
+
+ <$list-empty>
+ None!
+ $list-empty>
+
+ <$list-join>
$list-join>
+$list>
+\end
+
+<>
+
+<>
+
++
+title: ExpectedResult
+comment: I wish there was a good way to get rid of these extraneous paragraph elements
+
+Item:1
Item:2
Item:3
None!
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js
index 4da9e20b0..0d1351f31 100755
--- a/editions/test/tiddlers/tests/test-widget.js
+++ b/editions/test/tiddlers/tests/test-widget.js
@@ -527,6 +527,45 @@ describe("Widget module", function() {
expect(wrapper.children[0].children[15].sequenceNumber).toBe(53);
});
+ var testListJoin = function(oldList, newList) {
+ return function() {
+ var wiki = new $tw.Wiki();
+ // Add some tiddlers
+ wiki.addTiddler({title: "Numbers", text: "", list: oldList});
+ var text = "<$list filter='[list[Numbers]]' variable='item' join=', '><- >$list>";
+ var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
+ // Render the widget node to the DOM
+ var wrapper = renderWidgetNode(widgetNode);
+ // Test the rendering
+ expect(wrapper.innerHTML).toBe("
" + oldList.split(' ').join(', ') + "
");
+ // Change the list and ensure new rendering is still right
+ wiki.addTiddler({title: "Numbers", text: "", list: newList});
+ refreshWidgetNode(widgetNode,wrapper,["Numbers"]);
+ expect(wrapper.innerHTML).toBe("" + newList.split(' ').join(', ') + "
");
+ }
+ }
+
+ it("the list widget with join should update correctly when empty list gets one item", testListJoin("", "1"));
+ it("the list widget with join should update correctly when empty list gets two items", testListJoin("", "1 2"));
+ it("the list widget with join should update correctly when single-item list is appended to", testListJoin("1", "1 2"));
+ it("the list widget with join should update correctly when single-item list is prepended to", testListJoin("1", "2 1"));
+ it("the list widget with join should update correctly when list is appended", testListJoin("1 2 3 4", "1 2 3 4 5"));
+ it("the list widget with join should update correctly when last item is removed", testListJoin("1 2 3 4", "1 2 3"));
+ it("the list widget with join should update correctly when first item is inserted", testListJoin("1 2 3 4", "0 1 2 3 4"));
+ it("the list widget with join should update correctly when first item is removed", testListJoin("1 2 3 4", "2 3 4"));
+ it("the list widget with join should update correctly when first two items are swapped", testListJoin("1 2 3 4", "2 1 3 4"));
+ it("the list widget with join should update correctly when last two items are swapped", testListJoin("1 2 3 4", "1 2 4 3"));
+ it("the list widget with join should update correctly when last item is moved to the front", testListJoin("1 2 3 4", "4 1 2 3"));
+ it("the list widget with join should update correctly when last item is moved to the middle", testListJoin("1 2 3 4", "1 4 2 3"));
+ it("the list widget with join should update correctly when first item is moved to the back", testListJoin("1 2 3 4", "2 3 4 1"));
+ it("the list widget with join should update correctly when middle item is moved to the back", testListJoin("1 2 3 4", "1 3 4 2"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 1", testListJoin("1 3 4", "1 2 3"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 2", testListJoin("1 3 4", "1 3 2"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 3", testListJoin("1 3 4", "2 1 3"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 4", testListJoin("1 3 4", "2 3 1"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 5", testListJoin("1 3 4", "3 1 2"));
+ it("the list widget with join should update correctly when the last item disappears at the same time as other edits 6", testListJoin("1 3 4", "3 2 1"));
+
var testCounterLast = function(oldList, newList) {
return function() {
var wiki = new $tw.Wiki();
diff --git a/editions/tw5.com/tiddlers/widgets/ListWidget.tid b/editions/tw5.com/tiddlers/widgets/ListWidget.tid
index 592185d36..ce4389261 100644
--- a/editions/tw5.com/tiddlers/widgets/ListWidget.tid
+++ b/editions/tw5.com/tiddlers/widgets/ListWidget.tid
@@ -84,6 +84,7 @@ The action of the list widget depends on the results of the filter combined with
|limit |<<.from-version "5.3.2">> Optional numeric limit for the number of results that are returned. Negative values will return the results from the end of the list |
|template |The title of a template tiddler for transcluding each tiddler in the list. When no template is specified, the body of the ListWidget serves as the item template. With no body, a simple link to the tiddler is returned. |
|editTemplate |An alternative template to use for [[DraftTiddlers|DraftMechanism]] in edit mode |
+|join |<<.from-version "5.3.2">> Text to include between each list item |
|variable |The name for a [[variable|Variables]] in which the title of each listed tiddler is stored. Defaults to ''currentTiddler'' |
|counter |<<.from-version "5.2.0">> Optional name for a [[variable|Variables]] in which the 1-based numeric index of each listed tiddler is stored (see below) |
|emptyMessage |Message to be displayed when the list is empty |
@@ -120,10 +121,29 @@ Displays as:
$list>
<<<
-Note that using the `counter` attribute can reduce performance when working with list items that dynamically reorder or update themselves. The best advice is only to use it when it is really necessary: to obtain a numeric index, or to detect the first or last entries in the list.
+Note that using the `counter` attribute can reduce performance when working with list items that dynamically reorder or update themselves. The best advice is only to use it when it is really necessary: to obtain a numeric index, or to detect the first or last entries in the list. Note that if you are only using it to insert something (like a comma) between list items, the `join` attribute performs much better and you should use it instead of `counter`.
Setting `counter="transclusion"` is a handy way to make child elements for each list element be identified as unique. A common use case are multiple [[tag macros|tag Macro]] for the same tag generated by a list widget. Refer to [[tag macro examples|tag Macro (Examples)]] for more details.
+!! `join` attribute
+
+<<.from-version "5.3.2">> The optional `join` attribute allow you to insert some [[WikiText]] between each list item without needing to use the `counter` attribute, which can become quite slow if the list is updated frequently.
+
+<<.from-version "5.3.2">> If the widget `<$list-join>` is found as an immediate child of the <<.wid "ListWidget">> widget then the content of that widget is used as the "join" template, included between two list items. Note that the <<.attr "join">> attribute takes precedence if it is present.
+
+For example:
+
+
+```
+<$list filter="[tag[About]sort[title]]" join=", " variable="item"><- >$list>
+```
+
+Displays as:
+
+<<<
+<$list filter="[tag[About]sort[title]]" join=", " variable="item"><
- >$list>
+<<<
+
!! Edit mode
The `<$list>` widget can optionally render draft tiddlers through a different template to handle editing, see DraftMechanism.
diff --git a/plugins/tiddlywiki/tiddlyweb/html-json-skinny-tiddler.tid b/plugins/tiddlywiki/tiddlyweb/html-json-skinny-tiddler.tid
index d4f96fde7..b7329c265 100644
--- a/plugins/tiddlywiki/tiddlyweb/html-json-skinny-tiddler.tid
+++ b/plugins/tiddlywiki/tiddlyweb/html-json-skinny-tiddler.tid
@@ -1,4 +1,3 @@
title: $:/core/templates/html-json-skinny-tiddler
-<$list filter="[compare:number:gteq[1]] ~[!match[1]]">`,`<$text text=<>/>$list>
<$jsontiddler tiddler=<> exclude="text" escapeUnsafeScriptChars="yes" $revision=<> $bag="default" $_is_skinny=""/>
diff --git a/plugins/tiddlywiki/tiddlyweb/html-json-tiddler.tid b/plugins/tiddlywiki/tiddlyweb/html-json-tiddler.tid
index bd7a0deec..f357321fb 100644
--- a/plugins/tiddlywiki/tiddlyweb/html-json-tiddler.tid
+++ b/plugins/tiddlywiki/tiddlyweb/html-json-tiddler.tid
@@ -1,4 +1,3 @@
title: $:/core/templates/html-json-tiddler
-<$list filter="[!match[1]]">`,`<$text text=<>/>$list>
-<$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes" $revision=<> $bag="default">/>
+<$jsontiddler tiddler=<> escapeUnsafeScriptChars="yes" $revision=<> $bag="default"/>
From 233b871fdf7406c5a50e1c0f391e4aed1bbb180a Mon Sep 17 00:00:00 2001
From: Saq Imtiaz
Date: Sun, 26 Nov 2023 18:42:53 +0100
Subject: [PATCH 10/31] Update help for CommandsCommand to avoid deduplication
(#7858)
---
core/language/en-GB/Help/commands.tid | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/language/en-GB/Help/commands.tid b/core/language/en-GB/Help/commands.tid
index 454159b44..7551885f0 100644
--- a/core/language/en-GB/Help/commands.tid
+++ b/core/language/en-GB/Help/commands.tid
@@ -10,7 +10,7 @@ Sequentially run the command tokens returned from a filter
Examples
```
---commands "[enlist{$:/build-commands-as-text}]"
+--commands "[enlist:raw{$:/build-commands-as-text}]"
```
```
From 6bd69cc53ff95efc36fdbea615825ef0dcc5de1e Mon Sep 17 00:00:00 2001
From: Maurycy Zarzycki
Date: Mon, 27 Nov 2023 22:53:42 +0100
Subject: [PATCH 11/31] Add changes to PL translation from
233b871fdf7406c5a50e1c0f391e4aed1bbb180a (#7862)
---
languages/pl-PL/Help/commands.tid | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/languages/pl-PL/Help/commands.tid b/languages/pl-PL/Help/commands.tid
index a0bc5932d..1085ac0a5 100644
--- a/languages/pl-PL/Help/commands.tid
+++ b/languages/pl-PL/Help/commands.tid
@@ -10,7 +10,7 @@ Uruchamia komendy zwrócone przez filtr.
Dla przykładu:
```
---commands "[enlist{$:/build-commands-as-text}]"
+--commands "[enlist:raw{$:/build-commands-as-text}]"
```
```
From 8f661423f7a36257ffabc31ffc1b8a0eb95f1ae8 Mon Sep 17 00:00:00 2001
From: Bram Chen
Date: Tue, 28 Nov 2023 06:01:25 +0800
Subject: [PATCH 12/31] Update chinese translations (#7859)
* Update help for CommandsCommand
---
languages/zh-Hans/Help/commands.tid | 2 +-
languages/zh-Hant/Help/commands.tid | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/languages/zh-Hans/Help/commands.tid b/languages/zh-Hans/Help/commands.tid
index 68f86f20c..5a6754ec6 100644
--- a/languages/zh-Hans/Help/commands.tid
+++ b/languages/zh-Hans/Help/commands.tid
@@ -10,7 +10,7 @@ description: 运行从筛选器传回的命令
示例
```
---commands "[enlist{$:/build-commands-as-text}]"
+--commands "[enlist:raw{$:/build-commands-as-text}]"
```
```
diff --git a/languages/zh-Hant/Help/commands.tid b/languages/zh-Hant/Help/commands.tid
index afeaa1e49..eead14103 100644
--- a/languages/zh-Hant/Help/commands.tid
+++ b/languages/zh-Hant/Help/commands.tid
@@ -10,7 +10,7 @@ description: 執行從篩選器傳回的命令
範例
```
---commands "[enlist{$:/build-commands-as-text}]"
+--commands "[enlist:raw{$:/build-commands-as-text}]"
```
```
From a21f45b93a0935b6f84daba8b773771b21b59071 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Tue, 28 Nov 2023 10:53:38 +0000
Subject: [PATCH 13/31] Release note: fix typos
---
editions/prerelease/tiddlers/Release 5.3.2.tid | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index adcff9e67..ce1338ba0 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -106,7 +106,7 @@ Improvements to the following translations:
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7769">> all the relevant core widgets to allow arbitrary `data-*` attributes and `style.*` attributes to be applied to the generated DOM nodes. This is useful for passing data to the EventCatcherWidget
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/7849">> [[jsonextract Operator]], [[jsonget Operator]], [[jsonset Operator]] and [[jsontype Operator]] to allow negative indexes into arrays to be counted from the end of the array
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7690">> the default page layout to better support CSS grid and flexbox layouts
-* <<.link-bage-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7787">> the editor to use grid layout, simplifying customisation
+* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/7787">> the editor to use grid layout, simplifying customisation
! Bug Fixes
@@ -132,7 +132,7 @@ Improvements to the following translations:
! Node.js Improvements
-* <> a significant flaw in the synchronisation algorithm used by the client-server configuration. The flaw could lead to tiddlers temporarily disappearing from the browser
+* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/7843">> a significant flaw in the synchronisation algorithm used by the client-server configuration. The flaw could lead to tiddlers temporarily disappearing from the browser
! Performance Improvements
From 3b84088b27b33d8e2057e52f0cc6810f100c667e Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Tue, 28 Nov 2023 11:44:21 +0000
Subject: [PATCH 14/31] Syncer: Reduce logging intensity
---
core/modules/syncer.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/core/modules/syncer.js b/core/modules/syncer.js
index 58b087b8d..12781ad5a 100644
--- a/core/modules/syncer.js
+++ b/core/modules/syncer.js
@@ -425,7 +425,7 @@ Syncer.prototype.processTaskQueue = function() {
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
// Choose the next task to perform
var task = this.chooseNextTask();
- self.logger.log("Chosen next task " + task);
+ // self.logger.log("Chosen next task " + task);
// Perform the task if we had one
if(typeof task === "object" && task !== null) {
this.numTasksInProgress += 1;
@@ -551,14 +551,14 @@ SaveTiddlerTask.prototype.run = function(callback) {
revision: revision,
timestampLastSaved: new Date()
};
- self.syncer.logger.log("Updating tiddler info in SaveTiddlerTask.run for " + self.title + " " + JSON.stringify(self.syncer.tiddlerInfo[self.title]));
+ // self.syncer.logger.log("Updating tiddler info in SaveTiddlerTask.run for " + self.title + " " + JSON.stringify(self.syncer.tiddlerInfo[self.title]));
// Invoke the callback
callback(null);
},{
tiddlerInfo: self.syncer.tiddlerInfo[self.title]
});
} else {
- this.syncer.logger.log(" Not Dispatching 'save' task:",this.title,"tiddler does not exist");
+ // this.syncer.logger.log(" Not Dispatching 'save' task:",this.title,"tiddler does not exist");
$tw.utils.nextTick(callback(null));
}
};
@@ -582,7 +582,7 @@ DeleteTiddlerTask.prototype.run = function(callback) {
return callback(err);
}
// Remove the info stored about this tiddler
- self.syncer.logger.log("Deleting tiddler info in DeleteTiddlerTask.run for " + self.title);
+ // self.syncer.logger.log("Deleting tiddler info in DeleteTiddlerTask.run for " + self.title);
delete self.syncer.tiddlerInfo[self.title];
// Invoke the callback
callback(null);
@@ -659,7 +659,7 @@ SyncFromServerTask.prototype.run = function(callback) {
} else if(this.syncer.syncadaptor.getSkinnyTiddlers) {
this.syncer.logger.log("Retrieving skinny tiddler list");
this.syncer.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
- self.syncer.logger.log("Retrieved skinny tiddler list");
+ // self.syncer.logger.log("Retrieved skinny tiddler list");
// Check for errors
if(err) {
self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
From 53d493b8766b4bb28fd3c3b1d75d40e330032e5b Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Tue, 28 Nov 2023 11:51:56 +0000
Subject: [PATCH 15/31] Conditional shortcut docs: highlight use of "condition"
variable
---
.../tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid b/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
index 6cdfb1517..3ea99433e 100644
--- a/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
+++ b/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
@@ -6,6 +6,8 @@ type: text/vnd.tiddlywiki
<<.from-version "5.3.2">> The conditional shortcut syntax provides a convenient way to express if-then-else logic within WikiText. It evaluates a filter and considers the condition to be true if there is at least one result (regardless of the value of that result).
+Within an "if" or "elseif" clause, the variable `condition` contains the value of the first result of evaluating the filter condition.
+
A simple example:
<$macrocall $name='wikitext-example-without-html'
@@ -55,7 +57,6 @@ src='\procedure test(animal)
Notes:
* Clauses are parsed in inline mode by default. Force block mode parsing by following the opening `<% if %>`, `<% elseif %>` or `<% else %>` with two line breaks
-* Within an "if" or "elseif" clause, the variable `condition` contains the value of the first result of evaluating the filter condition
* Widgets and HTML elements must be within a single conditional clause; it is not possible to start an element in one conditional clause and end it in another
* The conditional shortcut syntax cannot contain pragmas such as procedure definitions
From c2e61fffe0fd2cb0abbff377cf72c211e02363b5 Mon Sep 17 00:00:00 2001
From: Mario Pietsch
Date: Tue, 28 Nov 2023 14:49:13 +0100
Subject: [PATCH 16/31] German translations (#7864)
---
languages/de-DE/Exporters.multids | 2 +-
languages/de-DE/Help/commands.tid | 4 ++--
languages/de-DE/Help/server.tid | 2 +-
languages/de-DE/Types/image_x-icon.tid | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/languages/de-DE/Exporters.multids b/languages/de-DE/Exporters.multids
index 6103f370d..6663dd17b 100644
--- a/languages/de-DE/Exporters.multids
+++ b/languages/de-DE/Exporters.multids
@@ -3,4 +3,4 @@ title: $:/language/Exporters/
StaticRiver: HTML - Statisch
JsonFile: JSON - Format
CsvFile: CSV - Format
-TidFile: .tid - Format
+TidFile: TID - Text Format
diff --git a/languages/de-DE/Help/commands.tid b/languages/de-DE/Help/commands.tid
index b7de1b86f..55aefa16e 100644
--- a/languages/de-DE/Help/commands.tid
+++ b/languages/de-DE/Help/commands.tid
@@ -1,7 +1,7 @@
title: $:/language/Help/commands
description: Ausführen von Befehlen aus einem Tiddler
-Sequentielle Abarbeitung von Befehlen aus einem Tiddler.
+Sequentielle Abarbeitung von Befehlen aus einem Tiddler.
```
--commands
@@ -9,6 +9,6 @@ Sequentielle Abarbeitung von Befehlen aus einem Tiddler.
Beispiele
-`--commands "[enlist{$:/build-commands-as-text}]"`
+`--commands "[enlist:raw{$:/build-commands-as-text}]"`
`--commands "[{$:/build-commands-as-json}jsonindexes[]] :map[{$:/build-commands-as-json}jsonget]"`
\ No newline at end of file
diff --git a/languages/de-DE/Help/server.tid b/languages/de-DE/Help/server.tid
index 94b40acc6..2bfbbdb47 100644
--- a/languages/de-DE/Help/server.tid
+++ b/languages/de-DE/Help/server.tid
@@ -1,5 +1,5 @@
title: $:/language/Help/server
-description: Stellt einen HTTP server für TiddlyWiki zur Verfügung. (Dieser Befehl ist abgekündigt! - Neu ist: "listen")
+description: (Dieser Befehl ist abgekündigt! - Neu ist: "listen") -- Stellt einen HTTP server für TiddlyWiki zur Verfügung.
TiddlyWiki bringt einen einfachen Web-Server mit.
diff --git a/languages/de-DE/Types/image_x-icon.tid b/languages/de-DE/Types/image_x-icon.tid
index d75ef6fad..043071d97 100644
--- a/languages/de-DE/Types/image_x-icon.tid
+++ b/languages/de-DE/Types/image_x-icon.tid
@@ -1,4 +1,4 @@
title: $:/language/Docs/Types/image/x-icon
-description: ICO - Piktogramm Format
+description: ICO - Piktogramm (Icon)
name: image/x-icon
group: Bilder
From 5dc468f1eae62b5656087cf4106ea60a46690466 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Tue, 28 Nov 2023 17:43:57 +0000
Subject: [PATCH 17/31] Fix release note typo
Apologies again @oflg
---
editions/prerelease/tiddlers/Release 5.3.2.tid | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index ce1338ba0..fcf9a5844 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -165,7 +165,7 @@ joebordes
kookma
linonetwo
mateuszwilczek
-oflig
+oflg
pille1842
pmario
rmunn
From fe17f16675431af8ab84e5847736db845c2c6865 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 09:31:19 +0000
Subject: [PATCH 18/31] Fix syncer not exiting when used on CLI
Fixes #7867
---
core/modules/syncer.js | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/core/modules/syncer.js b/core/modules/syncer.js
index 12781ad5a..66beaf591 100644
--- a/core/modules/syncer.js
+++ b/core/modules/syncer.js
@@ -189,7 +189,7 @@ Syncer.prototype.readTiddlerInfo = function() {
// Record information for known tiddlers
var self = this,
tiddlers = this.getSyncedTiddlers();
- this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers");
+ // this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers");
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.wiki.getTiddler(title);
if(tiddler) {
@@ -302,10 +302,16 @@ Syncer.prototype.getStatus = function(callback) {
Synchronise from the server by reading the skinny tiddler list and queuing up loads for any tiddlers that we don't already have up to date
*/
Syncer.prototype.syncFromServer = function() {
- this.forceSyncFromServer = true;
- this.processTaskQueue();
+ if(this.canSyncFromServer()) {
+ this.forceSyncFromServer = true;
+ this.processTaskQueue();
+ }
};
+Syncer.prototype.canSyncFromServer = function() {
+ return !!this.syncadaptor.getUpdatedTiddlers || !!this.syncadaptor.getSkinnyTiddlers;
+}
+
/*
Force load a tiddler from the server
*/
@@ -447,7 +453,7 @@ Syncer.prototype.processTaskQueue = function() {
// And trigger a timeout if there is a pending task
if(task === true) {
this.triggerTimeout(this.taskTimerInterval);
- } else {
+ } else if(this.canSyncFromServer()) {
this.triggerTimeout(this.pollTimerInterval);
}
}
From fc1e9b6c434b774159dbbe77b17075526bd6d293 Mon Sep 17 00:00:00 2001
From: Mateusz Wilczek <36714554+mateuszwilczek@users.noreply.github.com>
Date: Wed, 29 Nov 2023 11:06:47 +0100
Subject: [PATCH 19/31] Update forum link in update wizard (#7865)
* Update forum link in upgrade wizard
* Update links to forum in es-ES and de-AT editons
---
editions/de-AT/tiddlers/HelloThere.tid | 4 ++--
editions/de-AT/tiddlers/community/Fur_Entwickler.tid | 2 +-
editions/es-ES/tiddlers/Forums.tid | 9 ++++-----
editions/es-ES/tiddlers/HelloThere.tid | 4 ++--
editions/es-ES/tiddlers/Typography.tid | 2 +-
plugins/tiddlywiki/upgrade/UpgradeWizard.tid | 2 +-
6 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/editions/de-AT/tiddlers/HelloThere.tid b/editions/de-AT/tiddlers/HelloThere.tid
index 60c2147f8..aa8bf52e7 100644
--- a/editions/de-AT/tiddlers/HelloThere.tid
+++ b/editions/de-AT/tiddlers/HelloThere.tid
@@ -21,8 +21,8 @@ Willkommen bei ''~TiddlyWiki'', dem einzigartigen [[nicht-linearen|Philosophy vo
Anders, als bei herkömmlichen Online-Diensten, lässt Ihnen ~TiddlyWiki die Freiheit, wo sie ihre Daten speichern. Da ~TiddlyWiki alle Daten als simplen Text speichert, sind Notizen, die Sie heute machen, garantiert in Jahrzehnten noch einfach lesbar.
-
-{{$:/core/images/mail}} ~TiddlyWiki Mailing List
+
+{{$:/core/images/help}} ~TiddlyWiki Forum
{{$:/core/images/twitter}} @~TiddlyWiki on Twitter
diff --git a/editions/de-AT/tiddlers/community/Fur_Entwickler.tid b/editions/de-AT/tiddlers/community/Fur_Entwickler.tid
index 0c656c8dc..2dbc185d7 100644
--- a/editions/de-AT/tiddlers/community/Fur_Entwickler.tid
+++ b/editions/de-AT/tiddlers/community/Fur_Entwickler.tid
@@ -7,5 +7,5 @@ type: text/vnd.tiddlywiki
Es gibt mehrere Ressourcen für Entwickler, um mehr über das TiddlyWiki Projekt zu erfahren, zu diskutieren und vor allem mitzuhelfen.
* [[tiddlywiki.com/dev|https://tiddlywiki.com/dev]] Offizielle Entwickler Doku.
-* [[TiddlyWikiDev group|http://groups.google.com/group/TiddlyWikiDev]] Google Diskussionsforum für Entwickler.
+* [[TiddlyWikiDev group|https://talk.tiddlywiki.org/c/devs/]] Diskussionsforum für Entwickler.
* https://github.com/Jermolene/TiddlyWiki5 .. Github Repository.
diff --git a/editions/es-ES/tiddlers/Forums.tid b/editions/es-ES/tiddlers/Forums.tid
index 63ecc4339..57af7d0f2 100644
--- a/editions/es-ES/tiddlers/Forums.tid
+++ b/editions/es-ES/tiddlers/Forums.tid
@@ -12,7 +12,9 @@ Son listas de correo en las que hablamos de ~TiddlyWiki: pedimos ayuda, anunciam
Puedes participar a través de la página web asociada, o suscribirte via mail.
-!!Usuarios
+!! Usuarios
+
+[[Foro oficial de TiddlyWiki| https://talk.tiddlywiki.org/]]
[[Grupo principal de TiddlyWiki| http://groups.google.com/group/TiddlyWiki]]
@@ -25,10 +27,7 @@ o síguenos [[en Twitter|http://twitter.com/TiddlyWiki]] si quieres recibir las
!! Desarrolladores
-[[Grupo de desarrollo de TiddlyWiki|http://groups.google.com/group/TiddlyWikiDev]]
-
->No necesitas tener cuenta en Google para acceder al grupo. Suscríbete igualmente enviando un mail a:
-*mailto:tiddlywikidev+subscribe@googlegroups.com.
+[[Foro de desarrollo de TiddlyWiki|https://talk.tiddlywiki.org/c/devs]]
Accede a nuestra [[página de desarrollo|https://github.com/Jermolene/TiddlyWiki5]] en GitHub y haz tu contribución.
diff --git a/editions/es-ES/tiddlers/HelloThere.tid b/editions/es-ES/tiddlers/HelloThere.tid
index 0b1cea4bb..c08a27e7d 100644
--- a/editions/es-ES/tiddlers/HelloThere.tid
+++ b/editions/es-ES/tiddlers/HelloThere.tid
@@ -20,8 +20,8 @@ BIenvenido a TiddlyWiki, un bloc de notas [[no lineal|Philosophy of Tiddlers]]
Al revés que los servicios online convencionales, TiddlyWiki te deja escoger dónde quieres guardar tus datos, garantizándote que, por más que pase el tiempo, podrás seguir usando en el futuro las notas que tomes hoy.
-
-{{$:/core/images/mail}} ~TiddlyWiki en Google Groups
+
+{{$:/core/images/mail}} Foro oficial de ~TiddlyWiki
{{$:/core/images/video}} ~TiddlyWiki en ~YouTube
diff --git a/editions/es-ES/tiddlers/Typography.tid b/editions/es-ES/tiddlers/Typography.tid
index 58edf9220..47d3a05ce 100644
--- a/editions/es-ES/tiddlers/Typography.tid
+++ b/editions/es-ES/tiddlers/Typography.tid
@@ -8,7 +8,7 @@ type: text/vnd.tiddlywiki
Se recomienda el uso de las [[macros de documentación|Documentation Macros]] para facilitar las futuras tareas de mantenimiento del texto frente a nuevos cambios y actualizaciones.
-Se recomienda precaución en el uso arbitrario de estilos directos de formato (''negrita'', //cursiva// ...etc). Si se puede usar una macro, conviene usarla. Si no existe la macro adecuada, se puede crear o, si no se sabe cómo, pedir su creación en el [[Grupo de Google|http://groups.google.com/group/TiddlyWiki]].
+Se recomienda precaución en el uso arbitrario de estilos directos de formato (''negrita'', //cursiva// ...etc). Si se puede usar una macro, conviene usarla. Si no existe la macro adecuada, se puede crear o, si no se sabe cómo, pedir su creación en el [[Foro de TiddlyWiki|https://talk.tiddlywiki.org/]].
Por el mismo motivo, se aconseja el uso de acentos graves `...`
para transcribir fragmentos de código y ~WikiText, pero no para nombres de cosas tales como campos, operadores, variables o widgets. Estas tienen su macro correspondiente.
diff --git a/plugins/tiddlywiki/upgrade/UpgradeWizard.tid b/plugins/tiddlywiki/upgrade/UpgradeWizard.tid
index 922441bd6..8e398156a 100644
--- a/plugins/tiddlywiki/upgrade/UpgradeWizard.tid
+++ b/plugins/tiddlywiki/upgrade/UpgradeWizard.tid
@@ -44,7 +44,7 @@ Make sure that you keep a safe copy of your previous ~TiddlyWiki file.
Close this browser window to prevent others from being able to access your data.
-For help and support, visit [[the TiddlyWiki discussion forum|http://groups.google.com/group/TiddlyWiki]].
+For help and support, visit [[the TiddlyWiki discussion forum|https://talk.tiddlywiki.org]].
$reveal>
From f56f5dcc5655563cda7928aea7f65359857024fd Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 11:23:57 +0000
Subject: [PATCH 20/31] Fix savetiddlers handling of tiddlers with no text
field
---
core/modules/commands/savetiddlers.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/modules/commands/savetiddlers.js b/core/modules/commands/savetiddlers.js
index d3b82d726..9c750e204 100644
--- a/core/modules/commands/savetiddlers.js
+++ b/core/modules/commands/savetiddlers.js
@@ -46,7 +46,7 @@ Command.prototype.execute = function() {
type = tiddler.fields.type || "text/vnd.tiddlywiki",
contentTypeInfo = $tw.config.contentTypeInfo[type] || {encoding: "utf8"},
filename = path.resolve(pathname,$tw.utils.encodeURIComponentExtended(title));
- fs.writeFileSync(filename,tiddler.fields.text,contentTypeInfo.encoding);
+ fs.writeFileSync(filename,tiddler.fields.text || "",contentTypeInfo.encoding);
});
return null;
};
From 622512c380e5855fb5bba5d01119c2749e8e2618 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 11:24:54 +0000
Subject: [PATCH 21/31] Further reduce syncer logging
The rationale is that the deeper logs are only useful for debugging the syncer logic, and are overwhelming for most users
---
core/modules/syncer.js | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/core/modules/syncer.js b/core/modules/syncer.js
index 66beaf591..9769d9674 100644
--- a/core/modules/syncer.js
+++ b/core/modules/syncer.js
@@ -189,7 +189,6 @@ Syncer.prototype.readTiddlerInfo = function() {
// Record information for known tiddlers
var self = this,
tiddlers = this.getSyncedTiddlers();
- // this.logger.log("Initialising tiddlerInfo for " + tiddlers.length + " tiddlers");
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.wiki.getTiddler(title);
if(tiddler) {
@@ -236,7 +235,6 @@ Syncer.prototype.isDirty = function() {
return false;
}
var dirtyStatus = checkIsDirty();
- this.logger.log("Dirty status was " + dirtyStatus);
return dirtyStatus;
};
@@ -266,7 +264,6 @@ Syncer.prototype.storeTiddler = function(tiddlerFields) {
adaptorInfo: this.syncadaptor.getTiddlerInfo(tiddler),
changeCount: this.wiki.getChangeCount(tiddlerFields.title)
};
- this.logger.log("Updating tiddler info in syncer.storeTiddler for " + tiddlerFields.title + " " + JSON.stringify(this.tiddlerInfo[tiddlerFields.title]));
};
Syncer.prototype.getStatus = function(callback) {
@@ -431,7 +428,6 @@ Syncer.prototype.processTaskQueue = function() {
if((!this.syncadaptor.isReady || this.syncadaptor.isReady()) && this.numTasksInProgress === 0) {
// Choose the next task to perform
var task = this.chooseNextTask();
- // self.logger.log("Chosen next task " + task);
// Perform the task if we had one
if(typeof task === "object" && task !== null) {
this.numTasksInProgress += 1;
@@ -557,14 +553,12 @@ SaveTiddlerTask.prototype.run = function(callback) {
revision: revision,
timestampLastSaved: new Date()
};
- // self.syncer.logger.log("Updating tiddler info in SaveTiddlerTask.run for " + self.title + " " + JSON.stringify(self.syncer.tiddlerInfo[self.title]));
// Invoke the callback
callback(null);
},{
tiddlerInfo: self.syncer.tiddlerInfo[self.title]
});
} else {
- // this.syncer.logger.log(" Not Dispatching 'save' task:",this.title,"tiddler does not exist");
$tw.utils.nextTick(callback(null));
}
};
@@ -588,7 +582,6 @@ DeleteTiddlerTask.prototype.run = function(callback) {
return callback(err);
}
// Remove the info stored about this tiddler
- // self.syncer.logger.log("Deleting tiddler info in DeleteTiddlerTask.run for " + self.title);
delete self.syncer.tiddlerInfo[self.title];
// Invoke the callback
callback(null);
@@ -642,7 +635,6 @@ SyncFromServerTask.prototype.run = function(callback) {
callback(null);
};
if(this.syncer.syncadaptor.getUpdatedTiddlers) {
- self.syncer.logger.log("Retrieving updated tiddler list");
this.syncer.syncadaptor.getUpdatedTiddlers(self,function(err,updates) {
if(err) {
self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
@@ -663,9 +655,7 @@ SyncFromServerTask.prototype.run = function(callback) {
return successCallback();
});
} else if(this.syncer.syncadaptor.getSkinnyTiddlers) {
- this.syncer.logger.log("Retrieving skinny tiddler list");
this.syncer.syncadaptor.getSkinnyTiddlers(function(err,tiddlers) {
- // self.syncer.logger.log("Retrieved skinny tiddler list");
// Check for errors
if(err) {
self.syncer.displayError($tw.language.getString("Error/RetrievingSkinny"),err);
From c282208668ee89af07b81cf93a21ba68430affc0 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 12:06:40 +0000
Subject: [PATCH 22/31] Fix jsonset crash when applied to primitive types
See https://talk.tiddlywiki.org/t/final-checks-before-release-of-v5-3-2/8560/7
---
core/modules/filters/json-ops.js | 5 ++++-
editions/test/tiddlers/tests/test-json-filters.js | 1 +
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js
index 75a34e94a..0c58964eb 100644
--- a/core/modules/filters/json-ops.js
+++ b/core/modules/filters/json-ops.js
@@ -273,7 +273,10 @@ function setDataItem(data,indexes,value) {
lastIndex = $tw.utils.parseInt(lastIndex);
if(lastIndex < 0) { lastIndex = lastIndex + current.length };
}
- current[lastIndex] = value;
+ // Only set indexes on objects and arrays
+ if(typeof current === "object") {
+ current[lastIndex] = value;
+ }
return data;
}
diff --git a/editions/test/tiddlers/tests/test-json-filters.js b/editions/test/tiddlers/tests/test-json-filters.js
index a8903970a..bfb8a4504 100644
--- a/editions/test/tiddlers/tests/test-json-filters.js
+++ b/editions/test/tiddlers/tests/test-json-filters.js
@@ -124,6 +124,7 @@ describe("json filter tests", function() {
});
it("should support the jsonset operator", function() {
+ expect(wiki.filterTiddlers("[jsonset[a],[aa]]")).toEqual(['"First"','"Second"','"Third"']);
expect(wiki.filterTiddlers("[{First}jsonset[]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonset[],[Antelope]]")).toEqual(['"Antelope"']);
expect(wiki.filterTiddlers("[{First}jsonset:number[],[not a number]]")).toEqual(['0']);
From 6b47cbed32005382b274525f76fb8323ed2cfdb4 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 14:36:58 +0000
Subject: [PATCH 23/31] Scrollable: write bound value if title of bound tiddler
changes
Thanks @yaisog
---
core/modules/widgets/scrollable.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js
index 58597461b..7733308a8 100644
--- a/core/modules/widgets/scrollable.js
+++ b/core/modules/widgets/scrollable.js
@@ -183,7 +183,7 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
"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"])) {
+ if(!existingTiddler || (existingTiddler.fields["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
}
});
From c61c34e9df09bab7215a4c0b7a5c04d239341088 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 14:45:34 +0000
Subject: [PATCH 24/31] Debounce scrollable widget scroll handler
---
core/modules/widgets/scrollable.js | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js
index 7733308a8..c2acc563d 100644
--- a/core/modules/widgets/scrollable.js
+++ b/core/modules/widgets/scrollable.js
@@ -176,16 +176,23 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
// After a delay for rendering, scroll to the bound position
setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
// Save scroll position on DOM scroll event
+ var timeout;
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["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
- self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
+ if(timeout) {
+ window.cancelAnimationFrame(timeout);
+ timeout = null;
}
+ timeout = window.requestAnimationFrame(function() {
+ 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["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
+ self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
+ }
+ });
});
}
};
From f7359671aa827c868b896def60fe2e903dd974e9 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Wed, 29 Nov 2023 18:06:54 +0000
Subject: [PATCH 25/31] Defer scrollable widget updating bound tiddler for
100ms
See discussion https://talk.tiddlywiki.org/t/5-3-2pre-scroll-binding/8570/3?u=jeremyruston
---
core/modules/widgets/scrollable.js | 8 +++++---
editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid | 2 +-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js
index c2acc563d..b77d6a12b 100644
--- a/core/modules/widgets/scrollable.js
+++ b/core/modules/widgets/scrollable.js
@@ -12,6 +12,8 @@ Scrollable widget
/*global $tw: false */
"use strict";
+var DEBOUNCE_INTERVAL = 100; // Delay after last scroll event before updating the bound tiddler
+
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var ScrollableWidget = function(parseTreeNode,options) {
@@ -179,10 +181,10 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
var timeout;
this.outerDomNode.addEventListener("scroll",function(event) {
if(timeout) {
- window.cancelAnimationFrame(timeout);
+ clearTimeout(timeout);
timeout = null;
}
- timeout = window.requestAnimationFrame(function() {
+ timeout = setTimeout(function() {
var existingTiddler = self.wiki.getTiddler(self.scrollableBind),
newTiddlerFields = {
title: self.scrollableBind,
@@ -192,7 +194,7 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
if(!existingTiddler || (existingTiddler.fields["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
}
- });
+ },DEBOUNCE_INTERVAL);
});
}
};
diff --git a/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid b/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid
index d31eb6e31..6c52f0025 100644
--- a/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid
+++ b/editions/tw5.com/tiddlers/widgets/ScrollableWidget.tid
@@ -18,7 +18,7 @@ The content of the `<$scrollable>` widget is displayed within a pair of wrapper
|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.
+Binding the scroll position to a tiddler automatically copies the scroll coordinates into the `scroll-left` and `scroll-top` fields after 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."""/>
From e60ddf0b0ab668201997b06c64b94577673622a6 Mon Sep 17 00:00:00 2001
From: yaisog
Date: Thu, 30 Nov 2023 19:26:26 +0100
Subject: [PATCH 26/31] Handle switching the bound tiddler (#7868)
---
core/modules/widgets/scrollable.js | 53 ++++++++++++++++++------------
1 file changed, 32 insertions(+), 21 deletions(-)
diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js
index b77d6a12b..f6cb5e67b 100644
--- a/core/modules/widgets/scrollable.js
+++ b/core/modules/widgets/scrollable.js
@@ -177,28 +177,28 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
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
- var timeout;
- this.outerDomNode.addEventListener("scroll",function(event) {
- if(timeout) {
- clearTimeout(timeout);
- timeout = null;
- }
- timeout = setTimeout(function() {
- 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["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
- self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
- }
- },DEBOUNCE_INTERVAL);
- });
+ // Set up event listener
+ this.currentListener = this.listenerFunction.bind(this);
+ this.outerDomNode.addEventListener("scroll", this.currentListener);
}
};
+ScrollableWidget.prototype.listenerFunction = function(event) {
+ self = this;
+ clearTimeout(this.timeout);
+ this.timeout = setTimeout(function() {
+ 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["title"] !== newTiddlerFields["title"]) || (existingTiddler.fields["scroll-left"] !== newTiddlerFields["scroll-left"] || existingTiddler.fields["scroll-top"] !== newTiddlerFields["scroll-top"])) {
+ self.wiki.addTiddler(new $tw.Tiddler(existingTiddler,newTiddlerFields));
+ }
+ }, DEBOUNCE_INTERVAL);
+}
+
ScrollableWidget.prototype.updateScrollPositionFromBoundTiddler = function() {
// Bail if we're running on the fakedom
if(!this.outerDomNode.scrollTo) {
@@ -243,8 +243,19 @@ ScrollableWidget.prototype.refresh = function(changedTiddlers) {
this.refreshSelf();
return true;
}
- if(changedAttributes.bind || changedTiddlers[this.getAttribute("bind")]) {
- this.updateScrollPositionFromBoundTiddler();
+ // If the bound tiddler has changed, update the eventListener and update scroll position
+ if(changedAttributes["bind"]) {
+ if(this.currentListener) {
+ this.outerDomNode.removeEventListener("scroll", this.currentListener, false);
+ }
+ this.scrollableBind = this.getAttribute("bind");
+ this.currentListener = this.listenerFunction.bind(this);
+ this.outerDomNode.addEventListener("scroll", this.currentListener);
+ setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
+ }
+ // If a new scroll position was written into the tiddler, update scroll position
+ if(changedTiddlers[this.getAttribute("bind")]) {
+ setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
}
return this.refreshChildren(changedTiddlers);
};
From 4e67aafeb784265a8304f6de976089cdd106e9bf Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Sat, 2 Dec 2023 08:58:35 +0000
Subject: [PATCH 27/31] Scrollable hotfix: Avoid setTimeout
See #7869
---
core/modules/widgets/scrollable.js | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/core/modules/widgets/scrollable.js b/core/modules/widgets/scrollable.js
index f6cb5e67b..227c455c3 100644
--- a/core/modules/widgets/scrollable.js
+++ b/core/modules/widgets/scrollable.js
@@ -176,7 +176,7 @@ ScrollableWidget.prototype.render = function(parent,nextSibling) {
// 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);
+ this.updateScrollPositionFromBoundTiddler();
// Set up event listener
this.currentListener = this.listenerFunction.bind(this);
this.outerDomNode.addEventListener("scroll", this.currentListener);
@@ -251,13 +251,14 @@ ScrollableWidget.prototype.refresh = function(changedTiddlers) {
this.scrollableBind = this.getAttribute("bind");
this.currentListener = this.listenerFunction.bind(this);
this.outerDomNode.addEventListener("scroll", this.currentListener);
- setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
}
- // If a new scroll position was written into the tiddler, update scroll position
- if(changedTiddlers[this.getAttribute("bind")]) {
- setTimeout(this.updateScrollPositionFromBoundTiddler.bind(this),50);
+ // Refresh children
+ var result = this.refreshChildren(changedTiddlers);
+ // If the bound tiddler has changed, update scroll position
+ if(changedAttributes["bind"] || changedTiddlers[this.getAttribute("bind")]) {
+ this.updateScrollPositionFromBoundTiddler();
}
- return this.refreshChildren(changedTiddlers);
+ return result;
};
exports.scrollable = ScrollableWidget;
From 155db0f6f8a7bf65cdc2030b6f489915a82c13d1 Mon Sep 17 00:00:00 2001
From: Jeremy Ruston
Date: Mon, 4 Dec 2023 08:13:23 +0000
Subject: [PATCH 28/31] Improve release note
---
editions/prerelease/tiddlers/Release 5.3.2.tid | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index fcf9a5844..e1194c06c 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -12,7 +12,9 @@ description: Under development
!! Conditional Shortcut Syntax
-<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7710">> a new [[shortcut syntax|Conditional Shortcut Syntax]] for concisely expressing if-then-else logic. For example:
+<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7710">> a new [[shortcut syntax|Conditional Shortcut Syntax]] for concisely expressing if-then-else logic. This is the first of a new type of wikitext syntax based on tokens delimited with `<%` and `%>`. We plan to introduce other structures using the same format such as a "case" statement.
+
+These new token-based shortcuts allow a richer structure and expressivity than existing features such as widgets or pragmas. For example:
```
<% if [match[Elephant]] %>
@@ -24,9 +26,15 @@ description: Under development
<% endif %>
```
+Behind the scenes, the conditional shortcut syntax is rendered as the equivalent [[ListWidgets|ListWidget]].
+
!! Explicit Templates for the ListWidget
-<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7784">> support for `<$list-template>` and `<$list-empty>` as immediate children of the <<.wid "ListWidget">> widget to specify the list item template and/or the empty template. For example:
+<<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/7784">> support for `<$list-template>` and `<$list-empty>` as immediate children of the <<.wid "ListWidget">> widget to specify the list item template and/or the empty template.
+
+This new feature is designed to replace a common pattern of using the `emptyMessage` attribute of the ListWidget to render complex wikitext that thus has to be quoted. Working with wikitext within quotes is awkward and error prone. The new structure can be somewhat faster because it allows the empty message to be parsed in advanced of rendering.
+
+For example:
```
<$list filter=<>>
From 2b0675cac5c7e5467d375a71ca112e7f625ebe8b Mon Sep 17 00:00:00 2001
From: Saq Imtiaz
Date: Mon, 4 Dec 2023 09:53:24 +0100
Subject: [PATCH 29/31] Docs: fixes typos in conditonal shortcut syntax docs
(#7872)
* Docs: Conditional Shortcut Syntax corrections
* Update Conditional Shortcut Syntax.tid
Add a link to Filter Expression tiddler
---
.../tiddlers/wikitext/Conditional Shortcut Syntax.tid | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid b/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
index 3ea99433e..8cef3acfb 100644
--- a/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
+++ b/editions/tw5.com/tiddlers/wikitext/Conditional Shortcut Syntax.tid
@@ -4,14 +4,14 @@ tags: WikiText
title: Conditional Shortcut Syntax
type: text/vnd.tiddlywiki
-<<.from-version "5.3.2">> The conditional shortcut syntax provides a convenient way to express if-then-else logic within WikiText. It evaluates a filter and considers the condition to be true if there is at least one result (regardless of the value of that result).
+<<.from-version "5.3.2">> The conditional shortcut syntax provides a convenient way to express if-then-else logic within WikiText. It evaluates a [[filter expression|Filter Expression]] and considers the condition to be true if there is at least one result (regardless of the value of that result).
Within an "if" or "elseif" clause, the variable `condition` contains the value of the first result of evaluating the filter condition.
A simple example:
<$macrocall $name='wikitext-example-without-html'
-src='<% if [{$:/$:/info/url/protocol}match[file:]] %>
+src='<% if [{$:/info/url/protocol}match[file:]] %>
Loaded from a file URI
<% else %>
Not loaded from a file URI
@@ -21,11 +21,11 @@ src='<% if [{$:/$:/info/url/protocol}match[file:]] %>
One or more `<% elseif %>` clauses may be included before the `<% else %>` clause:
<$macrocall $name='wikitext-example-without-html'
-src='<% if [{$:/$:/info/url/protocol}match[file:]] %>
+src='<% if [{$:/info/url/protocol}match[file:]] %>
Loaded from a file URI
-<% elseif [{$:/$:/info/url/protocol}match[https:]] %>
+<% elseif [{$:/info/url/protocol}match[https:]] %>
Loaded from an HTTPS URI
-<% elseif [{$:/$:/info/url/protocol}match[http:]] %>
+<% elseif [{$:/info/url/protocol}match[http:]] %>
Loaded from an HTTP URI
<% else %>
Loaded from an unknown protocol
From 5578fa5f942011861d9f18494600197316d0cdeb Mon Sep 17 00:00:00 2001
From: Mateusz Wilczek <36714554+mateuszwilczek@users.noreply.github.com>
Date: Mon, 4 Dec 2023 16:24:33 +0100
Subject: [PATCH 30/31] Improve `jsonset` operator docs (#7873)
* Update docs of jsonset operator
* Move jsonset examples into a separate tiddler
* Update jsonset operator docs
---
.../tiddlers/filters/examples/jsonset.tid | 59 +++++++++++++
editions/tw5.com/tiddlers/filters/jsonset.tid | 88 +++++--------------
2 files changed, 83 insertions(+), 64 deletions(-)
create mode 100644 editions/tw5.com/tiddlers/filters/examples/jsonset.tid
diff --git a/editions/tw5.com/tiddlers/filters/examples/jsonset.tid b/editions/tw5.com/tiddlers/filters/examples/jsonset.tid
new file mode 100644
index 000000000..8cd1d1d61
--- /dev/null
+++ b/editions/tw5.com/tiddlers/filters/examples/jsonset.tid
@@ -0,0 +1,59 @@
+created: 20231204112944341
+modified: 20231204115056732
+tags: [[Operator Examples]] [[jsonset Operator]]
+title: jsonset Operator (Examples)
+
+<$let object-a="""{
+ "a": "one",
+ "b": "",
+ "c": "three",
+ "d": {
+ "e": "four",
+ "f": [
+ "five",
+ "six",
+ true,
+ false,
+ null
+ ],
+ "g": {
+ "x": "max",
+ "y": "may",
+ "z": "maize"
+ }
+ }
+}
+"""
+object-b="""{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}""">
+
+The examples below assume the following JSON object is contained in the variable `object-a`:
+
+<>
+
+<<.operator-example 1 "[jsonset[d],[Jaguar]]">>
+<<.operator-example 2 "[jsonset[d],[f],[Panther]]">>
+<<.operator-example 3 "[jsonset[d],[f],[-1],[Elephant]]">>
+<<.operator-example 4 "[jsonset[d],[f],[-2],[Elephant]]">>
+<<.operator-example 5 "[jsonset[d],[f],[-4],[Elephant]]">>
+<<.operator-example 6 "[jsonset[Panther]]" "If only a single parameter is specified, it replaces the entire JSON object">>
+<<.operator-example 7 "[jsonset[]]" "If only a single blank parameter is specified, no changes are made to the JSON object">>
+
+
+The examples below assume the following JSON object is contained in the variable `object-b`:
+
+<>
+
+<<.operator-example 8 "[jsonset[]]" "If only a single blank parameter is specified, no changes are made to the JSON object">>
+<<.operator-example 9 "[jsonset[],[Antelope]]" "If the property to be set is blank, the entire JSON object is replaced">>
+<<.operator-example 10 "[jsonset:number[],[not a number]]" "invalid numbers are interpreted as zero">>
+<<.operator-example 11 "[jsonset[id],[Antelope]]" "nonexistent top level properties are added to the object">>
+<<.operator-example 19 "[jsonset[missing],[id],[Antelope]]" "nonexistent nested properties are are ignored">>
+<<.operator-example 12 "[jsonset:notatype[id],[Antelope]]" "invalid type suffix is interpreted as the default string type">>
+<<.operator-example 13 "[jsonset:boolean[id],[false]]">>
+<<.operator-example 14 "[jsonset:boolean[id],[Antelope]]" "invalid boolean value causes no assignment to be made">>
+<<.operator-example 15 "[jsonset:number[id],[42]]">>
+<<.operator-example 16 "[jsonset:null[id]]">>
+<<.operator-example 17 "[jsonset:array[d],[f],[5]]">>
+<<.operator-example 18 "[jsonset:object[d],[f],[5]]">>
+
+<<.operator-example 20 "[] [] :and[jsonset[b],[two]]" "If the input consists of multiple JSON objects with matching properties, the value is set for all of them">>
\ No newline at end of file
diff --git a/editions/tw5.com/tiddlers/filters/jsonset.tid b/editions/tw5.com/tiddlers/filters/jsonset.tid
index 81552c7a1..1cfd076c2 100644
--- a/editions/tw5.com/tiddlers/filters/jsonset.tid
+++ b/editions/tw5.com/tiddlers/filters/jsonset.tid
@@ -1,22 +1,31 @@
+caption: jsonset
created: 20230915121010948
-modified: 20230915121010948
+modified: 20231204115203428
+op-input: a selection of JSON objects
+op-output: the JSON objects with the specified value assigned to the specified property
+op-parameter: one or more indexes of the property to modify, if applicable followed by the value to be assigned
+op-purpose: set the value of a property in JSON objects
+op-suffix: data type of the value to be assigned to the property
tags: [[Filter Operators]] [[JSON Operators]]
title: jsonset Operator
-caption: jsonset
-op-purpose: set the value of a property in JSON strings
-op-input: a selection of JSON strings
-op-parameter: one or more indexes of the property to retrieve and sometimes a value to assign
-op-output: the JSON strings with the specified property assigned
-<<.from-version "5.3.2">> See [[JSON in TiddlyWiki]] for background.
-
-The <<.op jsonset>> operator is used to set a property value in JSON strings. See also the following related operators:
+<<.from-version "5.3.2">> The <<.op jsonset>> operator is used to set a property value in JSON strings. See [[JSON in TiddlyWiki]] for background. See also the following related operators:
* <<.olink jsonget>> to retrieve the values of a property in JSON data
* <<.olink jsontype>> to retrieve the type of a JSON value
* <<.olink jsonindexes>> to retrieve the names of the fields of a JSON object, or the indexes of a JSON array
* <<.olink jsonextract>> to retrieve a JSON value as a string of JSON
+The type of the value to be assigned to the property can be optionally specified with a suffix:
+
+* ''string'': default, the string is specified as the final operand
+* ''boolean'': the boolean value is true if the final operand is the string "true" and false if the final operand is the string "false", any other value for the final string results prevents the property from being assigned
+* ''number'': the numeric value is taken from the final operand, invalid numbers are interpreted as zero
+* ''json'': the JSON string value is taken from the final operand, invalid JSON prevents the property from being assigned
+* ''object'': an empty object is assigned to the property, the final operand is ignored
+* ''array'': an empty array is assigned to the property, the final operand is ignored
+* ''null'': the special value null is assigned to the property, the final operand is ignored
+
Properties within a JSON object are identified by a sequence of indexes. In the following example, the value at `[a]` is `one`, and the value at `[d][f][0]` is `five`.
```
@@ -42,63 +51,14 @@ Properties within a JSON object are identified by a sequence of indexes. In the
}
```
-The following examples assume that this JSON data is contained in a variable called `jsondata`.
+The <<.op jsonset>> operator uses multiple parameters to specify the indexes of the property to set. When used to assign strings (default behaviour if no suffix is specified) the final operand is interpreted as the value to assign.
-The <<.op jsonset>> operator uses multiple operands to specify the indexes of the property to set. When used to assign strings the final operand is interpreted as the value to assign. For example:
+Negative indexes are counted from the end, so -1 means the last item, -2 the next-to-last item, and so on.
-```
-[jsonset[d],[Jaguar]] --> {"a": "one","b": "","c": "three","d": "Jaguar"}
-[jsonset[d],[f],[Panther]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": "Panther","g": {"x": "max","y": "may","z": "maize"}}"}
-```
+Indexes can be dynamically composed from variables and transclusions, e.g. `[jsonset,{!!field},[0],{CurrentResult}]`.
-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:
+In the special case where only a single parameter is defined, the operator replaces the entire input object with the the value of that parameter. If the single parameter is blank, the operation is ignored and no assignment takes place.
-```
-[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"}}"}
-```
+If the input consists of multiple JSON objects with matching properties, the value is set for all of them.
-Indexes can be dynamically composed from variables and transclusions:
-
-```
-[jsonset,{!!field},[0],{CurrentResult}]
-```
-
-The data type of the value to be assigned to the property can be specified with an optional suffix:
-
-|!Suffix |!Description |
-|''string'' |The string is specified as the final operand |
-|''boolean'' |The boolean value is true if the final operand is the string "true" and false if the final operand is the string "false". Any other value for the final string results prevents the property from being assigned |
-|''number'' |The numeric value is taken from the final operand. Invalid numbers are interpreted as zero |
-|''json'' |The JSON string value is taken from the final operand. Invalid JSON prevents the property from being assigned |
-|''object'' |An empty object is assigned to the property. The final operand is not used as a value |
-|''array'' |An empty array is assigned to the property. The final operand is not used as a value |
-|''null'' |The special value null is assigned to the property. The final operand is not used as a value |
-
-For example:
-
-```
-Input string:
-{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
-
-[jsonset[]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
-[jsonset[],[Antelope]] --> "Antelope"
-[jsonset:number[],[not a number]] --> 0
-[jsonset[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}
-[jsonset:notatype[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":"Antelope"}
-[jsonset:boolean[id],[false]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":false}
-[jsonset:boolean[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
-[jsonset:number[id],[42]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":42}
-[jsonset:null[id]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]},"id":null}
-[jsonset:array[d],[f],[5]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,[]]}}
-[jsonset:object[d],[f],[5]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null,{}]}}
-[jsonset[missing],[id],[Antelope]] --> {"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}
-```
-
-A subtlety is that the special case of a single operand sets the value of that operand as the new JSON string, entirely replacing the input object. If that operand is blank, the operation is ignored and no assignment takes place. Thus:
-
-```
-[jsonset[Panther]] --> "Panther"
-[jsonset[]] --> {"a": "one","b": "","c": "three","d": "{"e": "four","f": ["five", "six", true, false, null],"g": {"x": "max","y": "may","z": "maize"}}"}
-```
+<<.operator-examples "jsonset">>
\ No newline at end of file
From 4e06c31022c39423041cfc38aa7de818ee23213f Mon Sep 17 00:00:00 2001
From: Robin Munn
Date: Thu, 7 Dec 2023 15:34:07 +0700
Subject: [PATCH 31/31] Move list-join example onto single line (#7877)
It's a little less readable this way, but avoids the whitespace issue.
---
editions/prerelease/tiddlers/Release 5.3.2.tid | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/editions/prerelease/tiddlers/Release 5.3.2.tid b/editions/prerelease/tiddlers/Release 5.3.2.tid
index e1194c06c..4c7bc8874 100644
--- a/editions/prerelease/tiddlers/Release 5.3.2.tid
+++ b/editions/prerelease/tiddlers/Release 5.3.2.tid
@@ -62,18 +62,13 @@ Note that the <<.attr "emptyMessage">> and <<.attr "template">> attributes take
You can replace it with:
```
-<$list filter=<> variable="item" join=", ">
-<$text text=<- >/>
-$list>
+<$list filter=<> variable="item" join=", "><$text text=<
- >/>$list>
```
If the joiner text that you need is long and awkward to write in an attribute, you can use the new `<$list-join>` widget. Like `<$list-template>` and `<$list-empty>`, it must be an immediate child of the <<.wid "ListWidget">>:
```
-<$list filter=<> variable="item">
-<$text text=<
- >/>
-<$list-join>, and also let's not forget $list-join>
-$list>
+<$list filter=<> variable="item"><$text text=<
- >/><$list-join>, and also let's not forget $list-join>$list>
```
!! jsonset operator