Compare commits

...
Author SHA1 Message Date
Mario PietschandGitHub f9d5ee0ad7 Add ButtonWidget examples and execution order (#9945)
Document the button widget with runnable test cases and explain how its
attributes combine.

* Add eleven test case tiddlers, listed by title prefix so a new one needs
  no edit to the widget page
* Describe the order in which a button runs body actions, to, message,
  popup, set and actions, whatever order they are written in
* Prefer class over a hardcoded style
2026-08-16 13:17:33 +02:00
Mario PietschandGitHub 68630c939d Docs only Groupedlists examples (#9954)
* Add GroupedLists examples

Document grouped lists with runnable test cases over a shared
bibliography sample.

* Cover plain grouping, search, tabs by first character, both combined,
  and grouping on one field while searching another
* Name the grouped field in one procedure, so the grouping changes in
  one line
* Import it with import-compound, and list the examples by tag so a new
  one needs no page edit
* Rewrite the opening paragraph, which described only the sidebar tabs

* Replace GroupedLists code dumps with examples

Turn the two sidebar tabs the page describes into runnable test cases,
and tighten how the examples handle an empty result.

* Give each new example its own dummy payload, and point the page at the
  live tabs instead of quoting core source
* Carry the values through the filter from the start, so an All tab with
  no matches yields nothing rather than the literal All
* Use the list-empty widget

* use tc-tiny-gap instead of -left -right
2026-08-16 13:16:04 +02:00
superuser-doesandGitHub 7efbd01801 update 5.4.0 release notes: there was no change to default code wrap (#9962) 2026-08-08 11:46:30 +02:00
Théophile DesmedtandGitHub 5143138c16 [DOCS] Update [[Custom Widgets]] with new example for overriding LinkWidget (#9961)
* [DOCS] Update [[Custom Widgets]] with new example for overriding LinkWidget

* fix: non-svg link icon are now sizing properly

* replace HelloThere by list-link in the example for robustness

* Add class to span for better styling in Custom Widgets

* Fix typo in Custom Widgets.tid documentation

* Improve doc by using the proper macro to link widgets

* Change font size style to important for title icon

Updated the font size style for the title icon to be important.

* Update transclude and slot elements in Custom Widgets

Provide a fallback if no body for the link is given

* Enhance link widget to show icons conditionally

Updated the link widget to conditionally display an icon next to the link based on the target tiddler's icon field. Modified the rendering logic to use a conditional statement for the icon display.

* Change transclude height to size in Custom Widgets
2026-08-07 22:05:06 +02:00
Jeremy Ruston ceb0817176 Update Quine instructions 2026-08-06 10:48:53 +01:00
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
Jeremy Ruston 748f04c9e3 Update TiddlyWiki release checklist 2026-07-10 11:01:22 +01:00
saqimtiaz d391595836 Version number update for 5.4.1 2026-07-10 11:39:38 +02:00
saqimtiaz 967140a148 Merge branch 'master' of github.com:TiddlyWiki/TiddlyWiki5 2026-07-10 11:37:07 +02:00
saqimtiaz 95e68b0437 chore: added missing changenotes 2026-07-10 11:36:59 +02:00
Jeremy Ruston 8ddede1611 Missing v5.4.1 release notes 2026-07-10 10:36:20 +01:00
saqimtiaz ceb200b08c chore: update license and readme 2026-07-10 11:17:53 +02:00
saqimtiaz 00e5f48a59 chore: preparing for v5.4.1 release 2026-07-10 11:12:01 +02:00
saqimtiaz ea0e9105bc Merge branch 'tiddlywiki-com' 2026-07-10 11:00:56 +02:00
Jeremy Ruston 51459815ba Fix CI errors 2026-07-09 16:16:36 +01:00
Jeremy Ruston 8c62935a01 Fix incorrect reference to BurningTreeC's website 2026-07-09 14:14:47 +01:00
Jeremy Ruston 839fa2417d Fix CI issues
1. Remove references to obsolete plugins
2. Fix duplicate language string entry
2026-07-09 14:12:10 +01:00
Saq ImtiazandGitHub 1b8610e4d8 Fixes issue with copying cross window event properties (#9905)
* fix: issue with copying cross window event properties

* docs: added changenote
2026-07-09 12:50:12 +01:00
Mario PietschandGitHub ca063cbe90 Add quick test runner (#9743) 2026-07-08 13:56:16 +01:00
Jeremy Ruston 7d4328b2fb Merge branch 'master' of https://github.com/TiddlyWiki/TiddlyWiki5 2026-07-08 12:02:55 +01:00
Jeremy Ruston 1ae224ab74 Merge branch 'tiddlywiki-com' 2026-07-08 12:02:50 +01:00
Mario PietschandGitHub c308fc44c4 Fix invalid source positions in parse tree nodes (#9882)
* Fix invalid source positions in parse tree nodes

Several wikitext rules recorded start and end offsets that did not match
the source they represent. The offsets are only used for source position
mapping, not rendering or serialization, so output is unchanged and
source.slice(start, end) now equals the node text.

* import: the filter attribute start was set to the whole source string
  instead of the numeric offset. It used this.parser.source where it
  meant this.parser.pos, so every \import node carried a copy of the
  entire source as its start.

* codeinline: the text node end pointed past the closing backtick, so the
  span included the closing delimiter and could read one or two characters
  too far. It now ends where the content ends.

* wikilinkprefix and extlink: a suppressed link such as ~SomeLink or
  ~http://example.com produced a text node whose span included the
  leading ~ while its text did not. The span now starts after the ~.

* Add 5.4.1 change note for #9882

Document the parse tree source position fixes: inline code, suppressed
external links, suppressed wikilinks, and the import filter rule.

* Add tests for parse tree source positions

Cover the four wikitext parser rules whose `start` and `end` offsets are
corrected in this PR: codeinline, extlink, wikilinkprefix, and import.
2026-07-08 10:03:11 +01:00
Jeremy Ruston aa3fb85919 BurningTreeC is leader of the TiddlyDesktop team
@BurningTreeC please check the details of your card,
2026-07-08 09:58:40 +01:00
Jeremy Ruston bd052a33f8 Correct v5.4.1 banner 2026-07-08 09:45:29 +01:00
superuser-doesandGitHub bb766c36c3 [DOCS] Fix survey image link (#9897) 2026-07-01 15:55:49 +02:00
Jeremy Ruston 9c1f69e9c1 Revert "[ja_JP] Update of Japanese translations (#9853)"
Reverting as was inadvertently merged to tiddlywiki-com

This reverts commit 14b11575d0.
2026-06-04 08:28:09 +01:00
IchijikuIchigoandGitHub 14b11575d0 [ja_JP] Update of Japanese translations (#9853)
* [ja_JP] Japanese translation update from commit: 3ed481b

* [ja_JP] Japanese translation update from commit: ad3587b

* [ja_JP] Japanese translation update from commit: 6bc77cf

* [ja_JP] Japanese translation update from commit: 5d1c1ea, c4991a5

* [ja_JP] Japanese translation update from commit: 37a4613

* [ja_JP] Japanese translation update from commit: 748ef8a

* [ja_JP] Japanese translation update from commit: 92caa73

* [ja_JP] Japanese translation update from commit: 87ba87b

* [ja_JP] Japanese translation update from commit: 15ba415

* [ja_JP] Japanese translation update from commit: 64ee20e

* [ja_JP] Japanese translation update from commit: 5d1c1ea,add3d42

* [ja_JP] Japanese translation update from commit: 64ee20e

* [ja_JP] Japanese translation update from commit: b001333

* [ja_JP] Japanese translation update from commit: b434b9d

* [ja_JP] Japanese translation update from commit: 3983086
2026-06-04 08:25:12 +01:00
60 changed files with 1840 additions and 77 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
title: TiddlyWiki Team
tags: Community/Team
modified: 20260708095630754
created: 20260708095630754
leader: @BurningTreeC
team:
The TiddlyDesktop development repository is at https://github.com/TiddlyWiki/TiddlyDesktop
@@ -31,13 +31,16 @@ exports.parse = function() {
reEnd.lastIndex = this.parser.pos;
var match = reEnd.exec(this.parser.source),
text,
start = this.parser.pos;
start = this.parser.pos,
textEnd;
// Process the text
if(match) {
text = this.parser.source.substring(this.parser.pos,match.index);
textEnd = match.index;
this.parser.pos = match.index + match[0].length;
} else {
text = this.parser.source.substr(this.parser.pos);
textEnd = this.parser.sourceLength;
this.parser.pos = this.parser.sourceLength;
}
return [{
@@ -47,7 +50,7 @@ exports.parse = function() {
type: "text",
text: text,
start: start,
end: this.parser.pos
end: textEnd
}]
}];
};
@@ -32,7 +32,8 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Create the link unless it is suppressed
if(this.match[0].substr(0,1) === "~") {
return [{type: "text", text: this.match[0].substr(1), start: start, end: this.parser.pos}];
// Start after the suppressing "~" so the span matches the plain text
return [{type: "text", text: this.match[0].substr(1), start: start + 1, end: this.parser.pos}];
} else {
return [{
type: "element",
@@ -34,7 +34,7 @@ exports.parse = function() {
// Parse the filter terminated by a line break
var reMatch = /(.*)(?:$|\r?\n)/mg;
reMatch.lastIndex = this.parser.pos;
var filterStart = this.parser.source;
var filterStart = this.parser.pos;
var match = reMatch.exec(this.parser.source);
this.parser.pos = reMatch.lastIndex;
// Parse tree nodes to return
@@ -27,9 +27,11 @@ Parse the most recent match
*/
exports.parse = function() {
// Get the details of the match
var linkText = this.match[0];
var linkText = this.match[0],
// Start after the suppressing "~" so the span matches the plain text
start = this.parser.pos + 1;
// Move past the wikilink
this.parser.pos = this.matchRegExp.lastIndex;
// Return the link without unwikilink character as plain text
return [{type: "text", text: linkText.substr(1)}];
return [{type: "text", text: linkText.substr(1), start: start, end: this.parser.pos}];
};
+27 -7
View File
@@ -243,8 +243,28 @@ exports.slowInSlowOut = function(t) {
};
exports.copyObjectPropertiesSafe = function(object) {
const seen = new Set(),
isDOMElement = (value) => value instanceof Node || value instanceof Window;
const seen = new Set();
function isDOMElement(value) {
if(!value || typeof value !== "object") {
return false;
}
// Cross-realm DOM nodes
if(typeof value.nodeType === "number" &&
typeof value.nodeName === "string") {
return true;
}
// Cross-realm Window objects
if(value.window === value &&
value.document &&
value.location) {
return true;
}
return false;
}
function safeCopy(obj) {
// skip circular references
@@ -255,10 +275,6 @@ exports.copyObjectPropertiesSafe = function(object) {
if(typeof obj !== "object" || obj === null) {
return obj;
}
// skip DOM elements
if(isDOMElement(obj)) {
return undefined;
}
// copy arrays, preserving positions
if(Array.isArray(obj)) {
return obj.map((item) => {
@@ -266,7 +282,11 @@ exports.copyObjectPropertiesSafe = function(object) {
return value === undefined ? null : value;
});
}
// skip DOM elements
if(isDOMElement(obj)) {
return undefined;
}
seen.add(obj);
const copy = {};
let key,
@@ -8,9 +8,8 @@ type: text/vnd.tiddlywiki
# Ensure the new release banner image is up to date
# Update ''master'' with changes from ''tiddlywiki-com''
# Verify the version numbers in [[$:/config/OfficialPluginLibrary]] in `core/wiki/config/OfficialPluginLibrary.tid`
# Move the latest release note from the prerelease edition into the tw5.com edition
# Adjust the release date and the ''released'' field of the latest release tiddler (eg, [[Release 5.1.3]])
# Also adjust the github.com comparison link to point to the tag for the new release
# Adjust the release date and the ''released'' field of the release tiddler (eg, [[Release 5.1.3]])
# Update the ''release-introduction'' definition with the new release text, and if necessary the ''description'' field of the release tiddler
# Adjust the tiddler [[TiddlyWiki Archive]] to include the new version number
# Ensure [[TiddlyWiki Releases]] has the new version as the default tab
# Adjust the modified time of HelloThere
@@ -23,7 +22,6 @@ type: text/vnd.tiddlywiki
# Run `./bin/readme-bld.sh` to build the readme files
# Commit the new readme files to ''master''
# Restore `package.json` to the previous version number
# Adjust the link for "GitHub for detailed change history of this release" in the release note
# Add the credits for the new release banner to the release note, including a link to the GitHub instance of the image from the commit history
!! Make New Release
@@ -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
View File
@@ -2,7 +2,6 @@
"description": "Developer documentation from https://tiddlywiki.com/dev/",
"plugins": [
"tiddlywiki/highlight",
"tiddlywiki/nodewebkitsaver",
"tiddlywiki/github-fork-ribbon",
"tiddlywiki/menubar",
"tiddlywiki/internals",
-1
View File
@@ -1,7 +1,6 @@
{
"description": "A step by step introduction to TiddlyWiki",
"plugins": [
"tiddlywiki/cecily",
"tiddlywiki/codemirror",
"tiddlywiki/highlight",
"tiddlywiki/katex"
+71
View File
@@ -0,0 +1,71 @@
/*
Fast test runner that boots the test edition and runs selected test specs.
Skips the expensive --rendertiddler step from --build index.
Usage:
node test-parser-quick.js [test-files...]
Examples:
node test-parser-quick.js # Run ALL specs
node test-parser-quick.js test-wikitext-parser # Run one spec file
node test-parser-quick.js test-wikitext-parser test-filters # Run multiple spec files
*/
"use strict";
var $tw = require("../../boot/boot.js").TiddlyWiki();
$tw.boot.argv = ["editions/test"];
// Disable startup modules that aren't needed for tests
$tw.boot.disabledStartupModules = [
"favicon", "password", "browser-messaging", "info",
"render", "rootwidget", "story", "windows"
];
$tw.boot.boot(function() {
var args = process.argv.slice(2);
var allTests = $tw.wiki.filterTiddlers("[all[tiddlers+shadows]type[application/javascript]tag[$:/tags/test-spec]]");
// Filter test tiddlers if arguments provided
var testsToRun;
if(args.length > 0) {
testsToRun = allTests.filter(function(title) {
return args.some(function(arg) {
return title.toLowerCase().indexOf(arg.toLowerCase()) !== -1;
});
});
if(testsToRun.length === 0) {
console.error("No test files matched: " + args.join(", "));
console.error("Available test files:");
allTests.forEach(function(t) { console.error(" " + t); });
process.exit(1);
}
} else {
testsToRun = allTests;
}
// Override the test filter to only include our selected tests
var titlesSet = Object.create(null);
testsToRun.forEach(function(t) { titlesSet[t] = true; });
var origFilterTiddlers = $tw.wiki.filterTiddlers.bind($tw.wiki);
$tw.wiki.filterTiddlers = function(filterString) {
var result = origFilterTiddlers.apply(null, arguments);
if(filterString.indexOf("$:/tags/test-spec") !== -1) {
return result.filter(function(t) { return titlesSet[t]; });
}
return result;
};
console.log("Running " + testsToRun.length + " of " + allTests.length + " test files");
// Use the jasmine plugin's own runTests function
var jasmine = $tw.modules.execute("$:/plugins/tiddlywiki/jasmine/jasmine-plugin.js");
jasmine.runTests(function(err) {
if(err) {
console.error(err);
process.exit(1);
}
});
});
@@ -0,0 +1,60 @@
/*\
title: test-parsetree-positions.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Regression tests for #9882: wikitext parser rules must emit accurate
`start`/`end` source positions on their parse tree nodes. Tooling that maps
rendered output back to the source text relies on these offsets.
\*/
"use strict";
describe("Parse tree source position tests (#9882)", function() {
// Create a wiki
var wiki = $tw.test.wiki();
// Define a parsing shortcut
var parse = function(text) {
return wiki.parseText("text/vnd.tiddlywiki",text).tree;
};
it("should give inline code runs a text node that spans only the code, not the backticks", function() {
// codeinline.js: `code` gives a text node "code" spanning offsets 1 to 5. The closing backtick at 5 is excluded.
// Bug: `end` was set to `this.parser.pos`, which sits past the closing backtick, so end was 6 and the span swallowed the backtick.
expect(parse("`code`")).toEqual(
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 6, children: [ { type: "element", tag: "code", rule: "codeinline", start: 0, end: 6, children: [ { type: "text", text: "code", start: 1, end: 5 } ] } ] } ]
);
// ``a`b`` gives text "a`b" spanning 2 to 5. `end` must be the offset of the closing marker whatever its length.
// Bug: `end` was `this.parser.pos` (7), two characters past the code, so it also swallowed the closing ``.
expect(parse("``a`b``")).toEqual(
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 7, children: [ { type: "element", tag: "code", rule: "codeinline", start: 0, end: 7, children: [ { type: "text", text: "a`b", start: 2, end: 5 } ] } ] } ]
);
});
it("should start the text node of a suppressed external link after the ~", function() {
// extlink.js: ~https://example.com/ emits the plain text "https://example.com/", which spans offsets 1 to 21.
// Bug: `start` was the offset of the ~ (0), so the span was one character too wide and began on the ~ that the text omits.
expect(parse("~https://example.com/")).toEqual(
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 21, children: [ { type: "text", text: "https://example.com/", start: 1, end: 21, rule: "extlink" } ] } ]
);
});
it("should give a suppressed wikilink's text node source positions", function() {
// wikilinkprefix.js: ~SuppressedLink emits the plain text "SuppressedLink", spanning offsets 1 to 15.
// Bug: the text node carried no `start`/`end` at all. The parser framework then defaulted `start` to the ~ offset (0).
expect(parse("~SuppressedLink")).toEqual(
[ { type: "element", tag: "p", rule: "parseblock", start: 0, end: 15, children: [ { type: "text", text: "SuppressedLink", start: 1, end: 15, rule: "wikilinkprefix" } ] } ]
);
});
it("should record the filter's start offset for an \\import pragma", function() {
// import.js: \import [tag[x]] records the filter value "[tag[x]]" starting at offset 8, right after "\import ".
// Bug: `filterStart` was assigned `this.parser.source` (the whole source string) instead of `this.parser.pos`, so `start` was a string, not an offset.
expect(parse("\\import [tag[x]]\n")).toEqual(
[ { type: "importvariables", rule: "import", start: 0, end: 16, attributes: { filter: { type: "string", value: "[tag[x]]", start: 8, end: 16 } }, children: [] } ]
);
});
});
@@ -0,0 +1,240 @@
/*\
title: test-utils-copyObjectPropertiesSafe.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests $tw.utils.copyObjectPropertiesSafe, the root cause of #9869.
$eventcatcher serialises DOM events via JSON.stringify(copyObjectPropertiesSafe(event)).
The original bug was caused by instanceof Node/Window being realm-specific.
When an event originated from a different browser window, DOM objects from that
window were not detected and JSON.stringify() could throw "Illegal invocation".
The implementation must:
- skip DOM nodes and Window objects from other realms
- preserve normal event data
- preserve CustomEvent.detail payloads
- continue copying enumerable properties from non-DOM objects
- preserve arrays and break circular references
The tests use substitutes for foreign DOM objects because headless Node does not
provide a second browser realm. These objects model the important characteristics:
DOM nodes have nodeType/nodeName, and Window objects have window/self/document.
\*/
"use strict";
describe("copyObjectPropertiesSafe (#9869)", function() {
var cops = $tw.utils.copyObjectPropertiesSafe;
// Simulates a DOM node from another realm.
// The important characteristics are:
// - nodeType/nodeName identify it as a DOM node
// - circular parentNode references resemble real DOM trees
function fakeElement(tagName,extra) {
var node = $tw.utils.extend({
nodeType: 1,
nodeName: tagName,
tagName: tagName
},extra || {});
node.parentNode = node;
return node;
}
// Simulates a Window object from another realm.
function fakeWindow() {
var win = {
document: {},
location: {}
};
win.window = win;
win.self = win;
return win;
}
// An event carrying foreign DOM nodes and a Window.
function fakeEvent(type,target,extra) {
return $tw.utils.extend({
type: type,
target: target,
currentTarget: target,
view: fakeWindow(),
detail: 0,
isTrusted: true
},extra || {});
}
it("does not throw serialising any event type from a secondary window", function() {
var events = [
fakeEvent("focusin",fakeElement("INPUT")),
fakeEvent("change",fakeElement("SELECT")),
fakeEvent("click",fakeElement("BUTTON"),{
button: 0,
clientX: 5,
clientY: 9,
relatedTarget: null
}),
fakeEvent("mouseover",fakeElement("DIV"),{
relatedTarget: fakeElement("SPAN")
})
];
events.forEach(function(event) {
expect(function() {
JSON.stringify(cops(event));
}).not.toThrow();
});
});
it("drops DOM nodes and Window but keeps serialisable event data", function() {
var event = fakeEvent("click",fakeElement("BUTTON"),{
button: 0,
clientX: 5,
clientY: 9
});
var result = JSON.parse(JSON.stringify(cops(event)));
expect(result.target).toBeUndefined();
expect(result.currentTarget).toBeUndefined();
expect(result.view).toBeUndefined();
expect(result.type).toBe("click");
expect(result.detail).toBe(0);
expect(result.isTrusted).toBe(true);
expect(result.button).toBe(0);
expect(result.clientX).toBe(5);
expect(result.clientY).toBe(9);
});
it("preserves nested objects, arrays and circular reference handling", function() {
var event = fakeEvent("custom",fakeElement("DIV"),{
detail: {
nested: {
a: 1,
b: [2,3]
}
},
path: [
fakeElement("DIV"),
fakeWindow(),
42
]
});
event.self = event;
var result = JSON.parse(JSON.stringify(cops(event)));
expect(result.detail).toEqual({
nested: {
a: 1,
b: [2,3]
}
});
expect(result.path).toEqual([
null,
null,
42
]);
expect(result.self).toBeUndefined();
});
it("preserves CustomEvent.detail objects including custom object instances", function() {
function DetailObject() {
this.name = "example";
this.values = [1,2,3];
}
var event = fakeEvent("custom",fakeElement("DIV"),{
detail: new DetailObject()
});
var result = JSON.parse(JSON.stringify(cops(event)));
expect(result.detail).toEqual({
name: "example",
values: [1,2,3]
});
});
it("preserves CustomEvent.detail objects while dropping DOM objects inside them", function() {
var event = fakeEvent("custom",fakeElement("DIV"),{
detail: {
value: 123,
nested: {
target: fakeElement("SPAN"),
kept: "yes"
},
items: [
fakeElement("BUTTON"),
42
]
}
});
var result = JSON.parse(JSON.stringify(cops(event)));
expect(result.detail.value).toBe(123);
expect(result.detail.nested.target).toBeUndefined();
expect(result.detail.nested.kept).toBe("yes");
expect(result.detail.items).toEqual([
null,
42
]);
});
it("accepts primitive, array and object arguments", function() {
var nullProto = Object.create(null);
nullProto.kept = true;
expect(cops(42)).toBe(42);
expect(cops(null)).toBe(null);
expect(cops([
1,
"two",
{three: 3}
])).toEqual([
1,
"two",
{three: 3}
]);
expect(cops({
a: 1,
b: {
c: 2
}
})).toEqual({
a: 1,
b: {
c: 2
}
});
expect(JSON.parse(JSON.stringify(cops({
nullProto: nullProto
})))).toEqual({
nullProto: {
kept: true
}
});
});
});
+3 -2
View File
@@ -1,7 +1,8 @@
created: 20231005205623086
modified: 20250807100434131
modified: 20260710090951727
tags: About
title: TiddlyWiki Archive
type: text/vnd.tiddlywiki
\procedure versions()
5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9
@@ -9,7 +10,7 @@ title: TiddlyWiki Archive
5.1.20 5.1.21 5.1.22 5.1.23
5.2.0 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7
5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.3.8
5.4.0
5.4.0 5.4.1
\end
Older versions of TiddlyWiki are available in the [[archive|https://github.com/TiddlyWiki/tiddlywiki.com-gh-pages/tree/master/archive]]:
@@ -1,9 +1,9 @@
title: Community Survey 2025
created: 20250708130030654
modified: 20250826162904085
title: Community Survey 2025
<div style.float="right" style.padding-left="1em">
<$image source="Community Survey 2025" alt="Shaping the future of TiddlyWiki with the Community Survey 2025" width="280"/>
<$image source="Community Survey 2025 Image" alt="Shaping the future of TiddlyWiki with the Community Survey 2025" width="280"/>
</div>
The core developers work hard year by year to continuously improve ~TiddlyWiki. Part of the satisfaction is that we are not just building software for ourselves, were serving the needs of a wider community of users.
@@ -1,18 +1,22 @@
created: 20150106180000000
modified: 20241204085601176
list: GroupedLists/Example/TypesTab GroupedLists/Example/ByField GroupedLists/Example/WithSearch GroupedLists/Example/WithTabs GroupedLists/Example/TabsWithSearch GroupedLists/Example/SearchAnotherField GroupedLists/Example/RecentTab
modified: 20260729194941115
tags: ListWidget Lists
title: GroupedLists
type: text/vnd.tiddlywiki
The following sidebar tabs give examples of grouped lists created by nesting.
A grouped list is made by nesting one list inside another. The outer list collects each distinct value of a field, and the inner list finds the tiddlers holding that value.
!! [[Types Tab|$:/core/ui/MoreSideBar/Types]]
TiddlyWiki uses the pattern in two of its sidebar tabs. The [[Types Tab|$:/core/ui/MoreSideBar/Types]] groups tiddlers by their `type` field, and sits under ''More'' in the sidebar. The [[Recent Tab|$:/core/ui/SideBar/Recent]] groups them by day, through the <<.mlink timeline>> macro. Both are reproduced as examples below.
For the "Types Tab", the outer list filter as shown below selects each discrete value found in the `type` field. The inner list filter selects all the (non-system) tiddlers with that type.
!! Interactive Examples
<<tw-code "$:/core/ui/MoreSideBar/Types">>
Each example is a runnable test case carrying its own sample data. Where a field is grouped on, it is named once in <<.def groupField>>, so the grouping can be changed without touching anything else.
!! [[Recent Tab|$:/core/ui/SideBar/Recent]]
<<<
<$list filter="[tag[GroupedLists]]">
The list in the "Recent Tab" is generated using the <<.mlink timeline>> macro. Here, the outer list filter selects each discrete day found in the `modified` field, while the inner list filter selects all the tiddlers dated the same day in the `modified` field.
<<tw-code "$:/core/macros/timeline">>
; <$link/>
: {{!!description}}
</$list>
<<<
@@ -0,0 +1,31 @@
title: ButtonWidget/Example/Accessibility
description: Add accessibility and metadata attributes such as aria-label, role, tabindex and data
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
A button can carry accessibility and metadata attributes that pass straight through to the HTML element. They have no visible effect, so this example shows the markup rather than a result:
* `aria-label` gives an accessible name when the label is only an icon
* `role` overrides the implied ARIA role
* `tabindex` sets the keyboard focus order
* `data-*` attaches custom data attributes for scripts
* `selectedAria` chooses which ARIA state `selectedClass` toggles
Inspect the button in your browser to see the attributes on the element.
+
title: Output
<$wikify name="button-accessibility" mode="inline" output="html"
text="""<$button aria-label="Close"
role="button"
tabindex="0"
data-action="close"
>
×
</$button>
""">
<$text text=<<button-accessibility>>/>
</$wikify>
@@ -0,0 +1,33 @@
title: ButtonWidget/Example/Actions
description: Run one or more ActionWidgets when the button is clicked, using the preferred actions attribute
modified: 20260725015330000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `actions` attribute holds one or more ActionWidgets that run when the button is clicked. This is the preferred way to give a button its behaviour, because the whole behaviour sits in one place instead of being spread over several attributes.
A button runs `actions` last, after `to`, `message`, `popup` and `set`. ButtonWidget lists the full order. A single `<$button to="HelloThere">Click Me</$button>` still needs no action string.
The procedure below runs //two// actions in a single click:
* one sets a message
* the other bumps a counter
Click ''Greet'' a few times to watch both update.
+
title: Output
\procedure greetActions()
<$action-setfield $tiddler="$:/state/greeting" text="Hello!"/>
<$action-setfield $tiddler="$:/state/greeting-count"
text={{{ [[$:/state/greeting-count]get[text]] :else[[0]] +[add[1]] }}}
/>
\end
<$button actions=<<greetActions>>>
Greet
</$button>
Message: <$text text={{$:/state/greeting}}/> (clicked <$text text={{{ [[$:/state/greeting-count]get[text]] :else[[0]] }}}/> times)
@@ -0,0 +1,34 @@
title: ButtonWidget/Example/DragAndDrop
description: Make a button draggable with dragTiddler or dragFilter
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
`dragTiddler` makes the button draggable and sets the single tiddler it carries. `dragFilter` carries a list of tiddlers produced by a filter instead.
Choose one of the two:
* `dragTiddler` is a single tiddler title to drag
* `dragFilter` is a filter whose results are dragged as a list
Drag the button onto the drop area to see the title it carries. Drag and drop needs a desktop browser.
+
title: Output
\procedure recordDrop()
<$action-setfield $tiddler="$:/state/dropped" text=<<actionTiddler>>/>
\end
<$button dragTiddler="HelloThere" class="tc-btn-invisible tc-tiddlylink">
Drag me
</$button>
<$droppable actions=<<recordDrop>>>
<div style="border: 1px dashed #999; padding: 0.5em; margin-top: 0.5em;">
Drop the button here
</div>
</$droppable>
Dropped title: <$text text={{$:/state/dropped}}/>
@@ -0,0 +1,54 @@
title: ButtonWidget/Example/DragFilter
description: Drag a list of tiddlers with dragFilter and receive it with listActions
modified: 20260725001110000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
`dragFilter` makes the button draggable and carries every tiddler the filter returns, where `dragTiddler` carries a single title.
The drop target chooses how it receives that payload:
* `actions` runs once per dragged tiddler, with the title in `<<actionTiddler>>`
* `listActions` runs once for the whole payload, with all titles in `<<actionTiddlerList>>`
This test case carries four payload tiddlers tagged `Concepts`. The button drags all four, so the drop area shows the whole list at once. Drag and drop needs a desktop browser.
+
title: Cascades
tags: Concepts
A cascade is a filter list that is evaluated in turn until one returns a result.
+
title: ColourPalettes
tags: Concepts
A colour palette is a tiddler holding the named colours used by the user interface.
+
title: Commands
tags: Concepts
Commands are the operations run by the Node.js server from the command line.
+
title: Filters
tags: Concepts
A filter is an expression that selects a list of tiddler titles from the wiki.
+
title: Output
\procedure recordDropList()
<$action-setfield $tiddler="$:/state/dropped-list" text=<<actionTiddlerList>>/>
\end
<$button dragFilter="[tag[Concepts]]" class="tc-btn-invisible tc-tiddlylink">
Drag four tiddlers
</$button>
<$droppable listActions=<<recordDropList>>>
<div style="border: 1px dashed #999; padding: 0.5em; margin-top: 0.5em;">
Drop the button here
</div>
</$droppable>
Dropped titles: <$text text={{$:/state/dropped-list}}/>
@@ -0,0 +1,30 @@
title: ButtonWidget/Example/Message
description: Send a widget message with the message and param attributes
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `message` attribute sends a widget message when the button is clicked. The `param` attribute passes a single value with it.
The two attributes work together:
* `message` is the message type to send
* `param` is the value delivered with the message
A `messagecatcher` receives the message here and shows the parameter. Click the button to send it.
+
title: Output
\procedure receiveMessage()
<$action-setfield $tiddler="$:/state/received" text=<<event-param>>/>
\end
<$messagecatcher $tm-sample-message=<<receiveMessage>>>
<$button message="tm-sample-message" param="Hello from param">
Send message
</$button>
</$messagecatcher>
Received param: <$text text={{$:/state/received}}/>
@@ -0,0 +1,42 @@
title: ButtonWidget/Example/NewTiddler
description: Create a tiddler from a template with the tm-new-tiddler message
modified: 20260725002340000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
`tm-new-tiddler` is a message the core already handles, so the button needs no catcher of its own.
* `message` is set to `tm-new-tiddler`
* `param` names a template tiddler, and the new tiddler starts out with that template's fields
This test case carries a `TaskTemplate` payload tiddler holding `tags`, `status` and `priority`. Open the ''TaskTemplate'' tab above to see it.
The message creates a draft and normally opens it for editing. A test case has no story river, so the table lists the drafts instead. Each click adds one row, and the new tiddler takes the next free title because the previous draft still exists.
+
title: TaskTemplate
tags: Task
status: open
priority: normal
Describe the task here.
+
title: Output
<$button message="tm-new-tiddler" param="TaskTemplate">
New task
</$button>
<table>
<tr><th>Click</th><th>New tiddler</th><th>tags</th><th>status</th><th>priority</th></tr>
<$list filter="[has[draft.title]sort[draft.title]]" counter="clickNumber">
<tr>
<td><<clickNumber>></td>
<td><$text text={{!!draft.title}}/></td>
<td><$text text={{!!tags}}/></td>
<td><$text text={{!!status}}/></td>
<td><$text text={{!!priority}}/></td>
</tr>
</$list>
</table>
@@ -0,0 +1,38 @@
created: 20260724134149482
description: Toggle a popup with the popup attribute and a companion reveal widget
modified: 20260725003054000
tags: $:/tags/wiki-test-spec
title: ButtonWidget/Example/Popup
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `popup` attribute names a state tiddler that stores the popup coordinates. A companion `reveal` widget reads that state to show or hide the content.
The button uses these attributes:
* `popup` is the state tiddler for the popup
* `selectedClass` highlights the button while the popup is open
* `popupAbsCoords` writes absolute coordinates when set to `yes`, rather than relative ones
Use `popupTitle` in place of `popup` when the state title should not be read as a text reference. Click the button to toggle the popup.
The `reveal` widget always creates a DOM element of its own: a `div` in block mode, a `span` in inline mode. The `tag` attribute sets that element explicitly. Wrapping the popup content in a `<div>` would add a second element, so this example sets `tag="div"` and puts the `tc-drop-down` class on the widget itself. The popup is then one element instead of two.
+
title: Output
<$button popup="$:/state/popup/demo" selectedClass="tc-selected">
Toggle popup
</$button>
<$reveal type="popup"
tag="div"
state="$:/state/popup/demo"
position="belowleft"
animate="yes"
class="tc-drop-down"
>
This is the popup content.
</$reveal>
@@ -0,0 +1,41 @@
title: ButtonWidget/Example/Presentation
description: Change the rendered element and appearance with tag, class, style and disabled
modified: 20260725003716000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
Several attributes control how the button looks and which element it renders:
* `tag` renders a different HTML element in place of `button`
* `class` adds one or more CSS classes
* `style` sets a full CSS style string
* `disabled` set to `yes` disables the button
Reach for `class` first and keep the rules in a stylesheet. One class restyles every button that uses it, follows the colour palette and stays under the user's control. A hardcoded `style` overrides the theme and has to be repeated at every button, so use it only when no class can do the job.
`style` sets the whole style attribute and replaces anything a `style.*` attribute wrote before it, so do not mix the two.
+
title: Output
<$button tag="a" class="tc-tiddlylink">
tag="a" renders a link element
</$button>
<$button class="tc-btn-big-green">
class uses a rule from the stylesheet
</$button>
<$button disabled="yes">
disabled="yes"
</$button>
<$button style.color="green">
style is the last resort
</$button>
<$button style="font-weight: bold;">
style is the last resort
</$button>
@@ -0,0 +1,52 @@
title: ButtonWidget/Example/SelectedState
description: Highlight the active button with selectedClass, set, setTo and default used together
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
This example combines four attributes so a row of buttons can show which colour is active:
* `set` and `setTo` write the chosen colour to a state tiddler
* `selectedClass` marks the button whose `setTo` matches the current value of `set`
* `default` gives the value to compare against while the `set` tiddler does not yet exist
Because `default` is `red`, the ''Red'' button starts out selected. Click another colour to move the selection.
+
title: Output
<!-- The inline <style> below is only for this self-contained test/development example.
For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>
.demo-swatch {
padding: 0.2em 0.7em;
}
.demo-selected {
outline: 2px solid #1a73e8;
}
</style>
<$button set="$:/state/colour" setTo="red"
default="red"
class="demo-swatch tc-small-gap-right"
selectedClass="demo-selected"
>
Red
</$button>
<$button set="$:/state/colour" setTo="green"
default="red"
class="demo-swatch tc-small-gap-right"
selectedClass="demo-selected"
>
Green
</$button>
<$button set="$:/state/colour" setTo="blue"
default="red"
class="demo-swatch tc-small-gap-right"
selectedClass="demo-selected"
>
Blue
</$button>
Selected colour: <$text text={{{ [[$:/state/colour]get[text]] :else[[red]] }}}/>
@@ -0,0 +1,24 @@
title: ButtonWidget/Example/SetAndSetTo
description: Assign a value to a tiddler with the set and setTo attributes used together
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
`set` and `setTo` work as a pair to assign a value when the button is clicked:
* `set` names the storage location, a TextReference
* `setTo` is the value written to it
`set` points at the location //itself//, so it takes no curly brackets, unlike an ordinary transclusion.
Click a colour to write it to the state tiddler shown below.
+
title: Output
<$button set="$:/state/colour" setTo="red">Red</$button>
<$button set="$:/state/colour" setTo="green">Green</$button>
<$button set="$:/state/colour" setTo="blue">Blue</$button>
Selected colour: <$text text={{$:/state/colour}}/>
@@ -0,0 +1,37 @@
title: ButtonWidget/Example/SetField
description: Assign a field or index directly with setTitle, setField and setIndex
modified: 20260724235815000
tags: [[$:/tags/wiki-test-spec]]
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `set` attribute treats its value as a TextReference, which is ambiguous when a title contains `!!` or `##`. The `setTitle` group names the target directly instead.
The attributes work together:
* `setTitle` is the tiddler to change, with no TextReference parsing
* `setField` is the field to write, defaulting to `text`
* `setIndex` writes a data index instead of a field
* `setTo` is the value to assign
Click a button to set the `caption` field, then watch it update below.
+
title: Output
<$button setTitle="$:/state/demo"
setField="caption"
setTo="First"
class="tc-small-gap-right"
>
Set caption to First
</$button>
<$button setTitle="$:/state/demo"
setField="caption"
setTo="Second"
class="tc-small-gap-right"
>
Set caption to Second
</$button>
Caption is now: <$text text={{$:/state/demo!!caption}}/>
@@ -0,0 +1,173 @@
code-body: yes
created: 20260728220000000
description: Bibliography sample data shared by the GroupedLists examples
modified: 20260729180306417
title: GroupedLists/Bibliography
type: text/vnd.tiddlywiki-multiple
title: 3DWiki2011
bibtex-entry-type: InProceedings
bibtex-author: 3D Wiki Collective
bibtex-title: Spatial Notes in Three Dimensions
bibtex-date: 2011
+
title: 3rdWave2014
bibtex-entry-type: Article
bibtex-author: 3rd Wave Research Group
bibtex-title: Note Taking After the Desktop
bibtex-date: 2014
+
title: Aronsson2002
bibtex-entry-type: InProceedings
bibtex-author: Aronsson, Lars
bibtex-title: Operation of a Large Scale, General Purpose Wiki Website
bibtex-date: 2002
+
title: Atkinson2016
bibtex-entry-type: Article
bibtex-author: Atkinson, Paul
bibtex-title: Digital ethnographies
bibtex-date: 2016
+
title: Bakshi2016
bibtex-entry-type: Article
bibtex-author: Bakshi, Divya
bibtex-title: Hypertext and Feminisms: Voicing the Silence
bibtex-date: 2016
+
title: Barker2008
bibtex-entry-type: InProceedings
bibtex-author: Barker, Philip
bibtex-title: Using wikis and weblogs to enhance human performance
bibtex-date: 2008
+
title: Barker2008a
bibtex-entry-type: InProceedings
bibtex-author: Barker, Philip
bibtex-title: Using Wikis for Knowledge Management
bibtex-date: 2008
+
title: Bernstein2016
bibtex-entry-type: Book
bibtex-author: Bernstein, Mark
bibtex-title: Getting Started With Hypertext Narrative
bibtex-date: 2016
+
title: Dalgaard2001
bibtex-entry-type: InProceedings
bibtex-author: Dalgaard, Rune
bibtex-title: Hypertext and the Scholarly Archive: Intertexts, Paratexts and Metatexts at Work
bibtex-date: 2001
+
title: Dickinson2008
bibtex-entry-type: InProceedings
bibtex-author: Dickinson, Anne
bibtex-title: Is the e-Learning Object Create Interactive Accessible e-Learning Accessible?
bibtex-date: 2008
+
title: Finnemann2016
bibtex-entry-type: Article
bibtex-author: Finnemann, Niels Ole
bibtex-title: Hypertext configurations: Genres in networked digital media
bibtex-date: 2016
+
title: Frumkin2005
bibtex-entry-type: Article
bibtex-author: Frumkin, Jeremy
bibtex-title: The wiki and the digital library
bibtex-date: 2005
+
title: Maier2016
bibtex-entry-type: InCollection
bibtex-author: Maier, Carmen Daniela
bibtex-title: Hypertext and Hypermedia
bibtex-date: 2016
+
title: Morris2007
bibtex-entry-type: InProceedings
bibtex-author: Morris, Joseph C.
bibtex-title: DistriWiki: a distributed peer-to-peer wiki network
bibtex-date: 2007
+
title: Ruston2004
bibtex-entry-type: Book
bibtex-author: Ruston, Jeremy
bibtex-title: TiddlyWiki
bibtex-date: 2004
+
title: Rutherford2009
bibtex-entry-type: Thesis
bibtex-author: Rutherford, Jayne
bibtex-title: Graphical Input for TiddlyWiki
bibtex-date: 2009
+
title: Schaffert2006
bibtex-entry-type: InProceedings
bibtex-author: Schaffert, Sebastian
bibtex-title: IkeWiki: A semantic wiki for collaborative knowledge management
bibtex-date: 2006
+
title: Shang2016
bibtex-entry-type: Article
bibtex-author: Shang, Hui-Fang
bibtex-title: Online metacognitive strategies, hypermedia annotations, and motivation on hypertext comprehension
bibtex-date: 2016
+
title: Shang2016a
bibtex-entry-type: Article
bibtex-author: Shang, Hui-Fang
bibtex-title: Exploring demographic and motivational factors associated with hypertext reading by English as a foreign language students
bibtex-date: 2016
+
title: Skiba2005
bibtex-entry-type: Article
bibtex-author: Skiba, Diane J.
bibtex-title: Do your students wiki?
bibtex-date: 2005
+
title: Trentin2009
bibtex-entry-type: Article
bibtex-author: Trentin, Guglielmo
bibtex-title: Using a wiki to evaluate individual contribution to a collaborative learning project
bibtex-date: 2009
+
title: Truman2016
bibtex-entry-type: Article
bibtex-author: Truman, Gail
bibtex-title: Web Archiving Environmental Scan
bibtex-date: 2016
+
title: Wagner2004
bibtex-entry-type: Article
bibtex-author: Wagner, Christian
bibtex-title: Wiki: A technology for conversational knowledge management and group collaboration
bibtex-date: 2004
+
title: Wilson2007
bibtex-entry-type: Article
bibtex-author: Wilson, Tom D.
bibtex-title: Review of TiddlyWiki 2.1.3
bibtex-date: 2007
@@ -0,0 +1,45 @@
created: 20260728220100000
description: Group tiddlers by the value of a field chosen in one place
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729185240642
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/ByField
type: text/vnd.tiddlywiki-multiple
title: Narrative
`groupField` names the field to group by. It is the only line to change when you want a different grouping.
Two nested lists do the work:
* `f.valuesAll` collects each distinct value of that field, sorted
* `f.tiddlersFor` returns the tiddlers holding one of those values
This test case imports 24 bibliography tiddlers from the `GroupedLists/Bibliography` payload. They came from a ~BibTeX import, so the field is `bibtex-author` rather than `author`.
Barker and Shang each published twice, so their groups list two titles. Set `groupField` to `bibtex-entry-type` to regroup the same data by publication type.
+
title: Output
\whitespace trim
<!-- bibtex-author<value> would make the field name the operator, which cannot be a variable, so f.tiddlersFor compares via get<groupField> -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<$list filter="[f.valuesAll[]]" variable="_value">
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,51 @@
title: GroupedLists/Example/RecentTab
created: 20260729190100000
modified: 20260729190100000
description: Group tiddlers by day with the timeline macro, as the sidebar Recent tab does
tags: [[$:/tags/wiki-test-spec]] GroupedLists
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `timeline` macro groups tiddlers by the day found in a date field. The ''Recent'' tab in the sidebar is a single call to it, which is all the Output below is.
The nesting happens inside the macro:
* the outer filter uses `eachday` to yield one tiddler per distinct day
* the inner filter uses `sameday` to collect every tiddler falling on that day
* `dateField` chooses which field to read, so the same macro groups by `created` just as well as by `modified`
The five dummy payload tiddlers below carry hand set `modified` dates. Nothing else here has a date field, so they are exactly what the timeline finds.
Pass `format` to change how each day is headed, and `limit` to cap how many tiddlers are considered.
+
title: Output
\whitespace trim
<$transclude $variable="timeline" format="DDth MMM YYYY"/>
+
title: Alpha
modified: 20260103120000000
Modified on the third, later in the day.
+
title: Beta
modified: 20260103100000000
Modified on the third, earlier in the day.
+
title: Gamma
modified: 20260102120000000
Modified on the second, later in the day.
+
title: Delta
modified: 20260102100000000
Modified on the second, earlier in the day.
+
title: Epsilon
modified: 20260101120000000
Modified on the first.
@@ -0,0 +1,116 @@
created: 20260728224000000
description: Group by one field while the search box filters on another
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/SearchAnotherField
type: text/vnd.tiddlywiki-multiple
title: Narrative
`searchField` names the field the search box looks at, which no longer has to be the field the list groups by.
Three definitions carry the split:
* `groupField` is `bibtex-entry-type`, so the groups and the tabs are publication types
* `searchField` is `bibtex-author`, so typing still matches people
* `f.tiddlersMatching` narrows the tiddlers first, and the groups are derived from whatever survives
Because the groups are computed after the search, a whole group disappears once none of its entries match. The tab strip is still built from the unfiltered data, so the tabs themselves stay put.
This test case carries the same `_TabButton` and `_TabContent` payload tiddlers as the previous example.
Type `bar` to leave only ''InProceedings'', holding Barker's two papers. The other tabs drop to zero and show the `emptyMessage` of the list.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<searchField> is impossible.
f.tiddlersMatching searches the extracted value as a title, which works whichever field it came from. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-entry-type
\procedure searchField() bibtex-author
\procedure searchLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.tiddlersMatching() [all[tiddlers]!is[system]has<groupField>] :filter[get<searchField>search:title{$:/temp/GroupedLists/search-other}]
\function f.valuesFound() [f.tiddlersMatching[]each<groupField>get<groupField>sort[]]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesFound[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesFound[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [f.tiddlersMatching[]] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab-other"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<div class="tc-search demo-group-filter">
<label>
<<searchLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search-other"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<searchLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search-other]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search-other"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,111 @@
created: 20260728220400000
description: Combine the tab strip with a search box inside the tab panel
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/TabsWithSearch
type: text/vnd.tiddlywiki-multiple
title: Narrative
`_TabContent` holds the search box as well as the list, so the search sits inside the panel and filters within the selected tab.
Which values each definition reads is the whole design:
* `f.letters` reads the //unfiltered// values, so tabs never disappear while you type and the selected tab cannot be stranded on an empty panel
* `f.valuesShown` reads the //filtered// values, so the counts follow the search and a tab showing zero tells you not to look there
This test case carries the same `_TabButton` and `_TabContent` payload tiddlers as the previous example, with the search box added to the panel.
The input is a sibling of the list rather than a child of anything that recomputes, which is what keeps the cursor in the box while you type.
Type `ba` and watch the counts change. Any tab whose count falls to zero shows the `emptyMessage` of the list instead of nothing at all.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<groupField> and bibtex-author<value> are impossible.
f.valuesFound searches the collected values as titles, f.tiddlersFor compares via get<groupField>. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\procedure groupLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.valuesFound() [f.valuesAll[]] :filter[search:title{$:/temp/GroupedLists/search}]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesFound[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesFound[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab-search"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<div class="tc-search demo-group-filter">
<label>
<<groupLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<groupLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,60 @@
title: GroupedLists/Example/TypesTab
created: 20260729190000000
modified: 20260729190000000
description: Group tiddlers by their type, as the sidebar Types tab does
tags: [[$:/tags/wiki-test-spec]] GroupedLists
type: text/vnd.tiddlywiki-multiple
title: Narrative
`$:/core/Filters/TypedTiddlers` collects one tiddler per distinct type, and the inner list then finds every tiddler sharing that type.
The two lists do different jobs:
* the outer filter ends in `each[type]`, so it yields one representative tiddler per type rather than every tiddler
* the inner filter reads `{!!type}` from that representative, so `currentTiddler` is what carries the type from the outer list to the inner one
This is the code behind the ''Types'' tab in the sidebar, under ''More''. Open that tab to see it running over a real wiki.
The five dummy payload tiddlers below are the only ones here with a `type` field, so they are exactly what the list finds.
+
title: Output
\whitespace trim
<$list filter={{$:/core/Filters/TypedTiddlers!!filter}}>
<div class="tc-menu-list-item">
<$view field="type"/>
<$list filter="[type{!!type}!is[system]sort[title]]">
<div class="tc-menu-list-subitem">
<$link to={{!!title}}><$view field="title"/></$link>
</div>
</$list>
</div>
</$list>
+
title: Alpha
type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><circle cx="8" cy="8" r="7"/></svg>
+
title: Beta
type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><rect x="1" y="1" width="14" height="14"/></svg>
+
title: Gamma
type: text/plain
A plain text sample.
+
title: Delta
type: text/plain
Another plain text sample.
+
title: Epsilon
type: application/json
{"sample": true}
@@ -0,0 +1,82 @@
created: 20260728220200000
description: Narrow a grouped list with a search box
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729185240642
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/WithSearch
type: text/vnd.tiddlywiki-multiple
title: Narrative
`search:title` filters the values collected by `f.valuesAll`, so the grouped list narrows while you type without the tiddlers themselves being searched.
Two definitions are added to the plain grouped list:
* `groupLabel` is the human readable name shown beside the input
* `f.valuesFound` applies the search to the collected values
An empty box is a no operation, so the whole list comes back. The clear button appears only once there is something to clear, and puts the cursor back in the input.
Type `ann` to match Dickinson and Finnemann, `wi` to match several at once, or `zzz` to fall through to the `emptyMessage` of the outer list.
+
title: Output
\whitespace trim
<!-- Filter operator suffixes cannot be variables, so search:<groupField> and bibtex-author<value> are impossible.
f.valuesFound searches the collected values as titles, f.tiddlersFor compares via get<groupField>. -->
<!-- tm-focus-selector takes the first match in the document, so .tc-search input would hit the sidebar search. -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\procedure groupLabel() Author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.valuesFound() [f.valuesAll[]] :filter[search:title{$:/temp/GroupedLists/search}]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<!-- The inline style below is only for this self-contained example. For production, define styles in a tiddler tagged $:/tags/Stylesheet instead. -->
<style>.demo-group-filter { margin-bottom: 1em; }</style>
<div class="tc-search demo-group-filter">
<label>
<<groupLabel>>:<$edit-text
tiddler="$:/temp/GroupedLists/search"
tag="input"
type="search"
default=""
placeholder={{{ [[Filter by ]addsuffix<groupLabel>] }}}
class="tc-tiny-gap"
/>
</label>
<%if [[$:/temp/GroupedLists/search]get[text]minlength[1]] %>
<$button
class="tc-btn-invisible"
tooltip="Clear the filter"
aria-label="Clear the filter"
>
<$action-deletetiddler $tiddler="$:/temp/GroupedLists/search"/>
<$action-sendmessage $message="tm-focus-selector" $param=".demo-group-filter input"/>
{{$:/core/images/close-button}}
</$button>
<%endif%>
</div>
<$list filter="[f.valuesFound[]]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -0,0 +1,76 @@
created: 20260728220300000
description: Bucket the groups into tabs by first character, with a count per tab
import-compound: [[GroupedLists/Bibliography]]
modified: 20260729190424885
tags: $:/tags/wiki-test-spec GroupedLists
title: GroupedLists/Example/WithTabs
type: text/vnd.tiddlywiki-multiple
title: Narrative
The `tabs` macro takes its tab list from a filter, so here the tabs come out of the data: one per distinct first character, plus an ''All'' tab.
Three definitions feed it:
* `f.letters` collects the distinct first characters
* `f.valuesFor` selects the groups filed under one character
* `f.valuesShown` returns everything for ''All'', one character otherwise
This test case carries two payload tiddlers, `_TabButton` and `_TabContent`, because the `tabs` macro transcludes its caption and its panel by tiddler title and cannot take inline wikitext.
Matching is case insensitive, so `de Vries` and `De Vries` would share a tab. A character that is not a letter gets its own tab, which is why the sample data produces a `3` tab. Each caption carries the number of groups behind it, so no tab is a dead end.
+
title: Output
\whitespace trim
<!-- bibtex-author<value> would make the field name the operator, which cannot be a variable, so f.tiddlersFor compares via get<groupField> -->
<!-- No trailing whitespace after the field name: it becomes part of the name and the list silently renders nothing -->
\procedure groupField() bibtex-author
\function f.valuesAll() [all[tiddlers]!is[system]has<groupField>each<groupField>get<groupField>sort[]]
\function f.letters() [f.valuesAll[]] :map[uppercase[]split[]first[]] +[unique[]sort[]]
\function f.valuesFor(letter) [f.valuesAll[]] :filter[uppercase[]split[]first[]match<letter>]
\function f.valuesShown(letter) [f.valuesAll[]] :filter[<letter>match[All]] :else[f.valuesFor<letter>]
\function f.tiddlersFor(value) [!is[system]has<groupField>] :filter[get<groupField>match<value>] +[sort[title]]
<$macrocall
$name="tabs"
tabsList="[[All]] [f.letters[]]"
default="All"
state="$:/state/GroupedLists/tab"
buttonTemplate="_TabButton"
template="_TabContent"
/>
+
title: _TabButton
code-body: yes
\whitespace trim
<$text text=<<currentTab>>/><span class="tc-tiny-gap-left">(<$count filter="[f.valuesShown<currentTab>]"/>)</span>
+
title: _TabContent
code-body: yes
\whitespace trim
<$list filter="[f.valuesShown<currentTab>]" variable="_value">
<$list-empty>
//No matches//
</$list-empty>
<div class="tc-menu-list-item">
<<_value>>
<$list filter="[f.tiddlersFor<_value>]">
<div class="tc-menu-list-subitem">
<$link><$text text={{{ [<currentTiddler>get[bibtex-title]] :else[<currentTiddler>] }}}/></$link>
</div>
</$list>
</div>
</$list>
@@ -1,7 +1,7 @@
created: 20130822170200000
icon: $:/core/icon
list: [[A Gentle Guide to TiddlyWiki]] [[Discover TiddlyWiki]] [[Some of the things you can do with TiddlyWiki]] [[Ten reasons to switch to TiddlyWiki]] Examples [[What happened to the original TiddlyWiki?]]
modified: 20260420192600833
modified: 20260710091122803
tags: Welcome
title: HelloThere
type: text/vnd.tiddlywiki
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 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,11 +1,10 @@
title: $:/changenotes/5.4.0/#9107
description: Update configuration defaults
description: Update default sidebar layout
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: usability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9107
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9107 https://github.com/TiddlyWiki/TiddlyWiki5/issues/9757
github-contributors: Jermolene
* Changed default sidebar layout from ''fixed-fluid'' to ''fluid-fixed''
* Changed ''Wrap long lines in code blocks'' default to ''No''
Changed default sidebar layout from ''fixed-fluid'' to ''fluid-fixed''
@@ -0,0 +1,13 @@
change-category: internal
change-type: bugfix
created: 20260710092852681
description: Fixes an issue with the minheight of textareas.
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9828
modified: 20260710093223971
release: 5.4.1
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.1/#9828
type: text/vnd.tiddlywiki
Fixes an issue with the minheight of textareas.
@@ -0,0 +1,9 @@
title: $:/changenotes/5.4.0/#9829
description: Update Polish translation
release: 5.4.1
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: translation
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9829
github-contributors: EvidentlyCube
@@ -1,11 +1,14 @@
title: $:/changenotes/5.4.1/#9836
description: Fix mid-text link cutoff, fix disabling freelinks toggle blanks view, and add configurable MaxLinks cap
tags: $:/tags/ChangeNote
release: 5.4.1
change-type: bugfix
change-category: plugin
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9836
change-type: bugfix
created: 20260710092644515
description: Freelinks: Fix mid-text link cutoff, fix disabling freelinks toggle blanks view, and add configurable ~MaxLinks cap
github-contributors: s793016
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9836
modified: 20260710092700225
release: 5.4.1
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.1/#9836
type: text/vnd.tiddlywiki
Fixes correctness and reliability issues in the freelinks plugin.
@@ -0,0 +1,13 @@
change-category: widget
change-type: bugfix
created: 20260710093059614
description: Fixes a regression in the SelectWidget when selecting multiple values.
github-contributors: pmario
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9841
modified: 20260710093210171
release: 5.4.1
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.1/#9841
type: text/vnd.tiddlywiki
Fixes a regression in the SelectWidget when selecting multiple values.
@@ -0,0 +1,9 @@
title: $:/changenotes/5.4.0/#9870
description: Update Japanese translation
release: 5.4.1
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: translation
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9870
github-contributors: IchijikuIchigo
@@ -0,0 +1,14 @@
change-category: plugin
change-type: bugfix
created: 20260710093318232
description: Katex: restores a missing font
github-contributors: Leilei332
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9873
modified: 20260710093432151
release: 5.4.1
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.1/#9873
type: text/vnd.tiddlywiki
Restores the missing KaTeX_Caligraphic-Regular.woff2
font in the Katex plugin
@@ -0,0 +1,21 @@
title: $:/changenotes/5.4.1/#9882
created: 20260701142414000
modified: 20260701142414000
description: Fix invalid source positions in parse tree nodes
release: 5.4.1
tags: $:/tags/ChangeNote
change-type: bugfix
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9882
github-contributors: pmario
type: text/vnd.tiddlywiki
Several wikitext parser rules emitted parse tree nodes with missing or incorrect `start` and `end` source positions. Accurate positions are needed by tooling that maps rendered output back to the source text.
* ''Inline code'' (`` `code` ``): the text node `end` no longer runs past the closing backtick, so the span covers only the code text.
* ''Suppressed external links'' (`~https://example.com`): the text node now starts after the suppressing `~`, matching the plain text that is emitted.
* ''Suppressed wikilinks'' (`~SuppressedLink`): the text node now carries `start` and `end` positions. Previously it had none.
* ''Import filter'' (`\import`): the filter start position was set to the whole source string instead of the current parse position. It now uses the correct offset.
@@ -0,0 +1,13 @@
title: $:/changenotes/5.4.1/#9905
created: 20260709142414000
modified: 20260709142414000
description: Fix errors in eventcatcher while serializing event properties
release: 5.4.1
tags: $:/tags/ChangeNote
change-type: bugfix
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9905
github-contributors: saqimtiaz
type: text/vnd.tiddlywiki
Fixes an issue in utils.copyObjectPropertiesSafe which did not correctly handle DOM objects from other windows resulting in an error.
@@ -1,13 +1,19 @@
caption: 5.4.1
created: 20260508181012812
modified: 20260508181012812
description: Release v5.4.1 addresses regressions and bugs introduced in the v5.4.0 release.
modified: 20260710091945414
released: 20260710090509444
tags: ReleaseNotes
title: Release 5.4.1
type: text/vnd.tiddlywiki
description: Under development
\procedure release-introduction()
Release v5.4.1 is under development.
Release v5.4.1 addresses regressions and bugs introduced in the v5.4.0 release.
\end release-introduction
\define banner-credit-user-name() Peter
\define banner-credit-user-link() https://talk.tiddlywiki.org/u/peter
\define banner-credit-discussion-link() https://talk.tiddlywiki.org/t/vote-for-the-banner-tiddlywiki-v5-4-0/15016
\define banner-credit-permalink() https://raw.githubusercontent.com/TiddlyWiki/TiddlyWiki5/92caa7312ebc51c59cd345cc81b4a326661a0650/editions/tw5.com/tiddlers/images/New%20Release%20Banner.webp
<<releasenote 5.4.1>>
@@ -5,25 +5,27 @@ created: 20131129101027725
delivery: App
description: iPad/iPhone app for working with TiddlyWiki
method: save
modified: 20201007205336209
modified: 20260421210013537
tags: Saving iOS [[Standalone App]]
title: Saving on iPad/iPhone
type: text/vnd.tiddlywiki
The iPad/iPhone app ''Quine 2'' makes it possible to view, edit and then save changes to TiddlyWiki5 on iOS. [[Download it here|https://apps.apple.com/us/app/quine-2/id1450128957]].
The iPad/iPhone app ''Quine'' makes it possible to view, edit and then save changes to TiddlyWiki5 on iOS.
Currently in its third major version, rewritten for iOS and iPadOS 26.0.
[[Download it here|https://apps.apple.com/us/app/TiddlyWiki51664603]].
Instructions for use:
# Open Quine 2
# Tap the + toolbar button to create and open a new TiddlyWiki
# Open Quine
# From the file list tap the + toolbar button, or the "New Wiki" button, to create and open a new TiddlyWiki
# From the file list tap an existing TiddlyWiki file to open it
# Edit the TiddlyWiki as normal, and save as normal using either Autosave or the TiddlyWiki save button <<.icon $:/core/images/save-button-dynamic>>
# Tap the left hand "Documents" toolbar button to close an open TiddlyWiki
# Tap the left hand "<" toolbar button to close an open TiddlyWiki, returning to the file list
*Quine 2 works natively in iOS with the local file system and the iCloud file system
*Quine 2 also allows you to open, edit and save TiddlyWiki files stored with cloud file providers
*Quine 2 allows you to follow embedded WikiText links and canonical links to external files for cloud-like file providers which support "folder level sharing".
**This includes the apps "Secure Shellfish" and "Working Copy". Most providers, though, do not allow apps like Quine 2 to access linked files this way.
** If you wish to enable such links for "well behaved" file providers, toggle "on" the "Enable folder selection for out-of-sandbox links" setting in iOS Settings for Quine 2
*Quine works natively in iOS with the local file system and the iCloud file system
*Quine also allows you to open, edit and save TiddlyWiki files stored with other cloud file providers
*Quine allows you to follow embedded WikiText links and canonical links to external files on cloud-like file providers which support "folder level sharing".
//Note that Quine is published independently of TiddlyWiki//
@@ -1,6 +1,6 @@
caption: button
created: 20131024141900000
modified: 20251101091926820
modified: 20260725015330000
tags: Widgets TriggeringWidgets
title: ButtonWidget
type: text/vnd.tiddlywiki
@@ -66,4 +66,40 @@ Press me!
</$button>
```
''Tip:'' Set ''class'' to `tc-btn-invisible tc-tiddlylink` to have a button look like an internal link.
! Order of execution
The order of the attributes written in wikitext does ''not'' matter.
A button can carry several of these attributes at once. They are not alternatives: every one that is present is used, and ''always in the order shown below''.
# ActionWidgets in the button body
# <<.attr to>>
# <<.attr message>>, with <<.attr param>>
# <<.attr popup>> or <<.attr popupTitle>>
# <<.attr set>> or <<.attr setTitle>>, with <<.attr setTo>>, <<.attr setField>> and <<.attr setIndex>>
# <<.attr actions>>
<<.tip """When a button does more than one thing, put the whole behaviour in the `actions` attribute. It keeps the behaviour in one place and runs it last, after every other attribute above. A single `<$button to="HelloThere">Click Me</$button>` needs no action string.""">>
Action widgets are refreshed before each one runs, so they see the results of the actions before them. Ordinary widgets wrapped around them are not. See [[ActionWidget Execution Modes]] when a value looks out of date.
! Examples
This button uses the <<.attr to>> attribute to navigate to the [[HelloThere]] tiddler, opening it in the story river. The optional <<.attr tooltip>> attribute sets the text shown on hover.
<$macrocall $name='wikitext-example-without-html'
src='<$button to="HelloThere" tooltip="Navigate to the HelloThere tiddler">
Open ~HelloThere
</$button>'/>
<<.tip """Set ''class'' to `tc-btn-invisible tc-tiddlylink` to have a button look like an internal link.""">>
! Interactive Examples
<<<
<$list filter="[prefix[ButtonWidget/Example/]]">
; <$link/>
: {{!!description}}
</$list>
<<<
@@ -1,5 +1,5 @@
created: 20221007144237585
modified: 20240422084734129
modified: 20260807190532419
tags: Concepts
title: Custom Widgets
type: text/vnd.tiddlywiki
@@ -79,3 +79,34 @@ Python
<$let test="Tiger">
<$codeblock code=<<test>>/>
</$let>""">>
In this example, we override the <<.wlink "LinkWidget">> widget to automatically display an icon next to the link if the target tiddler has an `icon` field. We use the <<.wlink "ParametersWidget">> widget to capture all attributes passed to the original link and forward them to the underlying widget via <<.wlink "GenesisWidget">>:
<<wikitext-example-without-html """\widget $link()
\function link.icon()
[<params-var>jsonget[to]else<currentTiddler>get[icon]]
\end link.icon
\function link.text()
[<params-var>jsonget[to]else<currentTiddler>]
\end link.text
\whitespace trim
<$parameters $params="params-var">
<$genesis
$type="$link"
$remappable="no"
$names="[<params-var>jsonindexes[]]"
$values="[<params-var>jsonindexes[]] :map[<params-var>jsonget<currentTiddler>]"
>
<%if [<link.icon>]%>
<span class="tc-tiddler-title-icon tc-titlebar" style="fill:currentColor;font-size:11pt!important">
<$transclude $tiddler=<<link.icon>> size="11pt"/>
</span>
<%endif%>
<$slot $name="ts-raw">
<$text text=<<link.text>> />
</$slot>
</$genesis>
</$parameters>
\end
<<list-links "[has[icon]]">>""">>
@@ -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
">>
">>
-1
View File
@@ -10,7 +10,6 @@ Advanced/ShadowInfo/Shadow/Hint: Tiddler <$link to=<<infoTiddler>>><$text text=<
Advanced/ShadowInfo/Shadow/Source: Zdefiniiowany we wtyczce <$link to=<<pluginTiddler>>><$text text=<<pluginTiddler>>/></$link>
Advanced/ShadowInfo/OverriddenShadow/Hint: Nadpisany przez zwykłego tiddlera
Advanced/CascadeInfo/Heading: Szczegóły Kaskady
Advanced/CascadeInfo/Hint: These are the view template segments that are resolved for each of the system view template cascades
Advanced/CascadeInfo/Hint: Lista elementów widoku, których kaskady określają szablony użyte do wyświetlenia tiddlera
Advanced/CascadeInfo/Detail/View: Widok
Advanced/CascadeInfo/Detail/ActiveCascadeFilter: Aktywny filtr kaskady
+1 -1
View File
@@ -1,7 +1,7 @@
TiddlyWiki created by Jeremy Ruston, (jeremy [at] jermolene [dot] com)
Copyright (c) 2004-2007, Jeremy Ruston
Copyright (c) 2007-2026, UnaMesa Association
Copyright (c) 2007-2025, UnaMesa Association
All rights reserved.
Redistribution and use in source and binary forms, with or without
+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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "tiddlywiki",
"version": "5.4.0",
"version": "5.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tiddlywiki",
"version": "5.4.0",
"version": "5.4.1",
"license": "BSD",
"bin": {
"tiddlywiki": "tiddlywiki.js"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "tiddlywiki",
"preferGlobal": true,
"version": "5.4.1-prerelease",
"version": "5.4.1",
"author": "Jeremy Ruston <jeremy@jermolene.com>",
"description": "a non-linear personal web notebook",
"contributors": [
+1 -1
View File
@@ -1,6 +1,6 @@
<h1 class="">Welcome</h1><p>Welcome to <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a>, a non-linear personal web notebook that anyone can use and keep forever, independently of any corporation.</p><p>TiddlyWiki is a complete interactive wiki in JavaScript. It can be used as a single HTML file in the browser or as a powerful Node.js application. It is highly customisable: the entire user interface is itself implemented in hackable <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/WikiText.html">WikiText</a>.</p><h2 class="">Demo</h2><p>Learn more and see it in action at <a class="tc-tiddlylink-external" href="https://tiddlywiki.com/" rel="noopener noreferrer" target="_blank">https://tiddlywiki.com/</a></p><h2 class="">Developer Documentation</h2><p>Developer documentation is in progress at <a class="tc-tiddlylink-external" href="https://tiddlywiki.com/dev/" rel="noopener noreferrer" target="_blank">https://tiddlywiki.com/dev/</a></p><h2 class="">Pull Request Previews</h2><p>Pull request previews courtesy of <a class="tc-tiddlylink-external" href="https://netlify.com" rel="noopener noreferrer" target="_blank">Netlify</a></p><p><a href="https://www.netlify.com" rel="noopener noreferrer" target="_blank"><img alt="Deploys by Netlify" src="https://www.netlify.com/v3/img/components/netlify-light.svg"></a></p><h1 class="">Join the Community</h1><p>
<h2 class="">User forums</h2><h3 class="">Talk TiddlyWiki</h3><p>As the official TiddlyWiki forum, Talk TiddlyWiki is a place to talk about TiddlyWiki: requests for help, <a class="tc-tiddlylink-external" href="https://talk.tiddlywiki.org/c/announcements/20" rel="noopener noreferrer" target="_blank">announcements</a> of new releases and plugins, debating new features, or just sharing experiences. You can participate via the associated website, or subscribe via email.</p><p><a class="tc-tiddlylink-external" href="https://talk.tiddlywiki.org/" rel="noopener noreferrer" target="_blank">https://talk.tiddlywiki.org/</a></p><h3 class="">Google Groups</h3><p>For the convenience of existing users, we also continue to operate the original TiddlyWiki group (hosted on Google Groups since 2005): <a class="tc-tiddlylink-external" href="https://groups.google.com/group/TiddlyWiki" rel="noopener noreferrer" target="_blank">https://groups.google.com/group/TiddlyWiki</a></p><h2 class="">Developer forums</h2><ul><li><a class="tc-tiddlylink-external" href="https://tiddlywiki.com/dev" rel="noopener noreferrer" target="_blank">tiddlywiki.com/dev</a> is the official developer documentation</li><li>Get involved in the <a class="tc-tiddlylink-external" href="https://github.com/TiddlyWiki/TiddlyWiki5" rel="noopener noreferrer" target="_blank">development on GitHub</a></li><li><a class="tc-tiddlylink-external" href="https://github.com/TiddlyWiki/TiddlyWiki5/discussions" rel="noopener noreferrer" target="_blank">GitHub Discussions</a> are for Q&amp;A and open-ended discussion</li><li><a class="tc-tiddlylink-external" href="https://github.com/TiddlyWiki/TiddlyWiki5/issues" rel="noopener noreferrer" target="_blank">GitHub Issues</a> are for raising bug reports and proposing specific, actionable new ideas</li><li>See <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/Contributing.html">Contributing</a> for guidelines on how to contribute to the project.</li></ul><h2 class="">Other forums</h2><ul><li><a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a> Subreddit: <a class="tc-tiddlylink-external" href="https://www.reddit.com/r/TiddlyWiki5/" rel="noopener noreferrer" target="_blank">/r/TiddlyWiki5</a></li><li>Chat on Discord at <a class="tc-tiddlylink-external" href="https://discord.gg/HFFZVQ8" rel="noopener noreferrer" target="_blank">https://discord.gg/HFFZVQ8</a></li></ul>
</p><hr><h1 class="">Installing TiddlyWiki on Node.js</h1><p>TiddlyWiki is a <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/SingleFileApplication.html">SingleFileApplication</a>, which is easy to use. For advanced users and developers there is a possibility to use a Node.js client / server configuration. This configuration is also used to build the TiddlyWiki <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/SinglePageApplication.html">SinglePageApplication</a></p><ol><li>Install <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/Node.js.html">Node.js</a><ul><li>Linux: <blockquote><div><em>Debian/Ubuntu</em>:<br><code>apt install nodejs</code><br>May need to be followed up by:<br><code>apt install npm</code></div><div><em>Arch Linux</em><br><code>yay -S tiddlywiki</code> <br>(installs node and tiddlywiki)</div></blockquote></li><li>Mac<blockquote><div><code>brew install node</code></div></blockquote></li><li>Android<blockquote><div><a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/Serving%2520TW5%2520from%2520Android.html">Termux for Android</a></div></blockquote></li><li>Other <blockquote><div>See <a class="tc-tiddlylink-external" href="http://nodejs.org" rel="noopener noreferrer" target="_blank">http://nodejs.org</a></div></blockquote></li></ul></li><li>Open a command line terminal and type:<blockquote><div><code>npm install -g tiddlywiki</code></div><div>If it fails with an error you may need to re-run the command as an administrator:</div><div><code>sudo npm install -g tiddlywiki</code> (Mac/Linux)</div></blockquote></li><li>Ensure TiddlyWiki is installed by typing:<blockquote><div><code>tiddlywiki --version</code></div></blockquote><ul><li>In response, you should see <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a> report its current version (eg "5.4.0". You may also see other debugging information reported.)</li></ul></li><li>Try it out:<ol><li><code>tiddlywiki mynewwiki --init server</code> to create a folder for a new wiki that includes server-related components</li><li><code>tiddlywiki mynewwiki --listen</code> to start <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a></li><li>Visit <a class="tc-tiddlylink-external" href="http://127.0.0.1:8080/" rel="noopener noreferrer" target="_blank">http://127.0.0.1:8080/</a> in your browser</li><li>Try editing and creating tiddlers</li></ol></li><li>Optionally, make an offline copy:<ul><li>click the <span class="doc-icon"><svg class="tc-image-save-button-dynamic tc-image-button" height="22pt" viewBox="0 0 128 128" width="22pt">
</p><hr><h1 class="">Installing TiddlyWiki on Node.js</h1><p>TiddlyWiki is a <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/SingleFileApplication.html">SingleFileApplication</a>, which is easy to use. For advanced users and developers there is a possibility to use a Node.js client / server configuration. This configuration is also used to build the TiddlyWiki <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/SinglePageApplication.html">SinglePageApplication</a></p><ol><li>Install <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/Node.js.html">Node.js</a><ul><li>Linux: <blockquote><div><em>Debian/Ubuntu</em>:<br><code>apt install nodejs</code><br>May need to be followed up by:<br><code>apt install npm</code></div><div><em>Arch Linux</em><br><code>yay -S tiddlywiki</code> <br>(installs node and tiddlywiki)</div></blockquote></li><li>Mac<blockquote><div><code>brew install node</code></div></blockquote></li><li>Android<blockquote><div><a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/Serving%2520TW5%2520from%2520Android.html">Termux for Android</a></div></blockquote></li><li>Other <blockquote><div>See <a class="tc-tiddlylink-external" href="http://nodejs.org" rel="noopener noreferrer" target="_blank">http://nodejs.org</a></div></blockquote></li></ul></li><li>Open a command line terminal and type:<blockquote><div><code>npm install -g tiddlywiki</code></div><div>If it fails with an error you may need to re-run the command as an administrator:</div><div><code>sudo npm install -g tiddlywiki</code> (Mac/Linux)</div></blockquote></li><li>Ensure TiddlyWiki is installed by typing:<blockquote><div><code>tiddlywiki --version</code></div></blockquote><ul><li>In response, you should see <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a> report its current version (eg "5.4.1". You may also see other debugging information reported.)</li></ul></li><li>Try it out:<ol><li><code>tiddlywiki mynewwiki --init server</code> to create a folder for a new wiki that includes server-related components</li><li><code>tiddlywiki mynewwiki --listen</code> to start <a class="tc-tiddlylink tc-tiddlylink-resolves" href="https://tiddlywiki.com/static/TiddlyWiki.html">TiddlyWiki</a></li><li>Visit <a class="tc-tiddlylink-external" href="http://127.0.0.1:8080/" rel="noopener noreferrer" target="_blank">http://127.0.0.1:8080/</a> in your browser</li><li>Try editing and creating tiddlers</li></ol></li><li>Optionally, make an offline copy:<ul><li>click the <span class="doc-icon"><svg class="tc-image-save-button-dynamic tc-image-button" height="22pt" viewBox="0 0 128 128" width="22pt">
<g class="tc-image-save-button-dynamic-clean">
<path d="M120.783 34.33c4.641 8.862 7.266 18.948 7.266 29.646 0 35.347-28.653 64-64 64-35.346 0-64-28.653-64-64 0-35.346 28.654-64 64-64 18.808 0 35.72 8.113 47.43 21.03l2.68-2.68c3.13-3.13 8.197-3.132 11.321-.008 3.118 3.118 3.121 8.193-.007 11.32l-4.69 4.691zm-12.058 12.058a47.876 47.876 0 013.324 17.588c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48c14.39 0 27.3 6.332 36.098 16.362L58.941 73.544 41.976 56.578c-3.127-3.127-8.201-3.123-11.32-.005-3.123 3.124-3.119 8.194.006 11.319l22.617 22.617a7.992 7.992 0 005.659 2.347c2.05 0 4.101-.783 5.667-2.349l44.12-44.12z" fill-rule="evenodd"></path>
</g>