Compare commits

..
Author SHA1 Message Date
Jeremy Ruston c0b3403f87 Remove extraneous github ribbon in the dev edition 2026-07-21 12:11:14 +01:00
Jeremy Ruston 2ececf21c7 Cherry pick docs update from master 2026-07-18 11:36:37 +01:00
9a0bbf831b Signing the CLA (#9923)
* Signing the CLA

* Apply suggestion from @saqimtiaz

* Apply suggestion from @saqimtiaz

* Apply suggestion from @saqimtiaz

---------

Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2026-07-12 19:37:43 +02:00
superuser-doesandGitHub a30854a4c7 [DOCS] Corrected Description Lists tiddler (#9819)
* [DOCS] Corrected Description Lists tiddler

* Correct modified, remove trailing whitespace
2026-07-12 09:48:53 +02:00
Mario PietschandGitHub 856057be81 Add 8972 field names with parens impact note (#9875) 2026-07-12 09:20:31 +02:00
41 changed files with 343 additions and 702 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# Default to the current version number for building the plugin library
if [ -z "$TW5_BUILD_VERSION" ]; then
TW5_BUILD_VERSION=v5.5.0.
TW5_BUILD_VERSION=v5.4.1.
fi
echo "Using TW5_BUILD_VERSION as [$TW5_BUILD_VERSION]"
+5 -4
View File
@@ -9,14 +9,15 @@ Serve tiddlers over http
"use strict";
let fs, path, crypto, zlib, URL;
let fs, url, path, querystring, crypto, zlib;
if($tw.node) {
fs = require("fs"),
url = require("url"),
path = require("path"),
querystring = require("querystring"),
crypto = require("crypto"),
zlib = require("zlib");
URL = require("url").URL;
}
/*
@@ -259,8 +260,8 @@ Server.prototype.requestHandler = function(request,response,options) {
state.wiki = options.wiki || self.wiki;
state.boot = options.boot || self.boot;
state.server = self;
state.urlInfo = new URL(request.url, "http://localhost");
state.queryParameters = Object.fromEntries(state.urlInfo.searchParams);
state.urlInfo = url.parse(request.url);
state.queryParameters = querystring.parse(state.urlInfo.query);
state.pathPrefix = options.pathPrefix || this.get("path-prefix") || "";
// Enable CORS
if(this.corsEnable) {
+1 -2
View File
@@ -255,5 +255,4 @@ ViewTemplateTags/Caption: View Template Tags
ViewTemplateTags/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the tags area of a tiddler.
WikiInformation/Caption: Wiki Information
WikiInformation/Hint: This page summarises high level information about the configuration of this ~TiddlyWiki. It is designed to enable users to quickly share relevant aspects of the configuration of their ~TiddlyWiki with others, for example when seeking help in one of the forums. No private or personal information is included, and nothing is shared without being explicitly copied and pasted elsewhere
WikiInformation/Drag/Caption: Drag this link to copy this tool to another wiki
WikiInformation/Generate/Caption: Click to generate wiki information report
WikiInformation/Drag/Caption: Drag this link to copy this tool to another wiki
+54 -30
View File
@@ -9,56 +9,69 @@ Filter operators for manipulating the current selection list
"use strict";
/*
Fetch titles from the current list
*/
const prepare_results = (source) => {
const results = [];
source((tiddler,title) => results.push(title));
return results;
};
/*
Order a list
*/
exports.order = function(source,operator,options) {
const results = prepare_results(source);
return operator.operand.toLowerCase() === "reverse" ?
results.reverse() :
results;
var results = [];
if(operator.operand.toLowerCase() === "reverse") {
source(function(tiddler,title) {
results.unshift(title);
});
} else {
source(function(tiddler,title) {
results.push(title);
});
}
return results;
};
/*
Reverse list
*/
exports.reverse = function(source,operator,options) {
return prepare_results(source).reverse();
var results = [];
source(function(tiddler,title) {
results.unshift(title);
});
return results;
};
/*
First entry/entries in list
*/
exports.first = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,1);
return prepare_results(source).slice(0,count);
var count = $tw.utils.getInt(operator.operand,1),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(0,count);
};
/*
Last entry/entries in list
*/
exports.last = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,1);
return count === 0 ?
[] :
prepare_results(source).slice(-count);
var count = $tw.utils.getInt(operator.operand,1),
results = [];
if(count === 0) return results;
source(function(tiddler,title) {
results.push(title);
});
return results.slice(-count);
};
/*
All but the first entry/entries of the list
*/
exports.rest = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,1);
return prepare_results(source).slice(count);
var count = $tw.utils.getInt(operator.operand,1),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(count);
};
exports.butfirst = exports.rest;
exports.bf = exports.rest;
@@ -67,9 +80,12 @@ exports.bf = exports.rest;
All but the last entry/entries of the list
*/
exports.butlast = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,1),
results = prepare_results(source),
index = count === 0 ? results.length : -count;
var count = $tw.utils.getInt(operator.operand,1),
results = [];
source(function(tiddler,title) {
results.push(title);
});
var index = count === 0 ? results.length : -count;
return results.slice(0,index);
};
exports.bl = exports.butlast;
@@ -78,14 +94,22 @@ exports.bl = exports.butlast;
The nth member of the list
*/
exports.nth = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,1);
return prepare_results(source).slice(count - 1,count);
var count = $tw.utils.getInt(operator.operand,1),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(count - 1,count);
};
/*
The zero based nth member of the list
*/
exports.zth = function(source,operator,options) {
const count = $tw.utils.getInt(operator.operand,0);
return prepare_results(source).slice(count,count + 1);
};
var count = $tw.utils.getInt(operator.operand,0),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(count,count + 1);
};
+32 -18
View File
@@ -13,23 +13,37 @@ Filter operator for checking if a title starts with a prefix
Export our filter function
*/
exports.prefix = function(source,operator,options) {
const results = [],
suffixes = (operator.suffixes || [])[0] || [],
caseInsensitive = suffixes.indexOf("caseinsensitive") !== -1,
negate = operator.prefix === "!";
const operand = caseInsensitive ?
operator.operand.toLowerCase() :
operator.operand;
source((tiddler,title) => {
const value = caseInsensitive ? title.toLowerCase() : title;
const matches = value.startsWith(operand);
if(negate ? !matches : matches) {
results.push(title);
var results = [],
suffixes = (operator.suffixes || [])[0] || [];
if(suffixes.indexOf("caseinsensitive") !== -1) {
var operand = operator.operand.toLowerCase();
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(title.toLowerCase().substr(0,operand.length) !== operand) {
results.push(title);
}
});
} else {
source(function(tiddler,title) {
if(title.toLowerCase().substr(0,operand.length) === operand) {
results.push(title);
}
});
}
});
} else {
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(title.substr(0,operator.operand.length) !== operator.operand) {
results.push(title);
}
});
} else {
source(function(tiddler,title) {
if(title.substr(0,operator.operand.length) === operator.operand) {
results.push(title);
}
});
}
}
return results;
};
};
+5 -4
View File
@@ -22,11 +22,12 @@ exports.tag = function(source,operator,options) {
});
} else {
// Old semantics:
var tiddlers;
if(operator.prefix === "!") {
// Returns a copy of the input if operator.operand is missing
const excludeTagSet = new Set(options.wiki.getTiddlersWithTag(operator.operand));
tiddlers = options.wiki.getTiddlersWithTag(operator.operand);
source(function(tiddler,title) {
if(!excludeTagSet.has(title)) {
if(tiddlers.indexOf(title) === -1) {
results.push(title);
}
});
@@ -38,9 +39,9 @@ exports.tag = function(source,operator,options) {
return indexedResults;
}
} else {
const includeTagSet = new Set(options.wiki.getTiddlersWithTag(operator.operand));
tiddlers = options.wiki.getTiddlersWithTag(operator.operand);
source(function(tiddler,title) {
if(includeTagSet.has(title)) {
if(tiddlers.indexOf(title) !== -1) {
results.push(title);
}
});
+81 -70
View File
@@ -11,18 +11,20 @@ Extended filter operators to manipulate the current list.
/*
Fetch titles from the current list
*/
const prepare_results = (source) => {
const results = [];
source((tiddler,title) => results.push(title));
*/
var prepare_results = function (source) {
var results = [];
source(function (tiddler, title) {
results.push(title);
});
return results;
};
/*
Moves a number of items from the tail of the current list before the item named in the operand
*/
exports.putbefore = function(source,operator) {
const results = prepare_results(source),
*/
exports.putbefore = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand),
count = $tw.utils.getInt(operator.suffix,1);
return (index === -1) ?
@@ -32,9 +34,9 @@ exports.putbefore = function(source,operator) {
/*
Moves a number of items from the tail of the current list after the item named in the operand
*/
exports.putafter = function(source,operator) {
const results = prepare_results(source),
*/
exports.putafter = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand),
count = $tw.utils.getInt(operator.suffix,1);
return (index === -1) ?
@@ -44,9 +46,9 @@ exports.putafter = function(source,operator) {
/*
Replaces the item named in the operand with a number of items from the tail of the current list
*/
exports.replace = function(source,operator) {
const results = prepare_results(source),
*/
exports.replace = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand),
count = $tw.utils.getInt(operator.suffix,1);
return (index === -1) ?
@@ -56,39 +58,39 @@ exports.replace = function(source,operator) {
/*
Moves a number of items from the tail of the current list to the head of the list
*/
exports.putfirst = function(source,operator) {
const results = prepare_results(source),
*/
exports.putfirst = function (source, operator) {
var results = prepare_results(source),
count = $tw.utils.getInt(operator.suffix,1);
return [...results.slice(-count), ...results.slice(0, -count)];
return results.slice(-count).concat(results.slice(0, -count));
};
/*
Moves a number of items from the head of the current list to the tail of the list
*/
exports.putlast = function(source,operator) {
const results = prepare_results(source),
*/
exports.putlast = function (source, operator) {
var results = prepare_results(source),
count = $tw.utils.getInt(operator.suffix,1);
return [...results.slice(count), ...results.slice(0, count)];
return results.slice(count).concat(results.slice(0, count));
};
/*
Moves the item named in the operand a number of places forward or backward in the list
*/
exports.move = function(source,operator) {
const results = prepare_results(source),
*/
exports.move = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand),
count = $tw.utils.getInt(operator.suffix,1),
marker = results.splice(index, 1),
offset = (index + count) > 0 ? index + count : 0;
offset = (index + count) > 0 ? index + count : 0;
return results.slice(0, offset).concat(marker).concat(results.slice(offset));
};
/*
Returns the items from the current list that are after the item named in the operand
*/
exports.allafter = function(source,operator) {
const results = prepare_results(source),
*/
exports.allafter = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand);
return (index === -1) ? [] :
(operator.suffix) ? results.slice(index) :
@@ -97,9 +99,9 @@ exports.allafter = function(source,operator) {
/*
Returns the items from the current list that are before the item named in the operand
*/
exports.allbefore = function(source,operator) {
const results = prepare_results(source),
*/
exports.allbefore = function (source, operator) {
var results = prepare_results(source),
index = results.indexOf(operator.operand);
return (index === -1) ? [] :
(operator.suffix) ? results.slice(0, index + 1) :
@@ -108,9 +110,9 @@ exports.allbefore = function(source,operator) {
/*
Appends the items listed in the operand array to the tail of the current list
*/
exports.append = function(source,operator) {
const append = $tw.utils.parseStringArray(operator.operand,"true"),
*/
exports.append = function (source, operator) {
var append = $tw.utils.parseStringArray(operator.operand, "true"),
results = prepare_results(source),
count = parseInt(operator.suffix) || append.length;
return (append.length === 0) ? results :
@@ -120,9 +122,9 @@ exports.append = function(source,operator) {
/*
Prepends the items listed in the operand array to the head of the current list
*/
exports.prepend = function(source,operator) {
const prepend = $tw.utils.parseStringArray(operator.operand,"true"),
*/
exports.prepend = function (source, operator) {
var prepend = $tw.utils.parseStringArray(operator.operand, "true"),
results = prepare_results(source),
count = $tw.utils.getInt(operator.suffix,prepend.length);
return (prepend.length === 0) ? results :
@@ -132,19 +134,21 @@ exports.prepend = function(source,operator) {
/*
Returns all items from the current list except the items listed in the operand array
*/
exports.remove = function(source, operator) {
const array = $tw.utils.parseStringArray(operator.operand, "true"),
results = prepare_results(source);
if(array.length === 0) {
return results;
}
const count = parseInt(operator.suffix, 10) || array.length,
targetItems = operator.prefix ? array.slice(-count).reverse() : array.slice(0, count);
for(const item of targetItems) {
const index = results.indexOf(item);
*/
exports.remove = function (source, operator) {
var array = $tw.utils.parseStringArray(operator.operand, "true"),
results = prepare_results(source),
count = parseInt(operator.suffix) || array.length,
p,
len,
index;
len = array.length - 1;
for(p = 0; p < count; ++p) {
if(operator.prefix) {
index = results.indexOf(array[len - p]);
} else {
index = results.indexOf(array[p]);
}
if(index !== -1) {
results.splice(index, 1);
}
@@ -154,32 +158,40 @@ exports.remove = function(source, operator) {
/*
Returns all items from the current list sorted in the order of the items in the operand array
*/
exports.sortby = function(source,operator) {
const results = prepare_results(source);
*/
exports.sortby = function (source, operator) {
var results = prepare_results(source);
if(!results || results.length < 2) {
return results;
}
const lookup = $tw.utils.parseStringArray(operator.operand,"true");
return results.sort((a,b) => lookup.indexOf(a) - lookup.indexOf(b));
var lookup = $tw.utils.parseStringArray(operator.operand, "true");
results.sort(function (a, b) {
return lookup.indexOf(a) - lookup.indexOf(b);
});
return results;
};
/*
Removes all duplicate items from the current list
*/
exports.unique = function(source, operator) {
return Array.from(new Set(prepare_results(source)));
*/
exports.unique = function (source, operator) {
var results = prepare_results(source);
var set = results.reduce(function (a, b) {
if(a.indexOf(b) < 0) {
a.push(b);
}
return a;
}, []);
return set;
};
const cycleValueInArray = function(results,operands,stepSize) {
let resultsIndex,
var cycleValueInArray = function(results,operands,stepSize) {
var resultsIndex,
step = stepSize || 1,
i = 0,
opLength = operands.length,
nextOperandIndex;
const opLength = operands.length;
for(; i < opLength; i++) {
for(i; i < opLength; i++) {
resultsIndex = results.indexOf(operands[i]);
if(resultsIndex !== -1) {
break;
@@ -201,19 +213,18 @@ const cycleValueInArray = function(results,operands,stepSize) {
/*
Toggles an item in the current list.
*/
*/
exports.toggle = function(source,operator) {
return cycleValueInArray(prepare_results(source),operator.operands);
};
exports.cycle = function(source,operator) {
const results = prepare_results(source),
operands = operator.operand.length ? $tw.utils.parseStringArray(operator.operand,"true") : [""];
let step = $tw.utils.getInt(operator.operands[1] || "",1);
var results = prepare_results(source),
operands = (operator.operand.length ? $tw.utils.parseStringArray(operator.operand, "true") : [""]),
step = $tw.utils.getInt(operator.operands[1]||"",1);
if(step < 0) {
operands.reverse();
step = Math.abs(step);
}
return cycleValueInArray(results,operands,step);
};
};
+2 -3
View File
@@ -88,11 +88,10 @@ BackSubIndexer.prototype.update = function(updateDescriptor) {
var newTargets = [],
oldTargets = [],
self = this;
// System tiddlers are never indexed as sources, matching the _init() scan
if(updateDescriptor.old.exists && !this.wiki.isSystemTiddler(updateDescriptor.old.tiddler.fields.title)) {
if(updateDescriptor.old.exists) {
oldTargets = this._getTarget(updateDescriptor.old.tiddler);
}
if(updateDescriptor.new.exists && !this.wiki.isSystemTiddler(updateDescriptor.new.tiddler.fields.title)) {
if(updateDescriptor.new.exists) {
newTargets = this._getTarget(updateDescriptor.new.tiddler);
}
+54 -66
View File
@@ -528,79 +528,67 @@ exports.parseAttribute = function(source,pos) {
pos = token.end;
// Skip whitespace
pos = $tw.utils.skipWhiteSpace(source,pos);
do {
// Look for a string literal
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
if(stringLiteral) {
pos = stringLiteral.end;
node.type = "string";
node.value = stringLiteral.value;
break;
}
// Look for a string literal
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
if(stringLiteral) {
pos = stringLiteral.end;
node.type = "string";
node.value = stringLiteral.value;
} else {
// Look for a filtered value
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
if(filteredValue) {
pos = filteredValue.end;
node.type = "filtered";
node.filter = filteredValue.match[1];
break;
} else {
// Look for an indirect value
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
if(indirectValue) {
pos = indirectValue.end;
node.type = "indirect";
node.textReference = indirectValue.match[1];
} else {
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation) {
pos = macroInvocation.end;
node.type = "macro";
node.value = macroInvocation;
} else {
// Look for an MVV reference value
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
if(mvvReference) {
pos = mvvReference.end;
node.type = "macro";
node.value = mvvReference;
node.isMVV = true;
} else {
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue) {
pos = substitutedValue.end;
node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
} else {
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
} else if(source.charAt(pos) === "<" && source.charAt(pos + 1) === "<" && source.indexOf(">>",pos) !== -1) {
// Value looks like a macro invocation (starts with << with a closing >> ahead) but does not parse as one. Return null so the enclosing tag fails to parse rather than silently binding the attribute to "true" and treating the remainder as further attributes (restores v5.3.8 behaviour)
return null;
} else {
node.type = "string";
node.value = "true";
}
}
}
}
}
}
// Look for an indirect value
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
if(indirectValue) {
pos = indirectValue.end;
node.type = "indirect";
node.textReference = indirectValue.match[1];
break;
}
// Look for a macro invocation value
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
if(macroInvocation) {
pos = macroInvocation.end;
node.type = "macro";
node.value = macroInvocation;
break;
}
// Look for an MVV reference value
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
if(mvvReference) {
pos = mvvReference.end;
node.type = "macro";
node.value = mvvReference;
node.isMVV = true;
break;
}
// Look for a substituted value
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
if(substitutedValue) {
pos = substitutedValue.end;
node.type = "substituted";
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
break;
}
// Look for a unquoted value
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
if(unquotedValue) {
pos = unquotedValue.end;
node.type = "string";
node.value = unquotedValue.match[1];
break;
}
if(source.charAt(pos) === "<" && source.charAt(pos + 1) === "<" && source.indexOf(">>",pos) !== -1) {
// Value looks like a macro invocation (starts with << with a closing >> ahead) but does not parse as one. Return null so the enclosing tag fails to parse rather than silently binding the attribute to "true" and treating the remainder as further attributes (restores v5.3.8 behaviour)
return null;
}
node.type = "string";
node.value = "true";
} while(false);
}
} else {
// If there is no equals sign or colon, then this is an attribute with no value, defaulting to "true"
node.type = "string";
+2 -13
View File
@@ -115,9 +115,7 @@ var TW_Element = function(tag, namespace) {
this.children = [];
this._style = {}; // Internal style object
this.style = new TW_Style(this); // Proxy for style management
// createElementNS with empty-string or null normalises to null (no namespace) per spec.
// https://dom.spec.whatwg.org/#dom-document-createelementns
this.namespaceURI = namespace !== undefined ? (namespace || null) : "http://www.w3.org/1999/xhtml";
this.namespaceURI = namespace || "http://www.w3.org/1999/xhtml";
};
@@ -208,16 +206,7 @@ TW_Element.prototype.addEventListener = function(type,listener,useCapture) {
Object.defineProperty(TW_Element.prototype, "tagName", {
get: function() {
if(!this.tag) {
return "";
}
// HTML elements report uppercase tagName per DOM spec. Other namespaces
// preserve case. Fakedom only models HTML documents.
// https://dom.spec.whatwg.org/#dom-element-tagname
if(this.namespaceURI === "http://www.w3.org/1999/xhtml") {
return this.tag.toUpperCase();
}
return this.tag;
return this.tag || "";
}
});
+1 -1
View File
@@ -22,7 +22,7 @@ Inherit from the base widget class
DiffTextWidget.prototype = new Widget();
DiffTextWidget.prototype.invisibleCharacters = {
"\n": "\n",
"\n": "↩︎\n",
"\r": "⇠",
"\t": "⇥\t"
};
+3 -5
View File
@@ -4,15 +4,13 @@ description: create a new journal tiddler
\whitespace trim
\function get-tags() [<textFieldTags>] [<tagsFieldTags>] +[join[ ]]
<$let journalTitle=<<now format={{$:/config/NewJournal/Title}}>>
textFieldTags={{$:/config/NewJournal/Tags}}
tagsFieldTags={{$:/config/NewJournal/Tags!!tags}}
journalText={{$:/config/NewJournal/Text}}
>
<$let journalTitleTemplate={{$:/config/NewJournal/Title}} textFieldTags={{$:/config/NewJournal/Tags}} tagsFieldTags={{$:/config/NewJournal/Tags!!tags}} journalText={{$:/config/NewJournal/Text}}>
<$wikify name="journalTitle" text="<$transclude $variable='now' format=<<journalTitleTemplate>>/>">
<$reveal type="nomatch" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<get-tags>> text={{{ [<journalTitle>get[]] }}}/>
</$reveal>
<$reveal type="match" state=<<journalTitle>> text="">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=<<get-tags>> text=<<journalText>>/>
</$reveal>
</$wikify>
</$let>
+23 -16
View File
@@ -6,23 +6,30 @@ tags: $:/tags/EditTemplate
\procedure lingo-base() $:/language/EditTemplate/
\procedure tag-body-inner(colour,fallbackTarget,colourA,colourB,icon,tagField:"tags")
<$let foregroundColor=<<contrastcolour target=<<colour>> fallbackTarget=<<fallbackTarget>> colourA=<<colourA>> colourB=<<colourB>> >>
backgroundColor=<<colour>>
>
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
data-tag-title=<<currentTiddler>>
style=`color:$(foregroundColor)$; fill:$(foregroundColor)$; background-color:$(backgroundColor)$;`
>
<$transclude tiddler=<<icon>>/>
<$view field="title" format="text"/>
<$button class="tc-btn-invisible tc-remove-tag-button"
style.fill=<<foregroundColor>>
<$wikify name="foregroundColor"
text="""<$macrocall $name="contrastcolour"
target=<<colour>>
fallbackTarget=<<fallbackTarget>>
colourA=<<colourA>>
colourB=<<colourB>>/>
"""
>
<$let backgroundColor=<<colour>> >
<span class="tc-tag-label tc-tag-list-item tc-small-gap-right"
data-tag-title=<<currentTiddler>>
style=`color:$(foregroundColor)$; fill:$(foregroundColor)$; background-color:$(backgroundColor)$;`
>
<$action-listops $tiddler=<<saveTiddler>> $field=<<tagField>> $subfilter="-[{!!title}]"/>
{{$:/core/images/close-button}}
</$button>
</span>
</$let>
<$transclude tiddler=<<icon>>/>
<$view field="title" format="text"/>
<$button class="tc-btn-invisible tc-remove-tag-button"
style.fill=<<foregroundColor>>
>
<$action-listops $tiddler=<<saveTiddler>> $field=<<tagField>> $subfilter="-[{!!title}]"/>
{{$:/core/images/close-button}}
</$button>
</span>
</$let>
</$wikify>
\end
\procedure tag-body(colour,palette,icon,tagField:"tags")
+4 -2
View File
@@ -16,13 +16,15 @@ title: $:/core/ui/TagPickerTagTemplate
<$set name="backgroundColor"
value={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerColourFilter]!is[draft]get[text]] }}}
>
<$let foregroundColor=<<contrastcolour target=<<backgroundColor>> fallbackTarget=<<fallbackTarget>> colourA=<<colourA>> colourB=<<colourB>> >> >
<$wikify name="foregroundColor"
text="""<$macrocall $name="contrastcolour" target=<<backgroundColor>> fallbackTarget=<<fallbackTarget>> colourA=<<colourA>> colourB=<<colourB>>/>"""
>
<span class="tc-tag-label tc-btn-invisible"
style=<<tag-pill-styles>>
data-tag-title=<<currentTiddler>>
>
{{||$:/core/ui/TiddlerIcon}}<$view field="title" format="text"/>
</span>
</$let>
</$wikify>
</$set>
</$button>
+3 -4
View File
@@ -6,6 +6,7 @@ description: {{$:/language/Buttons/NewJournalHere/Hint}}
\whitespace trim
\procedure journalButton()
<$button tooltip={{$:/language/Buttons/NewJournalHere/Hint}} aria-label={{$:/language/Buttons/NewJournalHere/Caption}} class=<<tv-config-toolbar-class>>>
<$wikify name="journalTitle" text="""<$transclude $variable="now" format=<<journalTitleTemplate>>/>""">
<$action-sendmessage $message="tm-new-tiddler" title=<<journalTitle>> tags=`[[$(currentTiddlerTag)$]] $(journalTags)$`/>
<%if [<tv-config-toolbar-icons>match[yes]] %>
{{$:/core/images/new-journal-button}}
@@ -15,11 +16,9 @@ description: {{$:/language/Buttons/NewJournalHere/Hint}}
<$text text={{$:/language/Buttons/NewJournalHere/Caption}}/>
</span>
<%endif%>
</$wikify>
</$button>
\end
<$let journalTitle=<<now format={{$:/config/NewJournal/Title}}>>
journalTags={{$:/config/NewJournal/Tags}}
currentTiddlerTag=<<currentTiddler>>
>
<$let journalTitleTemplate={{$:/config/NewJournal/Title}} journalTags={{$:/config/NewJournal/Tags}} currentTiddlerTag=<<currentTiddler>>>
<<journalButton>>
</$let>
+1 -5
View File
@@ -17,10 +17,6 @@ This page summarises high level information about the configuration of this ~Tid
Drag this link to copy this tool to another wiki
\end intrinsic-lingo-Drag/Caption
\procedure intrinsic-lingo-Generate/Caption()
Click to generate wiki information report
\end intrinsic-lingo-Generate/Caption
\procedure lingo(title,mode:"inline")
<%if [<title>addprefix<lingo-base>is[shadow]] %>
<$transclude $tiddler={{{ [<title>addprefix<lingo-base>] }}} $mode=<<mode>>/>
@@ -109,7 +105,7 @@ Click to generate wiki information report
<$button>
<<display-wiki-info-modal>>
<<lingo title:"Generate/Caption">>
Click to generate wiki information report
</$button>
<$link to="$:/core/ui/ControlPanel/WikiInformation">
+1 -1
View File
@@ -1,6 +1,6 @@
title: $:/config/OfficialPluginLibrary
tags: $:/tags/PluginLibrary
url: https://tiddlywiki.com/library/v5.5.0/index.html
url: https://tiddlywiki.com/library/v5.4.1/index.html
caption: {{$:/language/OfficialPluginLibrary}}
{{$:/language/OfficialPluginLibrary/Hint}}
@@ -1,6 +0,0 @@
title: $:/editions/dev/github-fork-ribbon
tags: $:/tags/PageTemplate
caption: ~GitHub ribbon
description: ~GitHub ribbon for tw5.com/dev
<div class="github-fork-ribbon-wrapper right" style><div class="github-fork-ribbon" style="background-color:#DF4848;"><a href="https://github.com/TiddlyWiki/TiddlyWiki5" target="_blank" rel="noopener noreferrer">Find me on ~GitHub</a></div></div>
@@ -1,6 +1,6 @@
title: $:/config/LocalPluginLibrary
tags: $:/tags/PluginLibrary
url: http://127.0.0.1:8080/prerelease/library/v5.5.0/index.html
url: http://127.0.0.1:8080/prerelease/library/v5.4.1/index.html
caption: {{$:/language/OfficialPluginLibrary}} (Prerelease Local)
A locally installed version of the official ~TiddlyWiki plugin library at tiddlywiki.com for testing and debugging. //Requires a local web server to share the library//
@@ -1,6 +1,6 @@
title: $:/config/OfficialPluginLibrary
tags: $:/tags/PluginLibrary
url: https://tiddlywiki.com/prerelease/library/v5.5.0/index.html
url: https://tiddlywiki.com/prerelease/library/v5.4.1/index.html
caption: {{$:/language/OfficialPluginLibrary}} (Prerelease)
The prerelease version of the official ~TiddlyWiki plugin library at tiddlywiki.com. Plugins, themes and language packs are maintained by the core team.
@@ -1,94 +0,0 @@
created: 20260714055110474
modified: 20260714055129225
tags:
title: TiddlyWiki Pre-release Size Comparison
\function bytes.to.mib(size) [abs[]divide[1048576]fixed[2]addsuffix[ MiB]]
\function bytes.to.kib(size) [abs[]divide[1024]fixed[2]addsuffix[ KiB]]
\function format.file.size(size) [<size>abs[]compare:number:gteq[1048576]bytes.to.mib<size>] :else[<size>abs[]compare:number:gteq[1024]bytes.to.kib<size>] :else[<size>addsuffix[ bytes]]
\procedure get-wiki-filesize(filepath)
\procedure completion-get-json()
<!-- Success -->
<$list filter="[<status>compare:number:gteq[200]compare:number:lteq[299]]" variable="ignore">
<$action-log msg="completed" size={{{ [<data>jsonget[size]] }}}/>
<$action-setfield $tiddler=`$:/temp/file-size-comparison/$(filepath)$` text={{{ [<data>jsonget[size]] }}} />
</$list>
\end completion-get-json
<$action-sendmessage
$message="tm-http-request"
url=`https://api.github.com/repos/TiddlyWiki/tiddlywiki.com-gh-pages/contents/$(filepath)$?ref=master`
method="GET"
oncompletion=<<completion-get-json>>
var-filepath=<<filepath>>
/>
\end get-wiki-filesize
\procedure get-wiki-filesizes()
<$list filter="empty.html prerelease/empty.html" variable="filepath">
<$action-log />
<<$transclude $variable="get-wiki-filesize" filepath=<<filepath>> >>
</$list>
\end get-wiki-filesizes
<%if [[$:/temp/file-size-comparison/empty.html]is[tiddler]] [[$:/temp/file-size-comparison/prerelease/empty.html]is[tiddler]] :and[count[]match[2]]%>
<$let delta={{{ [{$:/temp/file-size-comparison/empty.html}subtract{$:/temp/file-size-comparison/prerelease/empty.html}] }}}
message={{{ [<delta>sign[]match[-1]then[Size increased]] :else[<delta>sign[]match[1]then[Size decreased]] :else[[Size has not changed]] }}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="158" height="20" role="img" style.zoom="1.5" aria-label="<$text text=<<message>>/>: <$text text={{{ [format.file.size<delta>] }}} /> bytes">
<title><$text text=<<message>>/>: <$text text={{{ [format.file.size<delta>] }}} /></title>
<filter id="blur">
<feGaussianBlur stdDeviation="16"/>
</filter>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="158" height="20" rx="3"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="93" height="20" fill="#555"/>
<rect x="93" width="65" height="20" fill="#67ac09"/>
<rect width="158" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<g transform="scale(.1)">
<g aria-hidden="true" fill="#010101">
<text x="475" y="150" fill-opacity=".8" filter="url(#blur)" textLength="830"><$text text=<<message>>/></text>
<text x="475" y="150" fill-opacity=".3" textLength="830"><$text text=<<message>>/></text>
</g>
<text x="475" y="140" textLength="830"><$text text=<<message>>/></text>
</g>
<g transform="scale(.1)">
<g aria-hidden="true" fill="#010101">
<text x="1245" y="150" fill-opacity=".8" filter="url(#blur)" textLength="550"><$text text={{{ [format.file.size<delta>] }}} /></text>
<text x="1245" y="150" fill-opacity=".3" textLength="550"><$text text={{{ [format.file.size<delta>] }}} /></text>
</g>
<text x="1245" y="140" textLength="550"><$text text={{{ [format.file.size<delta>] }}} /></text>
</g>
</g>
</svg>
; Pre-release empty.html
: <$text text={{{ [{$:/temp/file-size-comparison/prerelease/empty.html}] }}} /> bytes
; <$text text={{{ [tag[ReleaseNotes]sort[released]last[]] }}} /> empty.html
: <$text text={{{ [{$:/temp/file-size-comparison/empty.html}] }}} /> bytes
---
</$let>
<%else %>
<$button actions=<<get-wiki-filesizes>> class="tc-btn-big-green">
Compare size of empty.html
</$button>
<%endif%>
@@ -13,8 +13,6 @@ The pre-release is also available as an [[empty wiki|https://tiddlywiki.com/prer
</div>
{{ TiddlyWiki Pre-release Size Comparison }}
<$list filter="[tag[ReleaseNotes]!has[released]!sort[created]]">
<div class="tc-titlebar">
<h2 class="tc-title"><$text text=<<currentTiddler>>/></h2>
@@ -1,14 +0,0 @@
title: Filters/ListOps
description: Test listops operators
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
(<$text text={{{ =[[E]] =[[A]] =[[B]] =[[C]] =[[C]] =[[D]] =[[C]] +[unique[]join[]] }}}/>)
+
title: ExpectedResult
<p>(EABCD)</p>
@@ -1,162 +0,0 @@
/*\
title: test-back-indexer.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Regression tests for #9916: the back-indexer must never record system
tiddlers as backlink or backtransclude sources, neither when the index is
first built nor when it is incrementally updated.
\*/
"use strict";
describe("Back-indexer system source tests (#9916)", function() {
function setupWiki() {
// Create a wiki with indexers and one primed backlink pair
var wiki = new $tw.Wiki();
wiki.addIndexersToWiki();
wiki.addTiddler({
title: "TestIncoming",
text: ""});
wiki.addTiddler({
title: "TestOutgoing",
text: "A link to [[TestIncoming]]"});
return wiki;
}
it("should never report a system tiddler as a backlink source", function() {
// Browser console: $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]") to prime the index,
// then $tw.wiki.addTiddler(new $tw.Tiddler({title: "$:/temp/demo", text: "[[HelloThere]]"}))
// and run the filter again; $:/temp/demo must not appear.
var wiki = setupWiki();
// The first lookup builds the lazy index; its initial scan skips system tiddlers
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
// The incremental update() must skip them too
wiki.addTiddler({
title: "$:/temp/system-source",
text: "A link to [[TestIncoming]]"});
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
});
it("should keep backlinks stable while a linking system tiddler is modified and deleted", function() {
// Browser console: $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]") to prime the index,
// then $tw.wiki.addTiddler(new $tw.Tiddler({title: "$:/temp/demo", text: "[[HelloThere]]"})),
// change its text, $tw.wiki.deleteTiddler("$:/temp/demo"); the filter result never changes.
var wiki = setupWiki();
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.addTiddler({
title: "$:/temp/system-source",
text: "A link to [[TestIncoming]]"});
// Modify: both the old and the new side of the index update are system tiddlers
wiki.addTiddler({
title: "$:/temp/system-source",
text: "Links to [[TestIncoming]] and [[TestOutgoing]]"});
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.deleteTiddler("$:/temp/system-source");
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
});
it("should drop the backlink when its source is renamed to a system title", function() {
// Browser console: $tw.wiki.addTiddler(new $tw.Tiddler({title: "Demo", text: "[[HelloThere]]"})),
// confirm Demo is in $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]"), then
// $tw.wiki.renameTiddler("Demo","$:/Demo"); neither Demo nor $:/Demo remains a source.
var wiki = setupWiki();
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.renameTiddler("TestOutgoing","$:/TestOutgoing");
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("");
});
it("should gain the backlink when a linking system tiddler is renamed to a normal title", function() {
// Browser console: $tw.wiki.addTiddler(new $tw.Tiddler({title: "$:/temp/demo", text: "[[HelloThere]]"})),
// then $tw.wiki.renameTiddler("$:/temp/demo","DemoVisible");
// DemoVisible now appears in $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]").
var wiki = setupWiki();
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.addTiddler({
title: "$:/temp/system-source",
text: "A link to [[TestIncoming]]"});
wiki.renameTiddler("$:/temp/system-source","VisibleSource");
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing,VisibleSource");
});
it("should still report normal sources for a system tiddler target", function() {
// Only sources are filtered, targets are not. Browser console:
// $tw.wiki.addTiddler(new $tw.Tiddler({title: "Demo", text: "[[$:/config/NewJournal/Tags]]"}));
// $tw.wiki.filterTiddlers("[[$:/config/NewJournal/Tags]backlinks[]]") contains Demo.
var wiki = setupWiki();
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.addTiddler({
title: "TestSystemLinker",
text: "A link to [[$:/config/Target]]"});
expect(wiki.filterTiddlers("[[$:/config/Target]backlinks[]]").join(",")).toBe("TestSystemLinker");
});
describe("Adversarial probes", function() {
it("should never index a shadow tiddler as a source, even when revealed by deleting its override", function() {
// Goes red when a refactor of BackSubIndexer.update() checks the tiddler instead of
// the exists flag:
// if(updateDescriptor["new"].tiddler) { ... } // broken: goes red here
// if(updateDescriptor["new"].exists) { ... } // correct: stays green
// After deleteTiddler() on an override, boot.js fills new.tiddler via getTiddler(),
// which falls back to the revealed shadow, while new.exists stays false because
// only the real store counts. The broken variant indexes the shadow's links and
// the final expect fails with "TestOutgoing,ShadowSource".
// The first expect also goes red if _init() ever starts scanning shadows; the
// middle expect pins that a real override IS indexed, so this probe cannot be
// satisfied by indexing nothing at all.
// Browser console: override a plugin shadow with text [[HelloThere]], check it appears in
// $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]"), then $tw.wiki.deleteTiddler(title);
// the title disappears from the filter result even though the shadow still renders.
var wiki = setupWiki();
wiki.addTiddler({
title: "$:/plugins/test/shadow-plugin",
type: "application/json",
"plugin-type": "plugin",
text: JSON.stringify({tiddlers: {
"ShadowSource": {title: "ShadowSource", text: "A shadow link to [[TestIncoming]]"}
}})});
wiki.readPluginInfo();
wiki.registerPluginTiddlers("plugin");
wiki.unpackPluginTiddlers();
// The initial scan sees only real tiddlers, not the shadow
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.addTiddler({
title: "ShadowSource",
text: "An overriding link to [[TestIncoming]]"});
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing,ShadowSource");
// Deleting the override reveals the shadow; it must not enter the index
wiki.deleteTiddler("ShadowSource");
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
});
it("should handle hostile titles like __proto__ as source and target", function() {
// Goes red when any title-keyed hashmap on the backlinks path is created like this:
// this.index = {}; // broken: goes red here
// instead of:
// this.index = Object.create(null); // correct: stays green
// this.index = new Map(); // a Map/Set refactor also stays green
// The same applies to the boot tiddler store and to the per-target source maps
// (self.index[target] = ...). On a plain {} the assignment index["__proto__"] = x
// stores no key; it silently replaces the object's prototype. Depending on which
// map regresses, the __proto__ tiddler never registers as a source (second expect),
// or its target entry lands in the shared prototype, where it pollutes every other
// lookup and lookup("__proto__") returns garbage (third expect).
// Browser console: $tw.wiki.addTiddler(new $tw.Tiddler({title: "__proto__",
// text: "[[HelloThere]]"})); __proto__ appears in
// $tw.wiki.filterTiddlers("[[HelloThere]backlinks[]]") and
// $tw.wiki.filterTiddlers("[[__proto__]backlinks[]]") lists tiddlers linking to it.
var wiki = setupWiki();
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing");
wiki.addTiddler({
title: "__proto__",
text: "A link to [[TestIncoming]]"});
expect(wiki.filterTiddlers("TestIncoming +[backlinks[]]").join(",")).toBe("TestOutgoing,__proto__");
wiki.addTiddler({
title: "ProtoLinker",
text: "A link to [[__proto__]]"});
expect(wiki.filterTiddlers("[[__proto__]backlinks[]]").join(",")).toBe("ProtoLinker");
});
});
});
@@ -19,56 +19,6 @@ describe("fakedom tests", function() {
expect($tw.fakeDocument.createTextNode("text").TEXT_NODE).toBe(3);
});
// Per DOM spec, tagName returns the HTML-uppercased qualified name for HTML
// elements. Other namespaces preserve case.
// https://dom.spec.whatwg.org/#dom-element-tagname
var HTML_NS = "http://www.w3.org/1999/xhtml";
var SVG_NS = "http://www.w3.org/2000/svg";
it("tagName uppercases for HTML namespace", function() {
// Default namespace is HTML
expect($tw.fakeDocument.createElement("div").tagName).toBe("DIV");
// The exact predicate the select widget relies on (#9839)
expect($tw.fakeDocument.createElement("optgroup").tagName === "OPTGROUP").toBe(true);
// Already-uppercase input stays uppercase
expect($tw.fakeDocument.createElement("OPTGROUP").tagName).toBe("OPTGROUP");
// Mixed-case input is normalised
expect($tw.fakeDocument.createElement("Div").tagName).toBe("DIV");
// Hyphenated custom-element names uppercase whole tag, hyphens survive
expect($tw.fakeDocument.createElement("my-button").tagName).toBe("MY-BUTTON");
// Empty tag returns empty string
expect($tw.fakeDocument.createElement("").tagName).toBe("");
// Explicit HTML namespace via createElementNS uppercases the same way
expect($tw.fakeDocument.createElementNS(HTML_NS,"Div").tagName).toBe("DIV");
});
it("tagName preserves case for non-HTML namespaces", function() {
// SVG: lowercase preserved
expect($tw.fakeDocument.createElementNS(SVG_NS,"circle").tagName).toBe("circle");
// SVG: camelCase preserved (linearGradient is the canonical example)
expect($tw.fakeDocument.createElementNS(SVG_NS,"linearGradient").tagName).toBe("linearGradient");
// SVG: already-uppercase input is also preserved (NOT lowercased)
expect($tw.fakeDocument.createElementNS(SVG_NS,"DIV").tagName).toBe("DIV");
// Empty namespace string is "no namespace", not HTML. Case preserved.
expect($tw.fakeDocument.createElementNS("","div").tagName).toBe("div");
});
it("tagName reflects current state without mutating it", function() {
// Reading tagName must not overwrite the internal `tag` field
var el = $tw.fakeDocument.createElement("div");
expect(el.tagName).toBe("DIV");
expect(el.tag).toBe("div");
// Idempotent: two reads return identical values
var first = el.tagName, second = el.tagName;
expect(first).toBe(second);
// Dynamic namespace change is reflected. The getter must read current
// state, not a value cached at construction time.
var dynamic = $tw.fakeDocument.createElement("foo");
expect(dynamic.tagName).toBe("FOO");
dynamic.namespaceURI = SVG_NS;
expect(dynamic.tagName).toBe("foo");
});
// Real CSSStyleDeclaration returns undefined for Symbol property keys.
// Without a guard, the TW_Style Proxy throws on Symbol access. This bites
// in practice when Jasmine pretty-prints fakedom elements on failure.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 47 KiB

@@ -4,7 +4,7 @@ created: 20131219100608529
delivery: DIY
description: Flexible hosting on your own machine or in the cloud
method: sync
modified: 20221115230831173
modified: 20260712162730421
tags: Saving [[TiddlyWiki on Node.js]] Windows Mac Linux
title: Installing TiddlyWiki on Node.js
type: text/vnd.tiddlywiki
@@ -39,4 +39,39 @@ The `-g` flag causes TiddlyWiki to be installed globally. Without it, TiddlyWiki
<<.warning "If you are using Debian or Debian-based Linux and you are receiving a `node: command not found` error though node.js package is installed, you may need to create a symbolic link between `nodejs` and `node`. Consult your distro's manual and `whereis` to correctly create a link. See github [[issue 1434|http://github.com/TiddlyWiki/TiddlyWiki5/issues/1434]]. <br><br>Example Debian v8.0: `sudo ln -s /usr/bin/nodejs /usr/bin/node`">>
<br>
<<.tip "You can also install prior versions like this: <br><code> npm install -g tiddlywiki@5.1.13</code>">>
<<.tip "You can also install prior versions like this: <br><code> npm install -g tiddlywiki@5.1.13</code><br>Note: this will overwrite the installed version rather than installing it alongside.">>
!! Installing multiple Node.js verions alongside
There are multiple options:
!!! Local install
```
mkdir -p /home/user/opt/tiddlywiki-5.1.13 && cd "$_" # works in Bash
npm install tiddlywiki@5.1.13
```
This installs the given version in the current directory, at the expense of taking some extra disk space. The binary is in `node_modules/.bin/tiddlywiki` (relative to the current directory). For convenience, it is possible to create a symlink (in Linux) containing explicit version number in the name, like this:
```
ln -s /home/user/opt/tiddlywiki-5.1.13/node_modules/.bin/tiddlywiki /home/user/bin/tiddlywiki-5.1.13
```
Having `~/bin` in `PATH` environment variable (usually at the beginning of it), makes it possible to start this version of TiddlyWiki as simple as `tiddlywiki-5.1.13`.
!!! Use `npx`
To run another version without overwriting the currently installed version, use npx:
```
npx tiddlywiki@5.3.8 --version
```
<<.warning "This comes with a security risk, since it downloads and executes a package from the internet. See https://talk.tiddlywiki.org/t/tiddlywiki-docs-gettingstarted-node-js/15423/4">>
!!! Run directly from the source code repository
# clone https://github.com/TiddlyWiki/TiddlyWiki5/ locally
# checkout any version (using `git checkout` or `git switch` for modern versions of Git)
# set up a link or a shell script for it
@@ -0,0 +1,20 @@
title: $:/changenotes/5.4.0/#8972/impacts/parenthesised-field-names
changenote: $:/changenotes/5.4.0/#8972
created: 20260609193636000
modified: 20260609193636000
tags: $:/tags/ImpactNote
impact-type: compatibility-break
description: A field name containing round brackets can no longer be used as a filter operator suffix, because `(` now starts a multi value variable operand.
Two filter operators take a field name in the suffix position: `regexp` and `search`. Up to 5.3.8 such a field name could contain round brackets, for example:
```
[regexp:_cd-work(s)[(?i)suite]]
[search:_cd-work(s)[suite]]
```
In 5.4.0 the new `(varname)` operand syntax for [[Multi-Valued Variables]] makes `(` start an operand. The parser now splits such a field name at the `(`, so the filter no longer matches.
This will not be fixed. The two readings of `field(x)`, a field named `field(x)` versus the field `field` with the operand `(x)`, are genuinely ambiguous, so `(` and `)` are reserved in filter operator names and suffixes from 5.4.0 onwards.
Workaround: do not use `(` or `)` in field names that are referenced as a filter operator suffix. Rename the field, for example `_cd-works`.
@@ -1,8 +0,0 @@
title: $:/changenotes/5.4.0/#9715
change-type: performance
change-category: filters
tags: $:/tags/ChangeNote
github-contributors: linonetwo
release: 5.4.0
description: Optimized tag[] and !tag[] filter operators to use Set for O(1) lookup, matching search:tags[] performance.
github-links: [[https://github.com/Jermolene/TiddlyWiki5/pull/9715]]
@@ -1,12 +0,0 @@
change-category: widget
change-type: enhancement
created: 20260711020921000
description: The diff-text widget shows removed or added newlines with a visible ↲ glyph
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9736
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9736
type: text/vnd.tiddlywiki
* The diff-text widget marks newline characters in a diff with the visible `↲` glyph (U+21B2) instead of an invisible character, so line break changes are recognisable in the rendered diff (fixes [[Issue #9461|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9461]])
@@ -1,12 +0,0 @@
change-category: translation
change-type: enhancement
created: 20260711020729000
description: The "Click to generate wiki info" button in the control panel is now translatable
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9737
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9737
type: text/vnd.tiddlywiki
* The "Click to generate wiki info" button on the control panel's wiki information tab uses a language string instead of hard coded English text, so translators can localise it (fixes [[Issue #9654|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9654]])
@@ -1,12 +0,0 @@
change-category: nodejs
change-type: bugfix
created: 20260711020057000
description: The Node.js server no longer emits the DEP0169 url.parse() deprecation warning
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9742
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9742
type: text/vnd.tiddlywiki
* The server module parses request URLs with the WHATWG `URL` API instead of the deprecated `url.parse()`, so recent Node.js versions no longer print the DEP0169 deprecation warning on startup (fixes [[Issue #9628|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9628]])
@@ -1,10 +0,0 @@
title: $:/changenotes/5.5.0/#9816
description: Replaces some wikify widget with call dynamic syntax
release: 5.5.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9816
github-contributors: Leilei332
Replaces unnecessary wikify widget usage with call dynamic attribute syntax.
@@ -1,13 +0,0 @@
change-category: internal
change-type: bugfix
created: 20260711014241000
description: fakedom tagName is uppercase for HTML elements, matching the DOM spec
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9843
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9843
type: text/vnd.tiddlywiki
* `$tw.fakeDocument` elements now report `tagName` in uppercase for HTML elements, matching the DOM specification and real browsers; code that compares fakedom `tagName` against lowercase strings must be updated
* Creating an element with an empty or null namespace now produces a plain element without a namespace
@@ -1,12 +0,0 @@
change-category: filters
change-type: bugfix
created: 20260711011920000
description: backlinks[] and backtranscludes[] no longer report edited system tiddlers as sources
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9917
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9917
type: text/vnd.tiddlywiki
* Editing a system tiddler that links or transcludes another tiddler no longer adds it as a [[backlinks|backlinks Operator]] or [[backtranscludes|backtranscludes Operator]] source; the incremental back-index update now skips system tiddlers like the initial scan always did
@@ -1,13 +0,0 @@
change-category: filters
change-type: enhancement
created: 20260726124618748
description: Optimizes listops filter operators
github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9942
modified: 20260726125232992
release: 5.5.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.5.0/#9942
type: text/vnd.tiddlywiki
* The listops filter operators have been rewritten using more modern JavaScript (ES2017). The `unique` and `remove` operators have improved performance in certain circumstances.
@@ -1,13 +0,0 @@
caption: 5.5.0
created: 20260710103826404
modified: 20260710103826404
tags: ReleaseNotes
title: Release 5.5.0
type: text/vnd.tiddlywiki
description: Under development
\procedure release-introduction()
Release v5.5.0 is under development.
\end release-introduction
<<releasenote 5.5.0>>
@@ -7,7 +7,7 @@ type: text/vnd.tiddlywiki
!! Basic syntax
HTML description lists (<abbr title="also known as">AKA</abbr> definition lists) are created with this syntax:
HTML description lists (<abbr title="also known as">AKA</abbr> definition lists) are created with this syntax:
<<wikitext-example src:"; Term being described
: Description / Definition of that term
@@ -17,7 +17,7 @@ HTML description lists (<abbr title="also known as">AKA</abbr> definition lists)
!! Multiple terms and descriptions
You can create multiple descriptions for a term, or multiple terms for a single description:
You can create multiple descriptions for a term, or multiple terms with a single description:
<<wikitext-example src:"; Mouse
: A rodent with a small body and a long tail
@@ -41,4 +41,4 @@ Description lists may also be nested to create lists within lists:
::: A coffee made with espresso and steamed milk
; Tea
: A beverage typically made from tea leaves
">>
">>
+2 -1
View File
@@ -650,4 +650,5 @@ Rishu kumar, @rishu-7549, 2025/10/25
Himmel, @NotHimmel, 2026/03/19
@sean-clayton, 2026/05/16
@vuktw, 2026/07/12
+5 -15
View File
@@ -1,6 +1,6 @@
{
"name": "tiddlywiki",
"version": "5.5.0-prerelease",
"version": "5.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -19,7 +19,7 @@
"globals": "16.4.0"
},
"engines": {
"node": ">=20.0.0"
"node": ">=0.8.2"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -717,20 +717,10 @@
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "tiddlywiki",
"preferGlobal": true,
"version": "5.5.0-prerelease",
"version": "5.4.1",
"author": "Jeremy Ruston <jeremy@jermolene.com>",
"description": "a non-linear personal web notebook",
"contributors": [