From 5ff4e02a6136563482d3a9b430f32e4b88df3994 Mon Sep 17 00:00:00 2001 From: Scott Sauyet Date: Thu, 2 Oct 2025 06:22:37 -0400 Subject: [PATCH 01/82] Update TableOfContents.tid (#9312) Move Welcome back to the top of the TOC. Third attempt. Someday I'll get this right. --- editions/tw5.com/tiddlers/system/TableOfContents.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editions/tw5.com/tiddlers/system/TableOfContents.tid b/editions/tw5.com/tiddlers/system/TableOfContents.tid index b90e0c5935..69b4cb8cbd 100644 --- a/editions/tw5.com/tiddlers/system/TableOfContents.tid +++ b/editions/tw5.com/tiddlers/system/TableOfContents.tid @@ -1,6 +1,6 @@ caption: {{$:/language/SideBar/Contents/Caption}} created: 20140809114010378 -list: HelloThere [[Quick Start]] Learning [[Working with TiddlyWiki]] [[Customise TiddlyWiki]] Features Filters Languages Editions Plugins Platforms Reference Community About +list: Welcome HelloThere [[Quick Start]] Learning [[Working with TiddlyWiki]] [[Customise TiddlyWiki]] Features Filters Languages Editions Plugins Platforms Reference Community About list-before: modified: 20230322150307580 tags: $:/tags/SideBar From 619bdfcab59e1853c70f07da02b8222a563c2dcf Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Thu, 2 Oct 2025 18:42:30 +0800 Subject: [PATCH 02/82] Reimplement regexp sticky flag (#9119) --- core/modules/parsers/parseutils.js | 17 +++++++++-------- core/modules/parsers/wikiparser/rules/html.js | 6 +++--- core/modules/parsers/wikiparser/rules/image.js | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js index 30bc395095..05a85e1ec7 100644 --- a/core/modules/parsers/parseutils.js +++ b/core/modules/parsers/parseutils.js @@ -82,6 +82,7 @@ exports.parseTokenString = function(source,pos,token) { /* Look for a token matching a regex. Returns null if not found, otherwise returns {type: "regexp", match:, start:, end:,} +Use the "Y" (sticky) flag to avoid searching the entire rest of the string */ exports.parseTokenRegExp = function(source,pos,reToken) { var node = { @@ -172,7 +173,7 @@ exports.parseMacroParameter = function(source,pos) { start: pos }; // Define our regexp - var reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/g; + const reMacroParameter = /(?:([A-Za-z0-9\-_]+)\s*:)?(?:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|((?:(?:>(?!>))|[^\s>"'])+)))/y; // Skip whitespace pos = $tw.utils.skipWhiteSpace(source,pos); // Look for the parameter @@ -240,7 +241,7 @@ exports.parseMacroInvocation = function(source,pos) { params: [] }; // Define our regexps - var reMacroName = /([^\s>"'=]+)/g; + const reMacroName = /([^\s>"'=]+)/y; // Skip whitespace pos = $tw.utils.skipWhiteSpace(source,pos); // Look for a double less than sign @@ -277,7 +278,7 @@ exports.parseFilterVariable = function(source) { params: [], }, pos = 0, - reName = /([^\s"']+)/g; + reName = /([^\s"']+)/y; // If there is no whitespace or it is an empty string then there are no macro parameters if(/^\S*$/.test(source)) { node.name = source; @@ -302,11 +303,11 @@ exports.parseAttribute = function(source,pos) { start: pos }; // Define our regexps - var reAttributeName = /([^\/\s>"'`=]+)/g, - reUnquotedAttribute = /([^\/\s<>"'`=]+)/g, - reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/g, - reIndirectValue = /\{\{([^\}]+)\}\}/g, - reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/g; + const reAttributeName = /([^\/\s>"'`=]+)/y, + reUnquotedAttribute = /([^\/\s<>"'`=]+)/y, + reFilteredValue = /\{\{\{([\S\s]+?)\}\}\}/y, + reIndirectValue = /\{\{([^\}]+)\}\}/y, + reSubstitutedValue = /(?:```([\s\S]*?)```|`([^`]|[\S\s]*?)`)/y; // Skip whitespace pos = $tw.utils.skipWhiteSpace(source,pos); // Get the attribute name diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js index c5f0e86c56..47c375b5e1 100644 --- a/core/modules/parsers/wikiparser/rules/html.js +++ b/core/modules/parsers/wikiparser/rules/html.js @@ -49,7 +49,7 @@ exports.parse = function() { // Advance the parser position to past the tag this.parser.pos = tag.end; // Check for an immediately following double linebreak - var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g); + var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y); // Set whether we're in block mode tag.isBlock = this.is.block || hasLineBreak; // Parse the body if we need to @@ -100,7 +100,7 @@ exports.parseTag = function(source,pos,options) { orderedAttributes: [] }; // Define our regexps - var reTagName = /([a-zA-Z0-9\-\$\.]+)/g; + const reTagName = /([a-zA-Z0-9\-\$\.]+)/y; // Skip whitespace pos = $tw.utils.skipWhiteSpace(source,pos); // Look for a less than sign @@ -148,7 +148,7 @@ exports.parseTag = function(source,pos,options) { pos = token.end; // Check for a required line break if(options.requireLineBreak) { - token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g); + token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/y); if(!token) { return null; } diff --git a/core/modules/parsers/wikiparser/rules/image.js b/core/modules/parsers/wikiparser/rules/image.js index 2bc90b80d3..9ae093380a 100644 --- a/core/modules/parsers/wikiparser/rules/image.js +++ b/core/modules/parsers/wikiparser/rules/image.js @@ -113,7 +113,7 @@ exports.parseImage = function(source,pos) { // Skip whitespace pos = $tw.utils.skipWhiteSpace(source,pos); // Get the source up to the terminating `]]` - token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/g); + token = $tw.utils.parseTokenRegExp(source,pos,/(?:([^|\]]*?)\|)?([^\]]+?)\]\]/y); if(!token) { return null; } From 87ba87bdd21c84790baabd01252b7c9879307b86 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Thu, 2 Oct 2025 12:49:45 +0200 Subject: [PATCH 03/82] Set AES strength to 256 bit (#8249) * Set AES strength to 256 bit * Update Encryption tiddler to AES 256 --- boot/boot.js | 8 +++++--- editions/tw5.com/tiddlers/saving/Encryption.tid | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/boot/boot.js b/boot/boot.js index 9b88b98b14..ece7404680 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -799,12 +799,13 @@ the password, and to encrypt/decrypt a block of text $tw.utils.Crypto = function() { var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl, currentPassword = null, - callSjcl = function(method,inputText,password) { + callSjcl = function(method,inputText,password,options) { + options = options || {}; password = password || currentPassword; var outputText; try { if(password) { - outputText = sjcl[method](password,inputText); + outputText = sjcl[method](password,inputText,options); } } catch(ex) { console.log("Crypto error:" + ex); @@ -830,7 +831,8 @@ $tw.utils.Crypto = function() { return !!currentPassword; } this.encrypt = function(text,password) { - return callSjcl("encrypt",text,password); + // set default ks:256 -- see: http://bitwiseshiftleft.github.io/sjcl/doc/convenience.js.html + return callSjcl("encrypt",text,password,{v:1,iter:10000,ks:256,ts:64,mode:"ccm",adata:"",cipher:"aes"}); }; this.decrypt = function(text,password) { return callSjcl("decrypt",text,password); diff --git a/editions/tw5.com/tiddlers/saving/Encryption.tid b/editions/tw5.com/tiddlers/saving/Encryption.tid index ccf8e5a83f..0c48d53407 100644 --- a/editions/tw5.com/tiddlers/saving/Encryption.tid +++ b/editions/tw5.com/tiddlers/saving/Encryption.tid @@ -1,10 +1,10 @@ created: 20130825160900000 -modified: 20241106165307259 +modified: 20250617140259415 tags: Features [[Working with TiddlyWiki]] title: Encryption type: text/vnd.tiddlywiki -When used as a single HTML file, TiddlyWiki5 allows content to be encrypted with AES 128 bit encryption in CCM mode using the [[Stanford JavaScript Crypto Library]]. +When used as a single HTML file, TiddlyWiki5 allows content to be encrypted with AES 256 bit encryption in CCM mode using the [[Stanford JavaScript Crypto Library]]. # Switch to the ''Tools'' tab in the sidebar and look for the button with a padlock icon # If the button is labelled <<.icon $:/core/images/unlocked-padlock>> ''set password'' then the current wiki is not encrypted. Clicking the button will prompt for a password that will be used to encrypt subsequent saves @@ -14,5 +14,5 @@ When used as a single HTML file, TiddlyWiki5 allows content to be encrypted with Note that TiddlyWiki has two other unrelated features concerned with passwords/encryption: -* The ability to set a password when saving to TiddlySpot. This is done in the "Saving" tab of ''control panel'' <<.icon $:/core/images/options-button>>. -* The ability to use standard HTTP basic authentication with the [[Node.js|TiddlyWiki on Node.js]] server configuration. This is done on the command line with the ServerCommand. Combined with SSL, this gives the same level of transit encryption as you'd get with online services like Google or Dropbox, but there is no encryption of data on disk +* The ability to set a password when saving to [[Tiddlyhost]]. This is done in the "Saving" tab of ''control panel'' <<.icon $:/core/images/options-button>>. +* The ability to use standard HTTP basic authentication with the [[Node.js|TiddlyWiki on Node.js]] server configuration. This is done on the command line with the ListenCommand. Combined with SSL, this gives the same level of transit encryption as you'd get with online services like Google or Dropbox, but there is no encryption of data on disk From a2e5c2cca282b7b1cb0d0e6dd960dace3cf4b110 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Thu, 2 Oct 2025 12:49:59 +0200 Subject: [PATCH 04/82] Add documentation for Encrypted Import Problems (#9100) --- .../saving/Encrypted Wiki Import Problems.tid | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 editions/tw5.com/tiddlers/saving/Encrypted Wiki Import Problems.tid diff --git a/editions/tw5.com/tiddlers/saving/Encrypted Wiki Import Problems.tid b/editions/tw5.com/tiddlers/saving/Encrypted Wiki Import Problems.tid new file mode 100644 index 0000000000..894336f9ef --- /dev/null +++ b/editions/tw5.com/tiddlers/saving/Encrypted Wiki Import Problems.tid @@ -0,0 +1,22 @@ +created: 20250617180651017 +modified: 20250617183155928 +tags: Encryption +title: Encrypted Wiki Import Problems +type: text/vnd.tiddlywiki + +There have been some problems with ''importing encrypted'' TWs ''into'' version v5.2.0 and v5.2.1. + +While encrypting and decrypting single file wikis with v5.2.0 and v5.2.1 works as intended. There are some problems if encrypted wikis are ''imported'', if those wikis have been ''encrypted with any other version''. + +So if you import a wiki that was been encrypted with eg: TW v5.1.23 into v5.2.0 or v5.2.1 the v5.1.23 wiki could not be decrypted by v5.2.0 or v5.2.1. + +!! Upgrading + +* ''Importing'' encrypted wikis from any v5.1.x, v5.2.x and v5.3.x ''into the latest'' TW versions ''works as expected'' +* The ''only target'' TW versions that can cause ''importing problems'' are ''v5.2.0'' and ''v5.2.1'' + +!! Downgrading + +* Importing an encrypted v5.''3''.x into v5.''2''.x and v5.''3''.x ''works'' as expected +** ''Except'' v5.2.0 and v5.2.1 +* Importing an encrypted v5.''3''.x to v5.''1''.x ''does not work'' From 6791f0f130e8e96af7facfb6a3c7da39fd3cd778 Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Thu, 2 Oct 2025 22:33:09 +0200 Subject: [PATCH 05/82] Create eslint.yml (#9315) Adds a workflow to lint PRs, and reports issues found in lines modified in the PR. --- .github/workflows/eslint.yml | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/eslint.yml diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000000..a4c572a7a6 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,43 @@ +name: ESLint + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - master + workflow_dispatch: + +concurrency: + group: lint-${{ github.event.pull_request.number || github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + # Needed for GitHub Checks API + checks: write + +jobs: + eslint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install --include=dev + + - name: Run ESLint with reviewdog (GitHub Checks) + uses: reviewdog/action-eslint@v1 + with: + eslint_flags: '.' + reporter: github-pr-check + fail_level: error + level: error + tool_name: ESLint PR code From afe2ac45d98759f666b36de6420d1692a214c740 Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Thu, 2 Oct 2025 22:38:59 +0200 Subject: [PATCH 06/82] Fix: Update eslint.yml to not run on push --- .github/workflows/eslint.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index a4c572a7a6..48b02f621f 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -3,9 +3,6 @@ name: ESLint on: pull_request: types: [opened, synchronize, reopened] - push: - branches: - - master workflow_dispatch: concurrency: From 7c020d0eb614f5c5ba093b50a294f578bae7e9bf Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Mon, 6 Oct 2025 17:22:31 +0200 Subject: [PATCH 07/82] Add a CSV Table as Example to Typed Blocks in Wikitext (#9318) --- .../wikitext/Typed Blocks in WikiText.tid | 73 ++++++++----------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/editions/tw5.com/tiddlers/wikitext/Typed Blocks in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Typed Blocks in WikiText.tid index f136f5d836..8bb5502445 100644 --- a/editions/tw5.com/tiddlers/wikitext/Typed Blocks in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Typed Blocks in WikiText.tid @@ -1,78 +1,67 @@ +caption: Typed Blocks created: 20131205161051792 -modified: 20140120171407764 +modified: 20251006150732150 tags: WikiText title: Typed Blocks in WikiText type: text/vnd.tiddlywiki -caption: Typed Blocks WikiText can include blocks of text that are rendered with an explicit ContentType like this: -``` -$$$image/svg+xml - - - -$$$ -``` +!! Image SVG -This renders as: - -$$$image/svg+xml +< $$$ +""">> It is also possible to abbreviate the ContentType to a file extension. For example: -``` -$$$.svg +< $$$ -``` +""" +>> -This renders as: +!! Unknown Type -$$$.svg - - - -$$$ +Unknown types render as plain text -Unknown types render as plain text: - -``` -$$$text/unknown +<> -Which renders as: - -$$$text/unknown -Some plain text, which will not be //formatted//. -$$$ +!! Specific Type WikiText A render type can also be specified, causing a particular text rendering to be displayed. For example: -``` -$$$text/vnd.tiddlywiki>text/html +<text/html This is ''some'' wikitext $$$ +""">> -$$$text/vnd.tiddlywiki>text/plain +<text/plain This is ''some'' wikitext $$$ -``` +""">> -Renders as: +!! CSV Table -$$$text/vnd.tiddlywiki>text/html -This is ''some'' wikitext -$$$ - -$$$text/vnd.tiddlywiki>text/plain -This is ''some'' wikitext +<> From cae3d0fa2c9f9d8a5dfbcb9a38ba5403f0a6ec1f Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Mon, 6 Oct 2025 23:25:45 +0800 Subject: [PATCH 08/82] Switch to native support for base64 utf8 (#9253) * Switch to native support for base64 utf8 * Fix error on nodejs --- .../utils/base64-utf8/base64-utf8.module.js | 142 ------------------ .../base64-utf8/base64-utf8.module.min.js | 9 -- .../utils/base64-utf8/tiddlywiki.files | 14 -- core/modules/utils/utils.js | 38 ++++- 4 files changed, 32 insertions(+), 171 deletions(-) delete mode 100644 core/modules/utils/base64-utf8/base64-utf8.module.js delete mode 100644 core/modules/utils/base64-utf8/base64-utf8.module.min.js delete mode 100644 core/modules/utils/base64-utf8/tiddlywiki.files diff --git a/core/modules/utils/base64-utf8/base64-utf8.module.js b/core/modules/utils/base64-utf8/base64-utf8.module.js deleted file mode 100644 index 8bd4e272de..0000000000 --- a/core/modules/utils/base64-utf8/base64-utf8.module.js +++ /dev/null @@ -1,142 +0,0 @@ -// From https://gist.github.com/Nijikokun/5192472 -// -// UTF8 Module -// -// Cleaner and modularized utf-8 encoding and decoding library for javascript. -// -// copyright: MIT -// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com -(function (name, definition, context, dependencies) { - if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); } - else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); } - else { context[name] = definition.apply(context); } -})('utf8', function () { - return { - encode: function (string) { - if (typeof string !== 'string') return string; - else string = string.replace(/\r\n/g, "\n"); - var output = "", i = 0, charCode; - - for (i; i < string.length; i++) { - charCode = string.charCodeAt(i); - - if (charCode < 128) { - output += String.fromCharCode(charCode); - } else if ((charCode > 127) && (charCode < 2048)) { - output += String.fromCharCode((charCode >> 6) | 192); - output += String.fromCharCode((charCode & 63) | 128); - } else if ((charCode > 55295) && (charCode < 57344) && string.length > i+1) { - // Surrogate pair - var hiSurrogate = charCode; - var loSurrogate = string.charCodeAt(i+1); - i++; // Skip the low surrogate on the next loop pass - var codePoint = (((hiSurrogate - 55296) << 10) | (loSurrogate - 56320)) + 65536; - output += String.fromCharCode((codePoint >> 18) | 240); - output += String.fromCharCode(((codePoint >> 12) & 63) | 128); - output += String.fromCharCode(((codePoint >> 6) & 63) | 128); - output += String.fromCharCode((codePoint & 63) | 128); - } else { - // Not a surrogate pair, or a dangling surrogate without its partner that we'll just encode as-is - output += String.fromCharCode((charCode >> 12) | 224); - output += String.fromCharCode(((charCode >> 6) & 63) | 128); - output += String.fromCharCode((charCode & 63) | 128); - } - } - - return output; - }, - - decode: function (string) { - if (typeof string !== 'string') return string; - var output = "", i = 0, charCode = 0; - - while (i < string.length) { - charCode = string.charCodeAt(i); - - if (charCode < 128) { - output += String.fromCharCode(charCode), - i++; - } else if ((charCode > 191) && (charCode < 224)) { - output += String.fromCharCode(((charCode & 31) << 6) | (string.charCodeAt(i + 1) & 63)); - i += 2; - } else if ((charCode > 223) && (charCode < 240)) { - output += String.fromCharCode(((charCode & 15) << 12) | ((string.charCodeAt(i + 1) & 63) << 6) | (string.charCodeAt(i + 2) & 63)); - i += 3; - } else { - var codePoint = ((charCode & 7) << 18) | ((string.charCodeAt(i + 1) & 63) << 12) | ((string.charCodeAt(i + 2) & 63) << 6) | (string.charCodeAt(i + 3) & 63); - // output += String.fromCodePoint(codePoint); // Can't do this because Internet Explorer doesn't have String.fromCodePoint - output += String.fromCharCode(((codePoint - 65536) >> 10) + 55296) + String.fromCharCode(((codePoint - 65536) & 1023) + 56320); // So we do this instead - i += 4; - } - } - - return output; - } - }; -}, this); - -// Base64 Module -// -// Cleaner, modularized and properly scoped base64 encoding and decoding module for strings. -// -// copyright: MIT -// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com -(function (name, definition, context, dependencies) { - if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); } - else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); } - else { context[name] = definition.apply(context); } -})('base64', function (utf8) { - var $this = this; - var $utf8 = utf8 || this.utf8; - var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - return { - encode: function (input) { - if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." }; - if (typeof input !== 'string') return input; - else input = $utf8.encode(input); - var output = "", a, b, c, d, e, f, g, i = 0; - - while (i < input.length) { - a = input.charCodeAt(i++); - b = input.charCodeAt(i++); - c = input.charCodeAt(i++); - d = a >> 2; - e = ((a & 3) << 4) | (b >> 4); - f = ((b & 15) << 2) | (c >> 6); - g = c & 63; - - if (isNaN(b)) f = g = 64; - else if (isNaN(c)) g = 64; - - output += map.charAt(d) + map.charAt(e) + map.charAt(f) + map.charAt(g); - } - - return output; - }, - - decode: function (input) { - if (typeof $utf8 === 'undefined') throw { error: "MissingMethod", message: "UTF8 Module is missing." }; - if (typeof input !== 'string') return input; - else input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - var output = "", a, b, c, d, e, f, g, i = 0; - - while (i < input.length) { - d = map.indexOf(input.charAt(i++)); - e = map.indexOf(input.charAt(i++)); - f = map.indexOf(input.charAt(i++)); - g = map.indexOf(input.charAt(i++)); - - a = (d << 2) | (e >> 4); - b = ((e & 15) << 4) | (f >> 2); - c = ((f & 3) << 6) | g; - - output += String.fromCharCode(a); - if (f != 64) output += String.fromCharCode(b); - if (g != 64) output += String.fromCharCode(c); - } - - return $utf8.decode(output); - } - } -}, this, [ "utf8" ]); \ No newline at end of file diff --git a/core/modules/utils/base64-utf8/base64-utf8.module.min.js b/core/modules/utils/base64-utf8/base64-utf8.module.min.js deleted file mode 100644 index f2f808004a..0000000000 --- a/core/modules/utils/base64-utf8/base64-utf8.module.min.js +++ /dev/null @@ -1,9 +0,0 @@ -// From https://gist.github.com/Nijikokun/5192472 -// -// UTF8 Module -// -// Cleaner and modularized utf-8 encoding and decoding library for javascript. -// -// copyright: MIT -// author: Nijiko Yonskai, @nijikokun, nijikokun@gmail.com -!function(r,e,o,t){void 0!==o.module&&o.module.exports?o.module.exports=e.apply(o):void 0!==o.define&&"function"===o.define&&o.define.amd?define("utf8",[],e):o.utf8=e.apply(o)}(0,function(){return{encode:function(r){if("string"!=typeof r)return r;r=r.replace(/\r\n/g,"\n");for(var e,o="",t=0;t127&&e<2048)o+=String.fromCharCode(e>>6|192),o+=String.fromCharCode(63&e|128);else if(e>55295&&e<57344&&r.length>t+1){var i=e,n=r.charCodeAt(t+1);t++;var d=65536+(i-55296<<10|n-56320);o+=String.fromCharCode(d>>18|240),o+=String.fromCharCode(d>>12&63|128),o+=String.fromCharCode(d>>6&63|128),o+=String.fromCharCode(63&d|128)}else o+=String.fromCharCode(e>>12|224),o+=String.fromCharCode(e>>6&63|128),o+=String.fromCharCode(63&e|128);return o},decode:function(r){if("string"!=typeof r)return r;for(var e="",o=0,t=0;o191&&t<224)e+=String.fromCharCode((31&t)<<6|63&r.charCodeAt(o+1)),o+=2;else if(t>223&&t<240)e+=String.fromCharCode((15&t)<<12|(63&r.charCodeAt(o+1))<<6|63&r.charCodeAt(o+2)),o+=3;else{var i=(7&t)<<18|(63&r.charCodeAt(o+1))<<12|(63&r.charCodeAt(o+2))<<6|63&r.charCodeAt(o+3);e+=String.fromCharCode(55296+(i-65536>>10))+String.fromCharCode(56320+(i-65536&1023)),o+=4}return e}}},this),function(r,e,o,t){if(void 0!==o.module&&o.module.exports){if(t&&o.require)for(var i=0;i>2,f=(3&t)<<4|(i=r.charCodeAt(c++))>>4,a=(15&i)<<2|(n=r.charCodeAt(c++))>>6,h=63&n,isNaN(i)?a=h=64:isNaN(n)&&(h=64),C+=o.charAt(d)+o.charAt(f)+o.charAt(a)+o.charAt(h);return C},decode:function(r){if(void 0===e)throw{error:"MissingMethod",message:"UTF8 Module is missing."};if("string"!=typeof r)return r;r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");for(var t,i,n,d,f,a,h="",C=0;C>4,i=(15&d)<<4|(f=o.indexOf(r.charAt(C++)))>>2,n=(3&f)<<6|(a=o.indexOf(r.charAt(C++))),h+=String.fromCharCode(t),64!=f&&(h+=String.fromCharCode(i)),64!=a&&(h+=String.fromCharCode(n));return e.decode(h)}}},this,["utf8"]); \ No newline at end of file diff --git a/core/modules/utils/base64-utf8/tiddlywiki.files b/core/modules/utils/base64-utf8/tiddlywiki.files deleted file mode 100644 index b12e7dfb92..0000000000 --- a/core/modules/utils/base64-utf8/tiddlywiki.files +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tiddlers": [ - { - "file": "base64-utf8.module.min.js", - "fields": { - "type": "application/javascript", - "title": "$:/core/modules/utils/base64-utf8/base64-utf8.module.js", - "module-type": "library" - }, - "prefix": "(function(){", - "suffix": "}).call(exports);" - } - ] -} diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index a228f91d43..93abf00063 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -9,8 +9,6 @@ Various static utility functions. "use strict"; -var base64utf8 = require("$:/core/modules/utils/base64-utf8/base64-utf8.module.js"); - /* Display a message, in colour if we're on a terminal */ @@ -842,22 +840,50 @@ if(typeof window !== 'undefined') { } } +exports.base64ToBytes = function(base64) { + const binString = exports.atob(base64); + return Uint8Array.from(binString, (m) => m.codePointAt(0)); +}; + +exports.bytesToBase64 = function(bytes) { + const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join(""); + return exports.btoa(binString); +}; + +exports.base64EncodeUtf8 = function(str) { + if ($tw.browser) { + return exports.bytesToBase64(new TextEncoder().encode(str)); + } else { + const buff = Buffer.from(str, "utf-8"); + return buff.toString("base64"); + } +}; + +exports.base64DecodeUtf8 = function(str) { + if ($tw.browser) { + return new TextDecoder().decode(exports.base64ToBytes(str)); + } else { + const buff = Buffer.from(str, "base64"); + return buff.toString("utf-8"); + } +}; + /* Decode a base64 string */ exports.base64Decode = function(string64,binary,urlsafe) { - var encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64; + const encoded = urlsafe ? string64.replace(/_/g,'/').replace(/-/g,'+') : string64; if(binary) return exports.atob(encoded) - else return base64utf8.base64.decode.call(base64utf8,encoded); + else return exports.base64DecodeUtf8(encoded); }; /* Encode a string to base64 */ exports.base64Encode = function(string64,binary,urlsafe) { - var encoded; + let encoded; if(binary) encoded = exports.btoa(string64); - else encoded = base64utf8.base64.encode.call(base64utf8,string64); + else encoded = exports.base64EncodeUtf8(string64); if(urlsafe) { encoded = encoded.replace(/\+/g,'-').replace(/\//g,'_'); } From e753851d497e431475206e67bca93ca50eb5548c Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Mon, 6 Oct 2025 17:27:11 +0200 Subject: [PATCH 09/82] Add output directory to eslint ignore configuration (#9317) --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index d29d275699..af9bac3dd9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -45,6 +45,7 @@ export default defineConfig([{ "plugins/tiddlywiki/*/files/", "eslint.config.mjs", "playwright.config.js", + "**/output/**" ], }, From ee230978169bb4d6b561ad254b1b0bf15f64c6ce Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Mon, 6 Oct 2025 23:40:20 +0800 Subject: [PATCH 10/82] Set modal's mask-closable attribute to yes by default (#9313) * Set modal's mask-closable attribute to yes by default * Make backdrop closable only when field value is yes or empty --- core/modules/utils/dom/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/utils/dom/modal.js b/core/modules/utils/dom/modal.js index 4e407499eb..a7b538ec3a 100644 --- a/core/modules/utils/dom/modal.js +++ b/core/modules/utils/dom/modal.js @@ -210,7 +210,7 @@ Modal.prototype.display = function(title,options) { bodyWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false); footerWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false); // Whether to close the modal dialog when the mask (area outside the modal) is clicked - if(tiddler.fields && (tiddler.fields["mask-closable"] === "yes" || tiddler.fields["mask-closable"] === "true")) { + if(tiddler.fields && (tiddler.fields["mask-closable"] === "yes" || tiddler.fields["mask-closable"] === "true" || tiddler.fields["mask-closable"] === "" || "mask-closable" in tiddler.fields === false)) { modalBackdrop.addEventListener("click",closeHandler,false); } // Set the initial styles for the message From a36356a07d4762074fa458885e0a713f7ea012be Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Mon, 6 Oct 2025 18:00:13 +0200 Subject: [PATCH 11/82] Adds info tiddlers for viewport dimensions (#9260) * feat: added info tiddlers for viewport dimensions * feat: multi window support for dimensions info tiddlers * refactor: introduce standalone eventbus and refactor for ES2017 * docs: extended docs for InfoMechanism to cover new additions --- core/modules/info/dimensions.js | 86 +++++++++++++++++++ core/modules/startup/eventbus.js | 46 ++++++++++ core/modules/startup/info.js | 14 ++- core/modules/startup/windows.js | 2 + .../tiddlers/mechanisms/InfoMechanism.tid | 27 +++++- 5 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 core/modules/info/dimensions.js create mode 100644 core/modules/startup/eventbus.js diff --git a/core/modules/info/dimensions.js b/core/modules/info/dimensions.js new file mode 100644 index 0000000000..48458dd0f0 --- /dev/null +++ b/core/modules/info/dimensions.js @@ -0,0 +1,86 @@ +/*\ +title: $:/core/modules/info/windowdimensions.js +type: application/javascript +module-type: info +\*/ + +exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) { + if(!$tw.browser) { + return []; + } + + class WindowDimensionsTracker { + constructor(updateCallback) { + this.updateCallback = updateCallback; + this.resizeHandlers = new Map(); + this.dimensionsInfo = [ + ["outer/width", win => win.outerWidth], + ["outer/height", win => win.outerHeight], + ["inner/width", win => win.innerWidth], + ["inner/height", win => win.innerHeight], + ["client/width", win => win.document.documentElement.clientWidth], + ["client/height", win => win.document.documentElement.clientHeight] + ]; + } + + buildTiddlers(win,windowId) { + const prefix = `$:/info/browser/window/${windowId}/`; + return this.dimensionsInfo.map(([suffix, getter]) => ({ + title: prefix + suffix, + text: String(getter(win)) + })); + } + + clearTiddlers(windowId) { + const prefix = `$:/info/browser/window/${windowId}/`, + deletions = this.dimensionsInfo.map(([suffix]) => prefix + suffix); + this.updateCallback([], deletions); + } + + getUpdateHandler(win,windowId) { + let scheduled = false; + return () => { + if(!scheduled) { + scheduled = true; + requestAnimationFrame(() => { + this.updateCallback(this.buildTiddlers(win,windowId), []); + scheduled = false; + }); + } + }; + } + + trackWindow(win,windowId) { + const handler = this.getUpdateHandler(win, windowId); + handler(); // initial update + win.addEventListener("resize",handler,{passive:true}); + this.resizeHandlers.set(windowId,{win, handler}); + } + + untrackWindow(windowId) { + const entry = this.resizeHandlers.get(windowId); + if(entry) { + entry.win.removeEventListener("resize", entry.handler); + this.resizeHandlers.delete(windowId); + } + this.clearTiddlers(windowId); + } + } + + const tracker = new WindowDimensionsTracker(updateInfoTiddlersCallback); + + // Track main window + tracker.trackWindow(window,"system/main"); + + // Hook into event bus for user windows + if($tw.eventBus) { + $tw.eventBus.on("window:opened", ({window: win, windowID}) => { + tracker.trackWindow(win, "user/" + windowID); + }); + $tw.eventBus.on("window:closed", ({windowID}) => { + tracker.untrackWindow("user/" + windowID); + }); + } + + return []; +}; diff --git a/core/modules/startup/eventbus.js b/core/modules/startup/eventbus.js new file mode 100644 index 0000000000..0ad69fb27f --- /dev/null +++ b/core/modules/startup/eventbus.js @@ -0,0 +1,46 @@ +/*\ +title: $:/core/modules/startup/eventbus.js +type: application/javascript +module-type: startup + +Event bus for cross module communication +\*/ + +exports.name = "eventbus"; +exports.platforms = ["browser"]; +exports.before = ["windows"]; +exports.synchronous = true; + +$tw.eventBus = { + listenersMap: new Map(), + + on(event,handler) { + if(!this.listenersMap.has(event)) { + this.listenersMap.set(event,new Set()); + } + const listeners = this.listenersMap.get(event); + listeners.add(handler); + }, + + off(event,handler) { + const listeners = this.listenersMap.get(event); + if(listeners) { + listeners.delete(handler); + } + }, + + once(event,handler) { + const wrapper = (...args) => { + handler(...args); + this.off(event, wrapper); + }; + this.on(event, wrapper); + }, + + emit(event,data) { + const listeners = this.listenersMap.get(event); + if(listeners) { + listeners.forEach(fn => fn(data)); + } + } +}; diff --git a/core/modules/startup/info.js b/core/modules/startup/info.js index a5767ed341..dc65557c7d 100644 --- a/core/modules/startup/info.js +++ b/core/modules/startup/info.js @@ -19,11 +19,17 @@ var TITLE_INFO_PLUGIN = "$:/temp/info-plugin"; exports.startup = function() { // Function to bake the info plugin with new tiddlers - var updateInfoPlugin = function(tiddlerFieldsArray) { + // additions: array of tiddler field objects + // removals: array of titles to remove + var updateInfoPlugin = function(additions = [], removals = []) { // Get the existing tiddlers var json = $tw.wiki.getTiddlerData(TITLE_INFO_PLUGIN,{tiddlers: {}}); - // Add the new ones - $tw.utils.each(tiddlerFieldsArray,function(fields) { + $tw.utils.each(removals,function(title) { + if(json.tiddlers[title]) { + delete json.tiddlers[title]; + } + }); + $tw.utils.each(additions,function(fields) { if(fields && fields.title) { json.tiddlers[fields.title] = fields; } @@ -47,7 +53,7 @@ exports.startup = function() { } }); updateInfoPlugin(tiddlerFieldsArray); - var changes = $tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]); + $tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]); $tw.wiki.registerPluginTiddlers("info",[TITLE_INFO_PLUGIN]); $tw.wiki.unpackPluginTiddlers(); }; diff --git a/core/modules/startup/windows.js b/core/modules/startup/windows.js index 8506a9866a..39a2f59d62 100644 --- a/core/modules/startup/windows.js +++ b/core/modules/startup/windows.js @@ -56,9 +56,11 @@ exports.startup = function() { srcDocument.write(""); srcDocument.close(); srcDocument.title = windowTitle; + $tw.eventBus.emit("window:opened",{windowID, window: srcWindow}); srcWindow.addEventListener("beforeunload",function(event) { delete $tw.windows[windowID]; $tw.wiki.removeEventListener("change",refreshHandler); + $tw.eventBus.emit("window:closed",{windowID}); },false); // Set up the styles var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{ diff --git a/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid b/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid index 040dd7bd23..e22ff238a1 100644 --- a/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid +++ b/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid @@ -1,5 +1,5 @@ created: 20140720164948099 -modified: 20201124185829706 +modified: 20250819181815032 tags: Mechanisms title: InfoMechanism type: text/vnd.tiddlywiki @@ -19,6 +19,7 @@ System tiddlers in the namespace `$:/info/` are used to expose information about |[[$:/info/browser/language]] |<<.from-version "5.1.20">> Language as reported by browser (note that some browsers report two character codes such as `en` while others report full codes such as `en-GB`) | |[[$:/info/browser/screen/width]] |Screen width in pixels | |[[$:/info/browser/screen/height]] |Screen height in pixels | +|`$:/info/browser/window/*` |<<.from-version "5.4.0">> Tiddlers reporting window dimensions, updated when the windows are resized | |[[$:/info/node]] |Running under [[Node.js]]? ("yes" or "no") | |[[$:/info/url/full]] |<<.from-version "5.1.14">> Full URL of wiki (eg, ''<>'') | |[[$:/info/url/host]] |<<.from-version "5.1.14">> Host portion of URL of wiki (eg, ''<>'') | @@ -29,3 +30,27 @@ System tiddlers in the namespace `$:/info/` are used to expose information about |[[$:/info/url/protocol]] |<<.from-version "5.1.14">> Protocol portion of URL of wiki (eg, ''<>'') | |[[$:/info/url/search]] |<<.from-version "5.1.14">> Search portion of URL of wiki (eg, ''<>'') | |[[$:/info/darkmode]] |<<.from-version "5.1.23">> Is dark mode enabled? ("yes" or "no") | + +! Main Window Dimension Tiddlers + +<<.from-version "5.4.0">> These tiddlers reports the dimensions of the main ~TiddlyWiki window and are updated automatically whenever the main window is resized. + +|!Title |!Description | +|[[$:/info/browser/window/system/main/outer/width]] |Full browser window including chrome, tabs, toolbars | +|[[$:/info/browser/window/system/main/outer/height]] |Full browser window including chrome, tabs, toolbars | +|[[$:/info/browser/window/system/main/inner/width]] |Viewport width including scrollbars | +|[[$:/info/browser/window/system/main/inner/height]] |Viewport height including scrollbars | +|[[$:/info/browser/window/system/main/client/width]] |Content width excluding scrollbars | +|[[$:/info/browser/window/system/main/client/height]] |Content height excluding scrollbars | + +! User-Created Window Dimension Tiddlers + +<<.from-version "5.4.0">> These tiddler reports the dimensions of additional windows opened via [[tm-open-window|WidgetMessage: tm-open-window]]. The windowID used when opening the window is used to identify the corresponding info tiddlers. These tiddlers are updated automatically whenever the main window is resized. + +|!Title |!Description | +|`$:/info/browser/window/user//outer/width` | Full browser window including chrome, tabs, toolbars | +|`$:/info/browser/window/user//outer/height` | Full browser window including chrome, tabs, toolbars | +|`$:/info/browser/window/user//inner/width` |Viewport width including scrollbars | +|`$:/info/browser/window/user//inner/height` |Viewport height including scrollbars | +|`$:/info/browser/window/user//client/width` |Content width excluding scrollbars | +|`$:/info/browser/window/user//client/height` |Content height excluding scrollbars | From 865f36a6f51d7130fee49892405f1f2c90b455eb Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Mon, 6 Oct 2025 18:12:08 +0200 Subject: [PATCH 12/82] Removes deprecated attributes from $eventcatcher (#9259) * fix: removed deprecated attributes * docs: update for eventcatcher --- core/modules/widgets/eventcatcher.js | 5 +---- editions/tw5.com/tiddlers/widgets/EventCatcherWidget.tid | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/core/modules/widgets/eventcatcher.js b/core/modules/widgets/eventcatcher.js index 70dc992021..aac6519a48 100644 --- a/core/modules/widgets/eventcatcher.js +++ b/core/modules/widgets/eventcatcher.js @@ -44,7 +44,7 @@ EventWidget.prototype.render = function(parent,nextSibling) { domNode.addEventListener(type,function(event) { var selector = self.getAttribute("selector"), matchSelector = self.getAttribute("matchSelector"), - actions = self.getAttribute("$"+type) || self.getAttribute("actions-"+type), + actions = self.getAttribute("$"+type), stopPropagation = self.getAttribute("stopPropagation","onaction"), selectedNode = event.target, selectedNodeRect, @@ -122,9 +122,6 @@ EventWidget.prototype.execute = function() { self.types.push(key.slice(1)); } }); - if(!this.types.length) { - this.types = this.getAttribute("events","").split(" "); - } this.elementTag = this.getAttribute("tag"); // Make child widgets this.makeChildWidgets(); diff --git a/editions/tw5.com/tiddlers/widgets/EventCatcherWidget.tid b/editions/tw5.com/tiddlers/widgets/EventCatcherWidget.tid index a253a0e6c3..41a1420699 100644 --- a/editions/tw5.com/tiddlers/widgets/EventCatcherWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/EventCatcherWidget.tid @@ -1,5 +1,5 @@ created: 20201123113532200 -modified: 20221012194222875 +modified: 20250817151316712 tags: Widgets TriggeringWidgets title: EventCatcherWidget type: text/vnd.tiddlywiki @@ -32,10 +32,10 @@ The content of the `<$eventcatcher>` widget is displayed normally. |tag |Optional. The HTML element the widget creates to capture the events, defaults to:
» `span` when parsed in inline-mode
» `div` when parsed in block-mode | |class |Optional. A CSS class name (or names) to be assigned to the widget HTML element | |stopPropagation |<<.from-version "5.2.0">> Optional. Set to "always" to always stop event propagation even if there are no corresponding actions to invoke, "never" to never stop event propagation, or the default value "onaction" with which event propagation is only stopped if there are corresponding actions that are invoked. | -|events |//(deprecated – see below)// Space separated list of JavaScript events to be trapped, for example "click" or "click dblclick" | -|actions-* |//(deprecated – see below)// Action strings to be invoked when a matching event is trapped. Each event is mapped to an action attribute name of the form actions-event where event represents the type of the event. For example: `actions-click` or `actions-dblclick` | +|//events// |//(removed – see below)// Space separated list of JavaScript events to be trapped, for example "click" or "click dblclick" | +|//actions-*// |//(removed – see below)// Action strings to be invoked when a matching event is trapped. Each event is mapped to an action attribute name of the form actions-event where event represents the type of the event. For example: `actions-click` or `actions-dblclick` | -<<.from-version "5.2.0">> Note that the attributes `events` and `actions-*` are no longer needed. Instead you can use attributes starting with $ where the attribute name (excluding the $) specifies the name of the event and the value specifies the action string to be invoked. If any attributes with the prefix $ are present then the `types` attribute is ignored. +<<.from-version "5.4.0">> The attributes `events` and `actions-*` have been removed as they are no longer needed. Instead you can use attributes starting with $ where the attribute name (excluding the $) specifies the name of the event and the value specifies the action string to be invoked. If any attributes with the prefix $ are present then the `types` attribute is ignored. ! Variables From 2405e6530836ba5b18ed79cad61eaa1a50d3e556 Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Tue, 7 Oct 2025 00:26:34 +0800 Subject: [PATCH 13/82] Remove support for IE in range widget (#9275) --- core/modules/widgets/range.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/core/modules/widgets/range.js b/core/modules/widgets/range.js index 8d039b1f19..7c2d60c45c 100644 --- a/core/modules/widgets/range.js +++ b/core/modules/widgets/range.js @@ -94,8 +94,6 @@ RangeWidget.prototype.getActionVariables = function(options) { // actionsStart RangeWidget.prototype.handleMouseDownEvent = function(event) { - this.mouseDown = true; // TODO remove once IE is gone. - this.startValue = this.inputDomNode.value; // TODO remove this line once IE is gone! this.handleEvent(event); // Trigger actions if(this.actionsMouseDown) { @@ -106,26 +104,16 @@ RangeWidget.prototype.handleMouseDownEvent = function(event) { // actionsStop RangeWidget.prototype.handleMouseUpEvent = function(event) { - this.mouseDown = false; // TODO remove once IE is gone. this.handleEvent(event); // Trigger actions if(this.actionsMouseUp) { var variables = this.getActionVariables() this.invokeActionString(this.actionsMouseUp,this,event,variables); } - // TODO remove the following if() once IE is gone! - if ($tw.browser.isIE) { - if (this.startValue !== this.inputDomNode.value) { - this.handleChangeEvent(event); - this.startValue = this.inputDomNode.value; - } - } } RangeWidget.prototype.handleChangeEvent = function(event) { - if (this.mouseDown) { // TODO refactor this function once IE is gone. - this.handleInputEvent(event); - } + this.handleInputEvent(event); }; RangeWidget.prototype.handleInputEvent = function(event) { @@ -152,8 +140,6 @@ RangeWidget.prototype.handleEvent = function(event) { Compute the internal state of the widget */ RangeWidget.prototype.execute = function() { - // TODO remove the next 1 lines once IE is gone! - this.mouseUp = true; // Needed for IE10 // Get the parameters from the attributes this.tiddlerTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler")); this.tiddlerField = this.getAttribute("field","text"); From 15ba415a45dceac106455e0e8cbd037aab549c0d Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Tue, 7 Oct 2025 00:28:29 +0800 Subject: [PATCH 14/82] Use s instead of strike (#9131) * Use s instead of strike * Update docs --- core/modules/parsers/wikiparser/rules/emphasis/strikethrough.js | 2 +- editions/tw5.com/tiddlers/wikitext/Formatting in WikiText.tid | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modules/parsers/wikiparser/rules/emphasis/strikethrough.js b/core/modules/parsers/wikiparser/rules/emphasis/strikethrough.js index 24380ed115..3c05bbe2c1 100644 --- a/core/modules/parsers/wikiparser/rules/emphasis/strikethrough.js +++ b/core/modules/parsers/wikiparser/rules/emphasis/strikethrough.js @@ -39,7 +39,7 @@ exports.parse = function() { // Return the classed span return [{ type: "element", - tag: "strike", + tag: "s", children: tree }]; }; diff --git a/editions/tw5.com/tiddlers/wikitext/Formatting in WikiText.tid b/editions/tw5.com/tiddlers/wikitext/Formatting in WikiText.tid index 7ffb4e4974..91a3700f65 100644 --- a/editions/tw5.com/tiddlers/wikitext/Formatting in WikiText.tid +++ b/editions/tw5.com/tiddlers/wikitext/Formatting in WikiText.tid @@ -38,7 +38,7 @@ The full list of KeyboardShortcuts can be found in the $:/ControlPanel -> ''Keyb |Double underscores are used for `__underlined text__`|Double underscores are used for `underlined text` | |Double circumflex accents are used for `^^superscripted^^` text |Double circumflex accents are used for `superscripted` text | |Double commas are used for `,,subscripted,,` text |Double commas are used for `subscripted` text | -|Double tilde signs are used for `~~strikethrough~~` text |Double tilde signs are used for `strikethrough` text | +|Double tilde signs are used for `~~strikethrough~~` text |Double tilde signs are used for `strikethrough` text | |Single backticks are used for ```code` `` |Single backticks are used for `code` | |Double @ characters are used to create a `@@highlight@@` |Double @ characters are used to create a `highlighted` | From dc7f2a57bb60188cf428d6304471427fa06c9e84 Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Tue, 7 Oct 2025 18:55:26 +0800 Subject: [PATCH 15/82] Use currentColor to style svg (#9316) * Replace fill: with currentColor * Remove fill from wikitext * Replace fill property with color property for svg selectors * Further remove fill property * Replace more fill properties * Replace fill in seamless theme * Replace fill in plugins --- core/ui/EditTemplate/tags.tid | 6 +- core/ui/ViewTemplate/title.tid | 2 +- core/wiki/macros/tag.tid | 1 - core/wiki/macros/thumbnails.tid | 2 +- plugins/tiddlywiki/comments/styles.tid | 2 +- plugins/tiddlywiki/menubar/styles.tid | 16 +-- plugins/tiddlywiki/text-slicer/styles.tid | 2 +- .../tour/simplified-tiddler-with-tags.tid | 1 - plugins/tiddlywiki/tour/styles.tid | 5 +- plugins/tiddlywiki/upgrade/styles.tid | 4 +- themes/tiddlywiki/seamless/base.tid | 2 +- themes/tiddlywiki/vanilla/base.tid | 108 ++++++++---------- 12 files changed, 60 insertions(+), 91 deletions(-) diff --git a/core/ui/EditTemplate/tags.tid b/core/ui/EditTemplate/tags.tid index bbd3d301eb..298120606d 100644 --- a/core/ui/EditTemplate/tags.tid +++ b/core/ui/EditTemplate/tags.tid @@ -17,13 +17,11 @@ tags: $:/tags/EditTemplate <$let backgroundColor=<> > > - style=`color:$(foregroundColor)$; fill:$(foregroundColor)$; background-color:$(backgroundColor)$;` + style=`color:$(foregroundColor)$; background-color:$(backgroundColor)$;` > <$transclude tiddler=<>/> <$view field="title" format="text"/> - <$button class="tc-btn-invisible tc-remove-tag-button" - style.fill=<> - > + <$button class="tc-btn-invisible tc-remove-tag-button"> <$action-listops $tiddler=<> $field=<> $subfilter="-[{!!title}]"/> {{$:/core/images/close-button}} diff --git a/core/ui/ViewTemplate/title.tid b/core/ui/ViewTemplate/title.tid index b2cf1bd8e9..56b92efe84 100644 --- a/core/ui/ViewTemplate/title.tid +++ b/core/ui/ViewTemplate/title.tid @@ -2,7 +2,7 @@ title: $:/core/ui/ViewTemplate/title tags: $:/tags/ViewTemplate \whitespace trim -\define title-styles() fill:$(foregroundColor)$; +\define title-styles() color:$(foregroundColor)$;
diff --git a/core/wiki/macros/tag.tid b/core/wiki/macros/tag.tid index 2c539b42ef..1424036028 100644 --- a/core/wiki/macros/tag.tid +++ b/core/wiki/macros/tag.tid @@ -3,7 +3,6 @@ tags: $:/tags/Macro \define tag-pill-styles() background-color:$(backgroundColor)$; -fill:$(foregroundColor)$; color:$(foregroundColor)$; \end diff --git a/core/wiki/macros/thumbnails.tid b/core/wiki/macros/thumbnails.tid index 213b6520c5..83e03b906c 100644 --- a/core/wiki/macros/thumbnails.tid +++ b/core/wiki/macros/thumbnails.tid @@ -21,7 +21,7 @@ tags: $:/tags/Macro style="width:$width$px;height:$height$px;background-color:$background-color$;" >
$icon$
$caption$
\end diff --git a/plugins/tiddlywiki/comments/styles.tid b/plugins/tiddlywiki/comments/styles.tid index ed3cf1ddff..62b34c764d 100644 --- a/plugins/tiddlywiki/comments/styles.tid +++ b/plugins/tiddlywiki/comments/styles.tid @@ -25,7 +25,7 @@ tags: [[$:/tags/Stylesheet]] } .tc-comment-button button svg { - fill: <>; + color: <>; height: 2em; width: 2em; } diff --git a/plugins/tiddlywiki/menubar/styles.tid b/plugins/tiddlywiki/menubar/styles.tid index 26a96be01e..39ec6c6267 100644 --- a/plugins/tiddlywiki/menubar/styles.tid +++ b/plugins/tiddlywiki/menubar/styles.tid @@ -87,8 +87,6 @@ nav.tc-menubar li.tc-menubar-item > button { font-weight: 700; color: <>; color: <>; - fill: <>; - fill: <>; text-decoration: none; padding: 0.5em; margin: 0; @@ -105,16 +103,14 @@ nav.tc-menubar li.tc-menubar-item > button.tc-selected { background: <>; color: <>; color: <>; - fill: <>; - fill: <>; } nav.tc-menubar li.tc-menubar-item svg { transition: none; width: 1em; height: 1em; - fill: <>; - fill: <>; + color: <>; + color: <>; } nav.tc-menubar li.tc-menubar-item .tc-menubar-dropdown-arrow svg { @@ -124,8 +120,8 @@ nav.tc-menubar li.tc-menubar-item .tc-menubar-dropdown-arrow svg { nav.tc-menubar li.tc-menubar-item > a.tc-selected svg, nav.tc-menubar li.tc-menubar-item > button.tc-selected svg { - fill: <>; - fill: <>; + color: <>; + color: <>; } nav.tc-menubar li.tc-menubar-item > a:hover, @@ -135,8 +131,6 @@ nav.tc-menubar li.tc-menubar-item > button:hover { background: <>; color: <>; color: <>; - fill: <>; - fill: <>; border-radius: 0; text-decoration: none; } @@ -148,8 +142,6 @@ nav.tc-menubar li.tc-menubar-item > button:active { background: <>; color: <>; color: <>; - fill: <>; - fill: <>; border-radius: 0; text-decoration: none; } diff --git a/plugins/tiddlywiki/text-slicer/styles.tid b/plugins/tiddlywiki/text-slicer/styles.tid index 370b4d6845..328486c812 100644 --- a/plugins/tiddlywiki/text-slicer/styles.tid +++ b/plugins/tiddlywiki/text-slicer/styles.tid @@ -96,7 +96,7 @@ div.tc-edit-template-document-tiddler-heading a:hover { .tc-view-template-document-tiddler-heading-icon svg, .tc-edit-template-document-tiddler-heading-icon svg { - fill: <>; + color: <>; } .tc-view-template-document-tiddler { diff --git a/plugins/tiddlywiki/tour/simplified-tiddler-with-tags.tid b/plugins/tiddlywiki/tour/simplified-tiddler-with-tags.tid index c2c14a7b05..c5f27662c3 100644 --- a/plugins/tiddlywiki/tour/simplified-tiddler-with-tags.tid +++ b/plugins/tiddlywiki/tour/simplified-tiddler-with-tags.tid @@ -3,7 +3,6 @@ title: $:/plugins/tiddlywiki/tour/simplified-tiddler-with-tags \whitespace trim \define tag-pill-styles() background-color:$(backgroundColor)$; -fill:$(foregroundColor)$; color:$(foregroundColor)$; \end diff --git a/plugins/tiddlywiki/tour/styles.tid b/plugins/tiddlywiki/tour/styles.tid index 7bb434dabe..1a9bf871bf 100644 --- a/plugins/tiddlywiki/tour/styles.tid +++ b/plugins/tiddlywiki/tour/styles.tid @@ -66,7 +66,6 @@ tags: $:/tags/Stylesheet .tc-tour-panel-controls .tc-tour-panel-list-button.tc-selected { color: <>; - fill: <>; background: <>; } @@ -104,7 +103,6 @@ tags: $:/tags/Stylesheet border-radius: 1em; background: <>; color: <>; - fill: <>; text-transform: uppercase; } @@ -167,7 +165,7 @@ tags: $:/tags/Stylesheet } .tc-tour-task svg { - fill: <>; + color: <>; vertical-align: middle; width: 1.2em; height: 1.2em; @@ -240,7 +238,6 @@ tags: $:/tags/Stylesheet .tc-tour-settings-tour-step-launch-button:hover { background: <>; color: <>; - fill: <>; } .tc-tour-settings-tour-step-heading-step-number { diff --git a/plugins/tiddlywiki/upgrade/styles.tid b/plugins/tiddlywiki/upgrade/styles.tid index 944c6a9183..d6b880be1c 100644 --- a/plugins/tiddlywiki/upgrade/styles.tid +++ b/plugins/tiddlywiki/upgrade/styles.tid @@ -20,11 +20,11 @@ tags: $:/tags/Stylesheet .tc-upgrade-wizard svg.tc-image-download-button { width: 14em; height: 14em; - fill: <>; + color: <>; } .tc-upgrade-wizard:hover svg.tc-image-download-button { - fill: <>; + color: <>; } .tc-upgrade-wizard svg .tc-image-download-button-ring { diff --git a/themes/tiddlywiki/seamless/base.tid b/themes/tiddlywiki/seamless/base.tid index 21656aa9e9..0f07c35295 100644 --- a/themes/tiddlywiki/seamless/base.tid +++ b/themes/tiddlywiki/seamless/base.tid @@ -94,7 +94,7 @@ html:-webkit-full-screen { /* Adjust the colour of the page controls */ body.tc-body .tc-page-controls svg { - fill: <>; + color: <>; } /* Adjust the colour of the sidebar selected tabs */ diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index 7cd6f6e5b6..a7cc9e5498 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -93,7 +93,6 @@ button:-moz-focusring, input:-moz-focusring, textarea:-moz-focusring, select:-mo html button { line-height: 1.2; color: <>; - fill: <>; background: <>; border-color: <>; cursor: pointer; @@ -104,10 +103,6 @@ button:disabled { color: <>; } -button:disabled svg { - fill: <>; -} - /* ** Basic element styles */ @@ -130,7 +125,7 @@ body.tc-body { <> color: <>; background-color: <>; - fill: <>; + fill: currentColor; } <>; + color: <>; } /* @@ -608,7 +603,6 @@ button svg, button img, label svg, label img { border: none; cursor: pointer; color: <>; - fill: <>; } button:disabled.tc-btn-invisible { @@ -634,10 +628,6 @@ html body.tc-body .tc-btn-boxed svg { color: <>; } -html body.tc-body .tc-btn-boxed:hover svg { - fill: <>; -} - .tc-btn-rounded { font-size: 0.5em; line-height: 2; @@ -651,7 +641,7 @@ html body.tc-body .tc-btn-boxed:hover svg { html body.tc-body .tc-btn-rounded svg { font-size: 1.6666em; - fill: <>; + color: <>; } .tc-btn-rounded:hover { @@ -660,14 +650,10 @@ html body.tc-body .tc-btn-rounded svg { color: <>; } -html body.tc-body .tc-btn-rounded:hover svg { - fill: <>; -} - .tc-btn-icon svg { height: 1em; width: 1em; - fill: <>; + color: <>; } @@ -691,7 +677,6 @@ html body.tc-body .tc-btn-rounded:hover svg { margin: 4px 8px 4px 8px; background: <>; color: <>; - fill: <>; border: none; border-radius: 2px; font-size: 1.2em; @@ -704,7 +689,6 @@ html body.tc-body .tc-btn-rounded:hover svg { height: 2em; width: 2em; vertical-align: middle; - fill: <>; } .tc-primary-btn { @@ -717,7 +701,6 @@ html body.tc-body .tc-btn-rounded:hover svg { .tc-sidebar-lists button { color: <>; - fill: <>; } .tc-sidebar-lists button.tc-btn-mini { @@ -758,11 +741,11 @@ button svg.tc-image-button, button .tc-image-button img { .tc-unfold-banner svg, .tc-fold-banner svg { height: 0.75em; - fill: <>; + color: <>; } .tc-unfold-banner:hover svg, .tc-fold-banner:hover svg { - fill: <>; + color: <>; } .tc-fold-banner { @@ -917,11 +900,11 @@ button.tc-btn-invisible.tc-remove-tag-button { } .tc-topbar svg { - fill: <>; + color: <>; } .tc-topbar button:hover svg { - fill: <>; + color: <>; } @media (max-width: <>) { @@ -934,7 +917,6 @@ button.tc-btn-invisible.tc-remove-tag-button { .tc-sidebar-header { color: <>; - fill: <>; } .tc-sidebar-header .tc-title a.tc-tiddlylink-resolves { @@ -984,11 +966,11 @@ button.tc-btn-invisible.tc-remove-tag-button { } .tc-page-controls svg { - fill: <>; + color: <>; } .tc-page-controls button:hover svg, .tc-page-controls a:hover svg { - fill: <>; + color: <>; } .tc-sidebar-lists .tc-menu-list-item { @@ -1274,7 +1256,7 @@ button.tc-btn-invisible.tc-remove-tag-button { .tc-tiddler-controls button svg, .tc-tiddler-controls button img, .tc-search button svg, .tc-search a svg { - fill: <>; + color: <>; } .tc-tiddler-controls button svg, .tc-tiddler-controls button img { @@ -1289,12 +1271,12 @@ button.tc-btn-invisible.tc-remove-tag-button { .tc-tiddler-controls button.tc-selected svg, .tc-page-controls button.tc-selected svg { - fill: <>; + color: <>; } .tc-tiddler-controls button.tc-btn-invisible:hover svg, .tc-search button:hover svg, .tc-search a:hover svg { - fill: <>; + color: <>; } @media print { @@ -1422,7 +1404,6 @@ html body.tc-body.tc-single-tiddler-window { vertical-align: middle; background-color: <>; color: <>; - fill: <>; border-radius: 4px; padding: 3px; margin: 2px 0 2px 4px; @@ -1453,7 +1434,6 @@ html body.tc-body.tc-single-tiddler-window { .tc-editor-toolbar button:hover { background-color: <>; - fill: <>; color: <>; } @@ -1534,43 +1514,43 @@ html body.tc-body.tc-single-tiddler-window { */ .tc-page-controls svg.tc-image-new-button { - fill: <>; + color: <>; } .tc-page-controls svg.tc-image-options-button { - fill: <>; + color: <>; } .tc-page-controls svg.tc-image-save-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-info-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-edit-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-close-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-delete-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-cancel-button { - fill: <>; + color: <>; } .tc-tiddler-controls button svg.tc-image-done-button { - fill: <>; + color: <>; } .tc-page-controls svg.tc-image-layout-button { - fill: <>; + color: <>; } /* @@ -1710,7 +1690,7 @@ html body.tc-body.tc-single-tiddler-window { .tc-edit-field-remove svg { height: 1em; width: 1em; - fill: <>; + color: <>; vertical-align: middle; } @@ -1824,7 +1804,7 @@ html body.tc-body.tc-single-tiddler-window { .tc-btn-dropdown svg, .tc-btn-dropdown img { height: 1em; width: 1em; - fill: <>; + color: <>; } .tc-drop-down-wrapper { @@ -1847,15 +1827,15 @@ html body.tc-body.tc-single-tiddler-window { } .tc-drop-down button svg, .tc-drop-down a svg { - fill: <>; + color: <>; } .tc-drop-down button:disabled svg { - fill: <>; + color: <>; } .tc-drop-down button.tc-btn-invisible:hover svg { - fill: <>; + color: <>; } .tc-drop-down .tc-drop-down-info { @@ -2342,7 +2322,6 @@ html body.tc-body.tc-single-tiddler-window { .tc-manager-list-item-heading-selected { font-weight: bold; color: <>; - fill: <>; background-color: <>; } @@ -2469,7 +2448,7 @@ html body.tc-body.tc-single-tiddler-window { } .tc-alert-toolbar svg { - fill: <>; + color: <>; } .tc-alert-subtitle { @@ -2526,14 +2505,12 @@ html body.tc-body.tc-single-tiddler-window { border-bottom: none; background: <>; color: <>; - fill: <>; } .tc-drafts-list a:hover { text-decoration: none; background: <>; color: <>; - fill: <>; } .tc-drafts-list a svg { @@ -2562,13 +2539,16 @@ html body.tc-body.tc-single-tiddler-window { display: flex; text-shadow: none; border: 1px solid <>; - fill: <>; background-color: <>; margin: 0.5em 0 0.5em 0; padding: 4px; align-items: center; } +.tc-plugin-info svg { + color: <>; +} + .tc-sidebar-lists a.tc-tiddlylink.tc-plugin-info { color: <>; } @@ -2610,11 +2590,14 @@ a.tc-tiddlylink.tc-plugin-info:hover { text-decoration: none; background-color: <>; color: <>; - fill: <>; +} + +a.tc-tiddlylink.tc-plugin-info:hover svg { + color: <>; } a.tc-tiddlylink.tc-plugin-info:hover > .tc-plugin-info-chunk > svg { - fill: <>; + color: <>; } a.tc-tiddlylink.tc-plugin-info:hover > .tc-plugin-info-chunk .tc-plugin-info-stability { @@ -2738,7 +2721,6 @@ a.tc-tiddlylink.tc-plugin-info:hover > .tc-plugin-info-chunk .tc-plugin-info-sta font-weight: bold; background: green; color: white; - fill: white; border-radius: 4px; padding: 3px; } @@ -2927,7 +2909,7 @@ input.tc-palette-manager-colour-input { width: 0.7em; height: 0.7em; vertical-align: middle; - fill: <>; + color: <>; } .tc-table-of-contents ol { @@ -3050,7 +3032,6 @@ html body.tc-dirty svg.tc-image-save-button-dynamic .tc-image-save-button-dynami } html body.tc-dirty span.tc-dirty-indicator, html body.tc-dirty span.tc-dirty-indicator svg { - fill: <>; color: <>; } @@ -3137,7 +3118,7 @@ html body.tc-dirty span.tc-dirty-indicator, html body.tc-dirty span.tc-dirty-ind .tc-thumbnail-wrapper:hover .tc-thumbnail-icon svg, .tc-thumbnail-wrapper:hover .tc-thumbnail-icon img { - fill: #fff; + color: #fff; <> } @@ -3244,7 +3225,7 @@ html body.tc-dirty span.tc-dirty-indicator, html body.tc-dirty span.tc-dirty-ind } .tc-tree svg { - fill: #acacac; + color: #acacac; } .tc-tree span svg { @@ -3361,7 +3342,6 @@ span.tc-translink > a:first-child { } .tc-test-case-result-icon { - fill: #fff; padding: 0.25em; display: inline-block; line-height: 0; @@ -3370,6 +3350,10 @@ span.tc-translink > a:first-child { margin-right: 0.25em; } +.tc-test-case-result-icon svg { + color: #fff; +} + .tc-test-case-result-icon-pass { background-color: green; } @@ -3403,7 +3387,7 @@ span.tc-translink > a:first-child { } .tc-test-case-toolbar svg { - fill: <>; + color: <>; } .tc-test-case-toolbar .tc-drop-down { From 4552c117fc3f7a5737dd41e1061f9b5eb54bab27 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Tue, 7 Oct 2025 13:02:08 +0200 Subject: [PATCH 16/82] Add th-debug-element hook and data-debug-element minimal useful info (#9281) * add th-debug-element hook and date-debug-element minimal useful info * invoke th-dom-rendering-element hook * improve comment --- core/modules/widgets/element.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/modules/widgets/element.js b/core/modules/widgets/element.js index 34fd3b9ffc..8b0a88e868 100755 --- a/core/modules/widgets/element.js +++ b/core/modules/widgets/element.js @@ -74,6 +74,8 @@ ElementWidget.prototype.render = function(parent,nextSibling) { // Create the DOM node and render children var domNode = this.document.createElementNS(this.namespace,this.tag); this.assignAttributes(domNode,{excludeEventAttributes: true}); + // Allow hooks to manipulate the DOM node. Eg: Add debug info + $tw.hooks.invokeHook("th-dom-rendering-element", domNode, this); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); this.domNodes.push(domNode); From b462aaa9a35d92f02231fc39e1423d45942baee1 Mon Sep 17 00:00:00 2001 From: Mario Pietsch Date: Tue, 7 Oct 2025 13:03:11 +0200 Subject: [PATCH 17/82] Modules in draft tidders should not be executed (#9293) * modules in draft tiddlers should not be defined * improve warning message and use console.warn --- boot/boot.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/boot/boot.js b/boot/boot.js index ece7404680..0c7828abd2 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -1532,7 +1532,8 @@ Define all modules stored in ordinary tiddlers */ $tw.Wiki.prototype.defineTiddlerModules = function() { this.each(function(tiddler,title) { - if(tiddler.hasField("module-type")) { + // Modules in draft tiddlers are disabled + if(tiddler.hasField("module-type") && (!tiddler.hasField("draft.of"))) { switch(tiddler.fields.type) { case "application/javascript": // We only define modules that haven't already been defined, because in the browser modules in system tiddlers are defined in inline script @@ -1559,6 +1560,11 @@ $tw.Wiki.prototype.defineShadowModules = function() { this.eachShadow(function(tiddler,title) { // Don't define the module if it is overidden by an ordinary tiddler if(!self.tiddlerExists(title) && tiddler.hasField("module-type")) { + if(tiddler.hasField("draft.of")) { + // Report a fundamental problem + console.warn(`TiddlyWiki: Plugins should not contain tiddlers with a 'draft.of' field: ${tiddler.fields.title}`); + return; + } // Define the module $tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],tiddler.fields.text); } From 94673a10287e96c0df89bbb48702eeeb101dba68 Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Tue, 7 Oct 2025 20:05:09 +0800 Subject: [PATCH 18/82] Allow button widget to use all ARIA attibutes (#9154) * Allow button widget to use all ARIA attibutes * Add link in docs * Update docs --- core/modules/widgets/button.js | 12 ++++++++---- editions/tw5.com/tiddlers/widgets/ButtonWidget.tid | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 68f2fcd119..8f6f143763 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -61,6 +61,10 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { sourcePrefix: "data-", destPrefix: "data-" }); + this.assignAttributes(domNode,{ + sourcePrefix: "aria-", + destPrefix: "aria-" + }); // Assign other attributes if(this.style) { domNode.setAttribute("style",this.style); @@ -68,9 +72,6 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { if(this.tooltip) { domNode.setAttribute("title",this.tooltip); } - if(this["aria-label"]) { - domNode.setAttribute("aria-label",this["aria-label"]); - } if (this.role) { domNode.setAttribute("role", this.role); } @@ -215,7 +216,6 @@ ButtonWidget.prototype.execute = function() { this.setTo = this.getAttribute("setTo"); this.popup = this.getAttribute("popup"); this.hover = this.getAttribute("hover"); - this["aria-label"] = this.getAttribute("aria-label"); this.role = this.getAttribute("role"); this.tooltip = this.getAttribute("tooltip"); this.style = this.getAttribute("style"); @@ -271,6 +271,10 @@ ButtonWidget.prototype.refresh = function(changedTiddlers) { sourcePrefix: "data-", destPrefix: "data-" }); + this.assignAttributes(this.domNodes[0],{ + sourcePrefix: "aria-", + destPrefix: "aria-" + }); } return this.refreshChildren(changedTiddlers); }; diff --git a/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid index c726360bd5..d1e94283c6 100644 --- a/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid @@ -1,6 +1,6 @@ caption: button created: 20131024141900000 -modified: 20250720031248375 +modified: 20250720025737830 tags: Widgets TriggeringWidgets title: ButtonWidget type: text/vnd.tiddlywiki @@ -44,6 +44,7 @@ The content of the `<$button>` widget is displayed within the button. |class |An optional CSS class name to be assigned to the HTML element| |data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element | |style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element | +|aria-* |<<.from-version "5.4.0">> Optional [[ARIA attributes|https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes]]. | |style |An optional CSS style attribute to be assigned to the HTML element | |tag |An optional html tag to use instead of the default "button" | |dragTiddler |An optional tiddler title making the button draggable and identifying the payload tiddler. See DraggableWidget for details | From 34737f4e289b8ae2720ed5b7dd4d0548324e1d14 Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Tue, 7 Oct 2025 22:46:43 +0800 Subject: [PATCH 19/82] Improve ARIA support for link widget (#9167) --- core/modules/widgets/link.js | 14 +++++++++++--- editions/tw5.com/tiddlers/widgets/LinkWidget.tid | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index c8b54818dc..896b1c5307 100755 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -45,6 +45,10 @@ LinkWidget.prototype.render = function(parent,nextSibling) { sourcePrefix: "data-", destPrefix: "data-" }); + this.assignAttributes(domNode,{ + sourcePrefix: "aria-", + destPrefix: "aria-" + }); parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); this.domNodes.push(domNode); @@ -125,9 +129,13 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) { }); domNode.setAttribute("title",tooltipText); } - if(this["aria-label"]) { - domNode.setAttribute("aria-label",this["aria-label"]); + if(this.role) { + domNode.setAttribute("role",this.role); } + this.assignAttributes(domNode,{ + sourcePrefix: "aria-", + destPrefix: "aria-" + }) // Add a click event handler $tw.utils.addEventListeners(domNode,[ {name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}, @@ -188,7 +196,7 @@ LinkWidget.prototype.execute = function() { // Pick up our attributes this.to = this.getAttribute("to",this.getVariable("currentTiddler")); this.tooltip = this.getAttribute("tooltip"); - this["aria-label"] = this.getAttribute("aria-label"); + this.role = this.getAttribute("role"); this.linkClasses = this.getAttribute("class"); this.overrideClasses = this.getAttribute("overrideClass"); this.tabIndex = this.getAttribute("tabindex"); diff --git a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid index 8daf3f4e19..63b3280f5e 100644 --- a/editions/tw5.com/tiddlers/widgets/LinkWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LinkWidget.tid @@ -1,6 +1,6 @@ caption: link created: 20131024141900000 -modified: 20240730065043721 +modified: 20250720025154474 tags: Widgets title: LinkWidget type: text/vnd.tiddlywiki @@ -17,6 +17,7 @@ The content of the link widget is rendered within the `` tag representing the |!Attribute |!Description | |to |The title of the target tiddler for the link (defaults to the [[current tiddler|Current Tiddler]]) | |aria-label |Optional accessibility label | +|role |<<.from-version "5.4.0">> Optional [[ARIA role|https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles]] | |tooltip |Optional tooltip WikiText | |tabindex |Optional numeric [[tabindex|https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex]] | |draggable |"yes" to enable the link to be draggable (defaults to "yes") | @@ -25,6 +26,7 @@ The content of the link widget is rendered within the `` tag representing the |overrideClass|<<.from-version "5.1.16">> Optional CSS classes //instead of// the default classes | |data-* |<<.from-version "5.3.2">> Optional [[data attributes|https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes]] to be assigned to the HTML element | |style.* |<<.from-version "5.3.2">> Optional [[CSS properties|https://developer.mozilla.org/en-US/docs/Web/CSS/Reference]] to be assigned to the HTML element | +|aria-* |<<.from-version "5.4.0">> Optional [[ARIA attributes|https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes]] to be assigned to the HTML element | The draggable functionality is equivalent to using the DraggableWidget with the ''tiddler'' attribute set to the link target. From 3889a2a0d0f38bd09ad156353d016061fcd7a72f Mon Sep 17 00:00:00 2001 From: Leilei332 Date: Wed, 8 Oct 2025 17:28:59 +0800 Subject: [PATCH 20/82] Fix nested `span.tc-keyboard` element in core (#9235) * Fix nested `span.keyboard` element in core * Fix switching search results not working --- core/ui/AdvancedSearch/Filter.tid | 40 +++++++++++++--------- core/ui/AdvancedSearch/Shadows.tid | 12 +++++-- core/ui/AdvancedSearch/Standard.tid | 20 +++++++---- core/ui/AdvancedSearch/System.tid | 12 +++++-- core/ui/SideBarSegments/search.tid | 16 ++++++--- core/wiki/macros/keyboard-driven-input.tid | 26 ++++++++------ 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/core/ui/AdvancedSearch/Filter.tid b/core/ui/AdvancedSearch/Filter.tid index 7369e4c400..4005ebbdc7 100644 --- a/core/ui/AdvancedSearch/Filter.tid +++ b/core/ui/AdvancedSearch/Filter.tid @@ -62,28 +62,34 @@ caption: {{$:/language/Search/Filter/Caption}} \end +\procedure input-actions() +<%if [match[((input-tab-right))]] %> +<> +<%elseif [match[((input-tab-left))]] %> +<> +<%endif%> +\end + \whitespace trim <>