mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-05-06 05:31:29 +00:00
Compare commits
41 Commits
wikify-ope
...
blockquote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72deabc3c4 | ||
|
|
abf18a2f35 | ||
|
|
2ea3663ea7 | ||
|
|
78b6f6f442 | ||
|
|
3a4a8a206f | ||
|
|
aff5be7195 | ||
|
|
c4991a54a0 | ||
|
|
1d915389d9 | ||
|
|
f5b4b1781e | ||
|
|
65fcded29f | ||
|
|
962692c90c | ||
|
|
533414b1df | ||
|
|
ec27a4bf20 | ||
|
|
e1cf523e2c | ||
|
|
1ece822d7a | ||
|
|
b7a3418823 | ||
|
|
bb6d88f144 | ||
|
|
57c72756ef | ||
|
|
dda4c7fb10 | ||
|
|
fd3b96e2dd | ||
|
|
c07396c453 | ||
|
|
7fc255f90c | ||
|
|
4cd84a6ba1 | ||
|
|
fdfcd66c9b | ||
|
|
3983086b96 | ||
|
|
a61331e6ed | ||
|
|
8773a0433c | ||
|
|
846deb3039 | ||
|
|
111a168b0c | ||
|
|
02b4c04a56 | ||
|
|
9d5b5111d0 | ||
|
|
48cff401b2 | ||
|
|
7a866b93e3 | ||
|
|
fe3ac0b28a | ||
|
|
d93dcbea6c | ||
|
|
66d12c257c | ||
|
|
321b5bafda | ||
|
|
ba919fba7a | ||
|
|
b0d99f3bd3 | ||
|
|
91e7a62c13 | ||
|
|
a3acbaa2f1 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -2,10 +2,20 @@
|
||||
.c9/
|
||||
.vs/
|
||||
.vscode/
|
||||
.claude/
|
||||
# TiddlyWiki
|
||||
tmp/
|
||||
output/
|
||||
node_modules/
|
||||
$__StoryList.tid
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
$__StoryList.tid
|
||||
/playwright/.auth/
|
||||
test-screenshots/
|
||||
test-output.txt
|
||||
.playwright-mcp
|
||||
# TiddlyWiki MPC
|
||||
.tw-mcp
|
||||
@@ -47,6 +47,13 @@ $tw.utils.hop = function(object,property) {
|
||||
/** @deprecated Use Array.isArray instead */
|
||||
$tw.utils.isArray = (value) => Array.isArray(value);
|
||||
|
||||
/*
|
||||
Determine if a value is a date, even across VM boundaries
|
||||
*/
|
||||
$tw.utils.isDate = function(value) {
|
||||
return Object.prototype.toString.call(value) === "[object Date]";
|
||||
};
|
||||
|
||||
/*
|
||||
Check if an array is equal by value and by reference.
|
||||
*/
|
||||
|
||||
16
community/people/SaqImtiaz.tid
Normal file
16
community/people/SaqImtiaz.tid
Normal file
File diff suppressed because one or more lines are too long
7
community/people/kjharcombe.tid
Normal file
7
community/people/kjharcombe.tid
Normal file
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
created: 20250909171928024
|
||||
modified: 20251110133437795
|
||||
tags: Community/Team
|
||||
leader: @kjharcombe
|
||||
team: @MotovunJack
|
||||
title: Infrastructure Team
|
||||
|
||||
@@ -12,4 +13,4 @@ The infrastructure includes:
|
||||
* github.com/TiddlyWiki
|
||||
* tiddlywiki.com DNS
|
||||
* Netlify account for PR previews
|
||||
* edit.tiddlywiki.com
|
||||
* edit.tiddlywiki.com
|
||||
|
||||
@@ -14,31 +14,31 @@ Export our filter function
|
||||
*/
|
||||
exports.sort = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,false,undefined,operator.operands[1]);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,false);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.nsort = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",false,true,undefined,operator.operands[1]);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",false,true);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.sortan = function(source, operator, options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results, operator.operands[0] || "title", operator.prefix === "!",false,false,true,operator.operands[1]);
|
||||
options.wiki.sortTiddlers(results, operator.operand || "title", operator.prefix === "!",false,false,true);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.sortcs = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,false,undefined,operator.operands[1]);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,false);
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.nsortcs = function(source,operator,options) {
|
||||
var results = prepare_results(source);
|
||||
options.wiki.sortTiddlers(results,operator.operands[0] || "title",operator.prefix === "!",true,true,undefined,operator.operands[1]);
|
||||
options.wiki.sortTiddlers(results,operator.operand || "title",operator.prefix === "!",true,true);
|
||||
return results;
|
||||
};
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ exports.parseStringLiteral = function(source,pos) {
|
||||
type: "string",
|
||||
start: pos
|
||||
};
|
||||
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[((?:[^\]]|\](?!\]))*)\]\]/g;
|
||||
var reString = /(?:"""([\s\S]*?)"""|"([^"]*)")|(?:'([^']*)')|\[\[((?:[^\]]|\](?!\]))*)\]\]/y;
|
||||
reString.lastIndex = pos;
|
||||
var match = reString.exec(source);
|
||||
if(match && match.index === pos) {
|
||||
@@ -221,7 +221,7 @@ exports.parseMacroInvocationAsTransclusion = function(source,pos) {
|
||||
orderedAttributes: []
|
||||
};
|
||||
// Define our regexps
|
||||
var reVarName = /([^\s>"'=:]+)/g;
|
||||
var reVarName = /([^\s>"'=:]+)/y;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a double opening angle bracket
|
||||
@@ -237,9 +237,11 @@ exports.parseMacroInvocationAsTransclusion = function(source,pos) {
|
||||
}
|
||||
$tw.utils.addAttributeToParseTreeNode(node,"$variable",token.match[1]);
|
||||
pos = token.end;
|
||||
// Check that the tag is terminated by a space or >>
|
||||
if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) {
|
||||
return null;
|
||||
// Check that the tag is terminated by a space or >>, and that there is a closing >> somewhere ahead
|
||||
if(!(source.charAt(pos) === ">" && source.charAt(pos + 1) === ">") ) {
|
||||
if(!$tw.utils.parseWhiteSpace(source,pos) || source.indexOf(">>",pos) === -1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Process attributes
|
||||
pos = $tw.utils.parseMacroParametersAsAttributes(node,source,pos);
|
||||
@@ -267,7 +269,7 @@ exports.parseMVVReferenceAsTransclusion = function(source,pos) {
|
||||
orderedAttributes: []
|
||||
};
|
||||
// Define our regexps
|
||||
var reVarName = /([^\s>"'=:)]+)/g;
|
||||
var reVarName = /([^\s>"'=:)]+)/y;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a double opening parenthesis
|
||||
@@ -323,17 +325,17 @@ exports.parseMacroParameterAsAttribute = 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;
|
||||
var 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 and the separator token
|
||||
var nameToken = $tw.utils.parseTokenRegExp(source,pos,reAttributeName),
|
||||
namePos = nameToken && $tw.utils.skipWhiteSpace(source,nameToken.end),
|
||||
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/g),
|
||||
separatorToken = nameToken && $tw.utils.parseTokenRegExp(source,namePos,/=|:/y),
|
||||
isNewStyleSeparator = false; // If there is no separator then we don't allow new style values
|
||||
// If we have a name and a separator then we have a named attribute
|
||||
if(nameToken && separatorToken) {
|
||||
@@ -345,64 +347,78 @@ exports.parseMacroParameterAsAttribute = function(source,pos) {
|
||||
}
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Look for a string literal
|
||||
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
|
||||
if(stringLiteral) {
|
||||
pos = stringLiteral.end;
|
||||
node.type = "string";
|
||||
node.value = stringLiteral.value;
|
||||
// Mark the value as having been quoted in the source
|
||||
node.quoted = true;
|
||||
} else {
|
||||
// Look for a filtered value
|
||||
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
|
||||
if(filteredValue && isNewStyleSeparator) {
|
||||
pos = filteredValue.end;
|
||||
node.type = "filtered";
|
||||
node.filter = filteredValue.match[1];
|
||||
} else {
|
||||
|
||||
do {
|
||||
// Look for a string literal
|
||||
var stringLiteral = $tw.utils.parseStringLiteral(source,pos);
|
||||
if(stringLiteral) {
|
||||
pos = stringLiteral.end;
|
||||
node.type = "string";
|
||||
node.value = stringLiteral.value;
|
||||
// Mark the value as having been quoted in the source
|
||||
node.quoted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(isNewStyleSeparator) {
|
||||
// Look for a filtered value
|
||||
var filteredValue = $tw.utils.parseTokenRegExp(source,pos,reFilteredValue);
|
||||
if(filteredValue) {
|
||||
pos = filteredValue.end;
|
||||
node.type = "filtered";
|
||||
node.filter = filteredValue.match[1];
|
||||
break;
|
||||
}
|
||||
|
||||
// Look for an indirect value
|
||||
var indirectValue = $tw.utils.parseTokenRegExp(source,pos,reIndirectValue);
|
||||
if(indirectValue && isNewStyleSeparator) {
|
||||
if(indirectValue) {
|
||||
pos = indirectValue.end;
|
||||
node.type = "indirect";
|
||||
node.textReference = indirectValue.match[1];
|
||||
} else {
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
|
||||
if(macroInvocation && isNewStyleSeparator) {
|
||||
pos = macroInvocation.end;
|
||||
node.type = "macro";
|
||||
node.value = macroInvocation;
|
||||
} else {
|
||||
// Look for an MVV reference value
|
||||
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
|
||||
if(mvvReference && isNewStyleSeparator) {
|
||||
pos = mvvReference.end;
|
||||
node.type = "macro";
|
||||
node.value = mvvReference;
|
||||
node.isMVV = true;
|
||||
} else {
|
||||
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
|
||||
if(substitutedValue && isNewStyleSeparator) {
|
||||
pos = substitutedValue.end;
|
||||
node.type = "substituted";
|
||||
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
|
||||
} else {
|
||||
// Look for a unquoted value
|
||||
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
|
||||
if(unquotedValue) {
|
||||
pos = unquotedValue.end;
|
||||
node.type = "string";
|
||||
node.value = unquotedValue.match[1];
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Look for a macro invocation value
|
||||
var macroInvocation = $tw.utils.parseMacroInvocationAsTransclusion(source,pos);
|
||||
if(macroInvocation) {
|
||||
pos = macroInvocation.end;
|
||||
node.type = "macro";
|
||||
node.value = macroInvocation;
|
||||
break;
|
||||
}
|
||||
|
||||
// Look for an MVV reference value
|
||||
var mvvReference = $tw.utils.parseMVVReferenceAsTransclusion(source,pos);
|
||||
if(mvvReference) {
|
||||
pos = mvvReference.end;
|
||||
node.type = "macro";
|
||||
node.value = mvvReference;
|
||||
node.isMVV = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Look for a substituted value
|
||||
var substitutedValue = $tw.utils.parseTokenRegExp(source,pos,reSubstitutedValue);
|
||||
if(substitutedValue) {
|
||||
pos = substitutedValue.end;
|
||||
node.type = "substituted";
|
||||
node.rawValue = substitutedValue.match[1] || substitutedValue.match[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a unquoted value
|
||||
var unquotedValue = $tw.utils.parseTokenRegExp(source,pos,reUnquotedAttribute);
|
||||
if(unquotedValue) {
|
||||
pos = unquotedValue.end;
|
||||
node.type = "string";
|
||||
node.value = unquotedValue.match[1];
|
||||
break; // redundant, but leaving for consistency
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
// Bail if we don't have a value
|
||||
if(!node.type) {
|
||||
return null;
|
||||
|
||||
@@ -369,16 +369,31 @@ Sort an array of tiddler titles by a specified field
|
||||
isDescending: true if the sort should be descending
|
||||
isCaseSensitive: true if the sort should consider upper and lower case letters to be different
|
||||
*/
|
||||
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric,locale) {
|
||||
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
|
||||
var self = this;
|
||||
if(sortField === "title") {
|
||||
if(!isNumeric && !isAlphaNumeric) {
|
||||
const sorter = new Intl.Collator(locale, { sensitivity: isCaseSensitive ? "variant" : "accent" });
|
||||
if(isDescending) {
|
||||
titles.sort((a,b) => sorter.compare(b, a));
|
||||
if(isCaseSensitive) {
|
||||
if(isDescending) {
|
||||
titles.sort(function(a,b) {
|
||||
return b.localeCompare(a);
|
||||
});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
titles.sort((a,b) => sorter.compare(a, b));
|
||||
}
|
||||
if(isDescending) {
|
||||
titles.sort(function(a,b) {
|
||||
return b.toLowerCase().localeCompare(a.toLowerCase());
|
||||
});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
var x,y;
|
||||
@@ -399,8 +414,14 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
|
||||
}
|
||||
}
|
||||
}
|
||||
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
|
||||
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
|
||||
if(isAlphaNumeric) {
|
||||
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
|
||||
}
|
||||
if(!isCaseSensitive) {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -442,8 +463,14 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
|
||||
}
|
||||
a = String(a);
|
||||
b = String(b);
|
||||
const sorter = new Intl.Collator(locale, { numeric: isAlphaNumeric, sensitivity: isAlphaNumeric ? "base" : isCaseSensitive ? "variant" : "accent" });
|
||||
return isDescending ? sorter.compare(b, a) : sorter.compare(a, b);
|
||||
if(isAlphaNumeric) {
|
||||
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
|
||||
}
|
||||
if(!isCaseSensitive) {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
title: $:/core/ui/EditTemplate/body/toolbar/button
|
||||
|
||||
\whitespace trim
|
||||
|
||||
\define toolbar-button-icon()
|
||||
<$list
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"tiddlywiki/menubar",
|
||||
"tiddlywiki/jszip",
|
||||
"tiddlywiki/confetti",
|
||||
"tiddlywiki/tour"
|
||||
"tiddlywiki/tour",
|
||||
"tiddlywiki/dom-to-image"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("Backlinks tests", function() {
|
||||
}
|
||||
|
||||
describe("a tiddler with no links to it", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
|
||||
@@ -10,7 +10,7 @@ Tests the backtranscludes mechanism.
|
||||
|
||||
describe("Backtranscludes and transclude filter tests", function() {
|
||||
describe("a tiddler with no transcludes to it", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -25,7 +25,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A tiddler added to the wiki with a transclude to it", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -44,7 +44,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A tiddler transclude with template will still use the tiddler as result.", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -60,7 +60,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A data tiddler transclude will still use the tiddler as result.", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -81,7 +81,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
|
||||
describe("A tiddler that has a transclude added to it later", function() {
|
||||
it("should have an additional backtransclude", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -106,7 +106,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A tiddler that has a transclude remove from it later", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -128,7 +128,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A tiddler transcludeing to another that gets renamed", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -148,7 +148,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("A tiddler transcludeing to another that gets deleted", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestIncoming",
|
||||
@@ -168,7 +168,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("a tiddler with some transcludes on it in order", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestOutgoing",
|
||||
@@ -186,7 +186,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("include implicit self transclusion", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestOutgoing",
|
||||
@@ -202,7 +202,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("include explicit self transclusion", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestOutgoing",
|
||||
@@ -218,7 +218,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("exclude self when target tiddler is not string", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestOutgoing",
|
||||
@@ -234,7 +234,7 @@ describe("Backtranscludes and transclude filter tests", function() {
|
||||
});
|
||||
|
||||
describe("recognize transclusion defined by widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "TestOutgoing",
|
||||
|
||||
@@ -534,7 +534,7 @@ describe("Checkbox widget", function() {
|
||||
it("checkbox widget test: " + data.testName, function() {
|
||||
// Setup
|
||||
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddlers(data.tiddlers);
|
||||
var widgetNode = createWidgetNode(parseText(data.widgetText,wiki),wiki);
|
||||
renderWidgetNode(widgetNode);
|
||||
|
||||
@@ -13,7 +13,7 @@ Tests the compare filter.
|
||||
|
||||
describe("'compare' filter tests", function() {
|
||||
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
it("should compare numerical equality", function() {
|
||||
expect(wiki.filterTiddlers("[[2]compare:number:eq[0003]]").join(",")).toBe("");
|
||||
|
||||
@@ -583,7 +583,7 @@ describe("Filter tests", function() {
|
||||
});
|
||||
|
||||
it("should handle the '[is[draft]]' operator", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddlers([
|
||||
{title: "A"},
|
||||
{title: "Draft of 'A'", "draft.of": "A", "draft.title": "A"},
|
||||
|
||||
@@ -13,7 +13,7 @@ Tests the JSON filters and the format:json operator
|
||||
|
||||
describe("json filter tests", function() {
|
||||
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var tiddlers = [{
|
||||
title: "First",
|
||||
text: '{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}',
|
||||
|
||||
@@ -12,7 +12,7 @@ Tests for source attribute in parser returned from wiki.parseTextReference
|
||||
describe("Wiki.parseTextReference tests", function() {
|
||||
|
||||
// Create a wiki
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddler({
|
||||
title: "TiddlerOne",
|
||||
text: "The quick brown fox in $:/TiddlerTwo",
|
||||
|
||||
@@ -14,7 +14,7 @@ Tests the reduce prefix and filter.
|
||||
|
||||
describe("general filter prefix tests", function() {
|
||||
it("should handle nonexistent prefixes gracefully", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var results = wiki.filterTiddlers("[tag[A]] :nonexistent[tag[B]]");
|
||||
expect(results).toEqual(["Filter Error: Unknown prefix for filter run"]);
|
||||
});
|
||||
@@ -215,7 +215,7 @@ describe("general filter prefix tests", function() {
|
||||
|
||||
describe("'reduce' and 'intersection' filter prefix tests", function() {
|
||||
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
wiki.addTiddler({
|
||||
title: "Brownies",
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("Utility tests", function() {
|
||||
});
|
||||
|
||||
it("stringifyList shouldn't interfere with setting variables to negative numbers", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddler({title: "test", text: "<$set name=X filter='\"-7\"'>{{{ [<X>add[2]] }}}</$set>"});
|
||||
// X shouldn't be wrapped in brackets. If it is, math filters will treat it as zero.
|
||||
expect(wiki.renderTiddler("text/plain","test")).toBe("-5");
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should call all added event listeners on dispatchEvent", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
// Add a function listener.
|
||||
@@ -44,7 +44,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should remove an event listener correctly", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
function listener(e) {
|
||||
@@ -70,7 +70,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("stop further propagation by returns false won't block other listeners on the same level.", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
widget.addEventListener("stopEvent", function(e) {
|
||||
@@ -92,7 +92,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should dispatch event to parent widget if not handled on child", function() {
|
||||
var parentCalls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("parentEvent", function(e) {
|
||||
parentCalls.push("parentListener");
|
||||
@@ -110,7 +110,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should not dispatch event to parent if child's listener stops propagation", function() {
|
||||
var parentCalls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("bubbleTest", function(e) {
|
||||
parentCalls.push("parentListener");
|
||||
@@ -128,7 +128,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should call multiple listeners in proper order across child and parent", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var parentWidget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
parentWidget.addEventListener("chainEvent", function(e) {
|
||||
calls.push("parentListener");
|
||||
@@ -152,7 +152,7 @@ describe("Widget Event Listeners", function() {
|
||||
it("should handle events of different types separately", function() {
|
||||
var callsA = [];
|
||||
var callsB = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
widget.addEventListener("eventA", function(e) {
|
||||
callsA.push("A1");
|
||||
@@ -171,7 +171,7 @@ describe("Widget Event Listeners", function() {
|
||||
// Test using $tw.utils.each in removeEventListener internally (behavior verified via dispatch)
|
||||
it("should remove listeners using $tw.utils.each without affecting other listeners", function() {
|
||||
var calls = [];
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
function listener1(e) {
|
||||
calls.push("listener1");
|
||||
@@ -192,7 +192,7 @@ describe("Widget Event Listeners", function() {
|
||||
|
||||
it("should prevent adding the same event listener multiple times", function() {
|
||||
var calls = 0;
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var widget = createWidgetNode({type:"widget", text:"text"}, wiki);
|
||||
|
||||
function listener(e) {
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("Widget module", function() {
|
||||
}
|
||||
|
||||
it("should deal with text nodes and HTML elements", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Test parse tree
|
||||
var parseTreeNode = {type: "widget", children: [
|
||||
{type: "text", text: "A text node"},
|
||||
@@ -77,7 +77,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with transclude widgets and indirect attributes", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "the quick brown fox"}
|
||||
@@ -137,7 +137,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should detect recursion of the transclude macro", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/>"},
|
||||
@@ -158,7 +158,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should handle single-tiddler recursion with branching nodes", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "<$tiddler tiddler='TiddlerOne'><$transclude /> <$transclude /></$tiddler>"},
|
||||
@@ -182,7 +182,7 @@ describe("Widget module", function() {
|
||||
// end up being the same value for all iterations of the test.
|
||||
$tw.utils.each(["div","$button","$checkbox","$diff-text","$draggable","$droppable","dropzone","$eventcatcher","$keyboard","$link","$list filter=x variable=x","$radio","$reveal type=nomatch","$scrollable","$select","$view field=x"],function(tag) {
|
||||
it(`${tag} cleans itself up if children rendering fails`, function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddler({title: "TiddlerOne", text: `<$tiddler tiddler='TiddlerOne'><${tag}><$transclude />`});
|
||||
var parseTreeNode = {type: "widget", children: [
|
||||
{type: "transclude", attributes: {
|
||||
@@ -204,7 +204,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should handle many-tiddler recursion with branching nodes", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/> <$transclude tiddler='TiddlerTwo'/>"},
|
||||
@@ -225,7 +225,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with SVG elements", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Construct the widget node
|
||||
var text = "<svg class=\"tv-image-new-button\" viewBox=\"83 81 50 50\" width=\"22pt\" height=\"22pt\"><path d=\"M 101.25 112.5 L 101.25 127.5 C 101.25 127.5 101.25 127.5 101.25 127.5 L 101.25 127.5 C 101.25 129.156855 102.593146 130.5 104.25 130.5 L 111.75 130.5 C 113.406854 130.5 114.75 129.156854 114.75 127.5 L 114.75 112.5 L 129.75 112.5 C 131.406854 112.5 132.75 111.156854 132.75 109.5 L 132.75 102 C 132.75 100.343146 131.406854 99 129.75 99 L 114.75 99 L 114.75 84 C 114.75 82.343146 113.406854 81 111.75 81 L 104.25 81 C 104.25 81 104.25 81 104.25 81 C 102.593146 81 101.25 82.343146 101.25 84 L 101.25 99 L 86.25 99 C 86.25 99 86.25 99 86.25 99 C 84.593146 99 83.25 100.343146 83.25 102 L 83.25 109.5 C 83.25 109.5 83.25 109.5 83.25 109.5 L 83.25 109.5 C 83.25 111.156855 84.593146 112.5 86.25 112.5 Z\"/></svg>\n";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki,{parseAsInline:true}),wiki);
|
||||
@@ -237,7 +237,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should parse and render transclusions", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -254,7 +254,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should render the view widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a tiddler
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"}
|
||||
@@ -283,7 +283,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with the set widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -313,7 +313,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with the let widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "lookup"},
|
||||
{title: "TiddlerTwo", lookup: "value", newlookup: "value", wrong: "wrong"},
|
||||
@@ -347,7 +347,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with attributes specified as macro invocations", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Construct the widget node
|
||||
var text = "\\define myMacro(one:\"paramOne\",two,three:\"paramTwo\")\nMy something $one$, $two$ or other $three$\n\\end\n<div class=<<myMacro 'something' three:'thing'>>>Content</div>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
@@ -358,7 +358,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with built-in macros", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World", type: "text/vnd.tiddlywiki"}
|
||||
@@ -374,7 +374,7 @@ describe("Widget module", function() {
|
||||
|
||||
/* This test reproduces issue #4693. */
|
||||
it("should render the entity widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Construct the widget node
|
||||
var text = "\n\n<$entity entity=' ' />\n\n<$entity entity='✓' />\n";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
@@ -391,7 +391,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with the list widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -451,7 +451,7 @@ describe("Widget module", function() {
|
||||
|
||||
|
||||
it("should deal with the list widget using a counter variable", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -593,7 +593,7 @@ describe("Widget module", function() {
|
||||
|
||||
var testListJoin = function(oldList, newList) {
|
||||
return function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddler({title: "Numbers", text: "", list: oldList});
|
||||
var text = "<$list filter='[list[Numbers]]' variable='item' join=', '><<item>></$list>";
|
||||
@@ -632,7 +632,7 @@ describe("Widget module", function() {
|
||||
|
||||
var testCounterLast = function(oldList, newList) {
|
||||
return function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddler({title: "Numbers", text: "", list: oldList});
|
||||
var text = "<$list filter='[list[Numbers]]' variable='item' counter='c'><<item>><$text text={{{ [<c-last>match[no]then[, ]] }}} /></$list>";
|
||||
@@ -654,7 +654,7 @@ describe("Widget module", function() {
|
||||
it("the list widget with counter-last should update correctly when first item is removed", testCounterLast("1 2 3 4", "2 3 4"));
|
||||
|
||||
it("should deal with the list widget followed by other widgets", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -727,7 +727,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with the list widget and external templates", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "$:/myTemplate", text: "(<$view field='title'/>)"},
|
||||
@@ -747,7 +747,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should deal with the list widget and empty lists", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Construct the widget node
|
||||
var text = "<$list emptyMessage='nothing'><$view field='title'/></$list>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
@@ -758,7 +758,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should refresh lists that become empty", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||
@@ -788,7 +788,7 @@ describe("Widget module", function() {
|
||||
* if they use transclusion for their value. This relates to PR #4108.
|
||||
*/
|
||||
it("should refresh imported <$set> widgets", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "Raw", text: "Initial value"},
|
||||
@@ -808,7 +808,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should support mixed setWidgets and macros when importing", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "A", text: "\\define A() Aval"},
|
||||
@@ -824,7 +824,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should skip parameters widgets when importing", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add some tiddlers
|
||||
wiki.addTiddlers([
|
||||
{title: "B", text: "<$parameters bee=nothing><$set name='B' value='Bval'>\n\ndummy text</$set></$parameters>"},
|
||||
@@ -838,7 +838,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should use default $parameters if directly rendered", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var text = "<$parameters bee=default $$dollar=bill nothing empty=''>bee=<<bee>>, $dollar=<<$dollar>>, nothing=<<nothing>>, empty=<<empty>></$parameters>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
@@ -848,7 +848,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("should use default \\parameters if directly rendered", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var text = "\\parameters(bee:default $$dollar:bill nothing)\nbee=<<bee>>, $$dollar=<<$$dollar>>, nothing=<<nothing>>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
@@ -858,7 +858,7 @@ describe("Widget module", function() {
|
||||
});
|
||||
|
||||
it("can have more than one macroDef variable imported", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
wiki.addTiddlers([
|
||||
{title: "ABC", text: "<$set name=A value=A>\n\n<$set name=B value=B>\n\n<$set name=C value=C>\n\ndummy text</$set></$set></$set>"},
|
||||
{title: "D", text: "\\define D() D"}]);
|
||||
@@ -911,7 +911,7 @@ describe("Widget module", function() {
|
||||
* doesn't forget its childrenNodes.
|
||||
*/
|
||||
it("should work when import widget imports nothing", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var text = "\\import [prefix[XXX]]\nDon't forget me.";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
// Render the widget node to the DOM
|
||||
@@ -925,7 +925,7 @@ describe("Widget module", function() {
|
||||
* visual difference, but may affect plugins if it doesn't.
|
||||
*/
|
||||
it("should work when import pragma is standalone", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
var text = "\\import [prefix[XXX]]";
|
||||
var parseTreeNode = parseText(text,wiki);
|
||||
// Test the resulting parse tree node, since there is no
|
||||
@@ -944,7 +944,7 @@ describe("Widget module", function() {
|
||||
* at least ONE variable.
|
||||
*/
|
||||
it("adding imported variables doesn't change qualifyers", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
function wikiparse(text) {
|
||||
var tree = parseText(text,wiki);
|
||||
var widgetNode = createWidgetNode(tree,wiki);
|
||||
|
||||
@@ -12,7 +12,7 @@ Tests for wikitext parser
|
||||
describe("WikiText parser tests", function() {
|
||||
|
||||
// Create a wiki
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
|
||||
// Define a parsing shortcut
|
||||
var parse = function(text) {
|
||||
|
||||
@@ -12,7 +12,7 @@ Tests the wikitext rendering pipeline end-to-end. We also need tests that indivi
|
||||
describe("WikiText tests", function() {
|
||||
|
||||
// Create a wiki
|
||||
var wiki = new $tw.Wiki();
|
||||
var wiki = $tw.test.wiki();
|
||||
// Add a couple of tiddlers
|
||||
wiki.addTiddler({title: "TiddlerOne", text: "The quick brown fox"});
|
||||
wiki.addTiddler({title: "TiddlerTwo", text: "The rain in Spain\nfalls mainly on the plain"});
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
created: 20251001034405510
|
||||
list: A a Ä ä Z z O o Õ õ Ö ö Ü ü Y y
|
||||
modified: 20251218023544134
|
||||
tags:
|
||||
title: Locale Example
|
||||
type: text/vnd.tiddlywiki
|
||||
@@ -1,5 +1,5 @@
|
||||
created: 20150124112340000
|
||||
modified: 20251218023608468
|
||||
modified: 20150124113250000
|
||||
tags: [[sort Operator]] [[Operator Examples]]
|
||||
title: sort Operator (Examples)
|
||||
type: text/vnd.tiddlywiki
|
||||
@@ -11,10 +11,3 @@ type: text/vnd.tiddlywiki
|
||||
<<.operator-example 3 "one two Three four +[sort[]]">>
|
||||
<<.operator-example 4 "[prefix[Tiddl]sort[text]]">>
|
||||
<<.operator-example 5 "[has[created]sort[created]limit[10]]" "the oldest 10 tiddlers in the wiki">>
|
||||
|
||||
! Using a custom locale
|
||||
The following examples shows the differences when using the sort operator with default, Swedish and Estonian locale.
|
||||
|
||||
<<.operator-example 6 "[list[Locale Example]sort[]]">>
|
||||
<<.operator-example 7 "[list[Locale Example]sort[],[sv]]">>
|
||||
<<.operator-example 8 "[list[Locale Example]sort[],[et]]">>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
caption: nsort
|
||||
created: 20140410103123179
|
||||
modified: 20251227084602319
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as numbers
|
||||
op-parameter: accept same parameters as the [[sort Operator]]
|
||||
op-parameter-name: F
|
||||
op-purpose: sort the input by number field
|
||||
modified: 20150203190051000
|
||||
tags: [[Filter Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
|
||||
title: nsort Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
caption: nsort
|
||||
op-purpose: sort the input by number field
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-parameter: the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
op-parameter-name: F
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as numbers
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
|
||||
Non-numeric values are treated as having a higher value than any number, and the difference between capital and lowercase letters is ignored. Compare <<.olink nsortcs>>.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
caption: nsortcs
|
||||
created: 20140410103123179
|
||||
modified: 20251227084615705
|
||||
modified: 20150417125717078
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
op-output: the input, sorted into ascending order by field <<.place F>>, treating field values as numbers
|
||||
op-parameter: accept same parameters as the [[sort Operator]]
|
||||
op-parameter: the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
op-parameter-name: F
|
||||
op-purpose: sort the input titles by number field, treating upper and lower case as different
|
||||
tags: [[Filter Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
caption: sort
|
||||
created: 20140410103123179
|
||||
modified: 20251227084644651
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as text
|
||||
op-parameter: the <<.op sort>> operator accepts 1 or 2 parameters, see below for details
|
||||
op-parameter-name: F
|
||||
op-purpose: sort the input by text field
|
||||
modified: 20150203191228000
|
||||
tags: [[Filter Operators]] [[Common Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
|
||||
title: sort Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
caption: sort
|
||||
op-purpose: sort the input by text field
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-parameter: the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
op-parameter-name: F
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as text
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
|
||||
The difference between capital and lowercase letters is ignored. Compare <<.olink sortcs>>.
|
||||
|
||||
```
|
||||
[sort[<field>],[<locale>]]
|
||||
```
|
||||
* ''field'' : the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
* ''locale'': (optional). A string with a [[BCP 47 language tag|https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag]]
|
||||
|
||||
<<.operator-examples "sort">>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
caption: sortan
|
||||
created: 20180222071605739
|
||||
modified: 20251227084439804
|
||||
modified: 20180223012553446
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as alphanumerics
|
||||
op-parameter: accept same parameters as the [[sort Operator]]
|
||||
op-parameter: the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
op-parameter-name: F
|
||||
op-purpose: sort the input by text field considering them as alphanumerics
|
||||
tags: [[Filter Operators]] [[Common Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
caption: sortcs
|
||||
created: 20140410103123179
|
||||
modified: 20251227084416522
|
||||
modified: 20150417125704503
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-neg-output: the input, likewise sorted into descending order
|
||||
op-output: the input, sorted into ascending order by field <<.field F>>, treating field values as text
|
||||
op-parameter: accept same parameters as the [[sort Operator]]
|
||||
op-parameter: the name of a [[field|TiddlerFields]], defaulting to <<.field title>>
|
||||
op-parameter-name: F
|
||||
op-purpose: sort the input by text field, treating upper and lower case as different
|
||||
tags: [[Filter Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
|
||||
|
||||
@@ -19,3 +19,5 @@ A <<.def "filter expression">> is the outermost level of the [[filter syntax|Fil
|
||||
<<.tip """If the diagram has a single start and end line, as shown above, it means there is more info in the linked level above. The breadcrumbs can be used to navigate""">>
|
||||
|
||||
<<.tip """If the diagram has no start and no end, as used in lower levels, it means that higher level syntax elements have been removed, to increase readability and simplicity. The breadcrumbs can be used to navigate""">>
|
||||
|
||||
<<.note """Filter expressions have a maximum recursion depth of 300. Filters that recursively invoke themselves (e.g. via the <<.olink subfilter>> or <<.olink filter>> operators) beyond this limit will return the error message `/**-- Excessive filter recursion --**/`. This protects against infinite loops.""">>
|
||||
|
||||
@@ -12,6 +12,8 @@ type: text/vnd.tiddlywiki
|
||||
"{" [: <-"indirect"-> /"anything but }"/] "}"
|
||||
|
|
||||
"<" [: <-"variable"-> /"anything but >"/] ">"
|
||||
|
|
||||
"(" [: <-"multi-valued variable"-> /"anything but )"/ ] ")" /"v5.4.0"/
|
||||
)
|
||||
"""/>
|
||||
|
||||
@@ -20,6 +22,7 @@ The parameter to a [[filter operator|Filter Operators]] can be:
|
||||
;<<.def hard>>
|
||||
: `[like this]`
|
||||
: The parameter is the exact text that appears between the square brackets.
|
||||
|
||||
;<<.def soft>>
|
||||
: <<.def indirect>>
|
||||
:: `{like this}`
|
||||
@@ -28,6 +31,9 @@ The parameter to a [[filter operator|Filter Operators]] can be:
|
||||
:: `<like this>`
|
||||
:: The parameter is the current value of the [[variable|Variables]] whose name appears between the angle brackets. Macro parameters are <<.em not>> supported up to v5.2.0
|
||||
::<<.from-version "5.2.0">> Literal macro parameters are supported. For example: `[<now [UTC]YYYY0MM0DD0hh0mm0ssXXX>]`.
|
||||
: <<.def "multi-valued variable">>
|
||||
:: `(like this)`
|
||||
:: <<.from-version "5.4.0">> The parameter expands to the complete list of values stored in the [[multi-valued variable|Multi-Valued Variables]] whose name appears between the round brackets. When accessed this way, the filter operator receives all values, not just the first. See [[Multi-Valued Variables]] for details.
|
||||
|
||||
<<.note """Every [[filter Operator]] must be followed by a parameter expression. In the case of [[Operators without parameters]], that expression is empty, as with the filter Operator <<.olink links>> in `[<currentTiddler>links[]]`.""">>
|
||||
|
||||
@@ -35,4 +41,8 @@ The parameter to a [[filter operator|Filter Operators]] can be:
|
||||
|
||||
<<.from-version "5.1.23">> [[Filter Step]]s support multiple parameters which are separated by a `,` character.
|
||||
|
||||
For example: `[param1],[param2]` or `<param1>,{param2}`
|
||||
For example: `[param1],[param2]` or `<param1>,{param2}` or `[param1],(param2)`
|
||||
|
||||
---
|
||||
|
||||
<<.warning """The `/regexp/(flags)` operand syntax is deprecated and will log a warning to the browser console. Use the <<.olink regexp>> operator instead.""">>
|
||||
|
||||
@@ -18,17 +18,17 @@ In programming terms, it is akin to a function call to which the step's input is
|
||||
{ [[parameter|"Filter Parameter"]] + "," }
|
||||
"""/>
|
||||
|
||||
The step's <<.def operator>> is drawn from a list of predefined keywoards which are known as [[filter operators|Filter Operators]].
|
||||
The step's <<.def operator>> is drawn from a list of predefined keywords which are known as [[filter operators|Filter Operators]].
|
||||
|
||||
Many steps require an explicit <<.def parameter>>, that further defines what the step is to do.
|
||||
|
||||
The <<.def suffix>> is additional text, often the name of a [[field|TiddlerFields]], that extends the meaning of certain operators.
|
||||
The <<.def suffix>> is additional text, often the name of a [[field|TiddlerFields]], that extends the meaning of certain operators. Suffixes are separated by `:` characters, and each suffix group can contain multiple comma-separated values. For example, in `compare:number:gteq`, the first suffix is `number` and the second is `gteq`. In `:sort:string:reverse,casesensitive`, the first suffix is `string` and the second suffix group contains both `reverse` and `casesensitive`.
|
||||
|
||||
If a step's <<.def operator>> and <<.def suffix>> are //omitted// altogether, it defaults to the [[title|title Operator]] operator.
|
||||
If a step's <<.def operator>> and <<.def suffix>> are //omitted// altogether, it defaults to the [[title|title Operator]] operator. If a suffix is present but the operator name before the colon is empty (e.g. `[:fieldname[value]]`), the operator defaults to <<.olink field>>.
|
||||
|
||||
<<.from-version "5.1.23">> Some steps accept multiple <<.def parameter>>s which are separated by a `,` character.
|
||||
|
||||
Any unrecognised operator is treated as if it was the suffix to the <<.olink field>> operator.
|
||||
Any unrecognised operator is treated as if it was the suffix to the <<.olink field>> operator. <<.from-version "5.3.0">> However, unrecognised operators whose name contains a `.` (dot) character are treated as user-defined filter operators, resolved by looking up a variable or function with that name.
|
||||
|
||||
Filter operators can be extended by plugins.
|
||||
|
||||
|
||||
@@ -43,3 +43,5 @@ For the difference between `+` and `:intersection`, see [[Intersection Filter Ru
|
||||
!! For Developers
|
||||
|
||||
To create a new filter run prefix, create a [[Javascript module|Modules]] with a [[module-type|ModuleType]] of `filterrunprefix`.
|
||||
|
||||
Compiled filter expressions are cached for performance. The cache holds up to 2000 entries and is reset in its entirety when this limit is exceeded.
|
||||
|
||||
@@ -15,6 +15,15 @@ type: text/vnd.tiddlywiki
|
||||
[[run|"Filter Run"]]
|
||||
"""/>
|
||||
|
||||
The filter output from previous runs is set aside. The `:intersection` filter run is started with all tiddler titles as input. Once this latest filter run has completed, the latest output is compared to the set-aside output. A new output is produced that contains only titles that appeared in both the set-aside output and the latest output.
|
||||
The filter output from previous runs is set aside. The `:intersection` filter run is started with all tiddler titles as input. Once this filter run has completed, the output is compared to the set-aside output. A new output is produced that contains only titles that appeared in both the set-aside output and the latest output. The order of titles in the output is preserved from the accumulated results.
|
||||
|
||||
!! Difference from `:and` / `+`
|
||||
|
||||
The key difference between `:intersection` and `:and` (or `+`) is what they receive as ''input'':
|
||||
|
||||
* `:and` / `+` feeds the ''accumulated results so far'' as input to the filter run. Operators in the run can only see and work with titles already in the results.
|
||||
* `:intersection` feeds ''all tiddler titles'' as input to the filter run (just like an unprefixed run), then keeps only titles that also appear in the accumulated results.
|
||||
|
||||
This means `:intersection` can match titles using operators that need to start from the full tiddler pool. See [[Interchangeable Filter Run Prefixes]] for more details.
|
||||
|
||||
[[Intersection Filter Run Prefix (Examples)]]
|
||||
|
||||
@@ -34,4 +34,14 @@ A named filter run prefix can precede any [[run|Filter Run]] of a [[filter expre
|
||||
|
||||
<<.tip """Within the filter runs prefixed with `:reduce`, `:sort`, `:map` and `:filter`, the <<.var currentTiddler>> variable is set to the title of the tiddler being processed.<br>The value of currentTiddler outside the subfilter is available in the variable <<.var "..currentTiddler">> <<.from-version "5.2.0">>""" >>
|
||||
|
||||
!! Suffixes
|
||||
|
||||
Named filter run prefixes can accept suffixes separated by `:` characters, each optionally containing comma-separated values. The general syntax is `:prefixname:suffix1:suffix2,...`. Currently the following prefixes accept suffixes:
|
||||
|
||||
|!Prefix |!Suffixes |!Example |
|
||||
|`:map` |`flat` — return all results instead of only the first per item |`:map:flat[...]` |
|
||||
|`:sort` |type (''string'', ''alphanumeric'', ''number'', ''integer'', ''version'', ''date'') and flags (''reverse'', ''casesensitive'', ''caseinsensitive'') |`:sort:number:reverse[...]` |
|
||||
|
||||
All other named prefixes (`:all`, `:and`, `:cascade`, `:else`, `:except`, `:filter`, `:intersection`, `:let`, `:or`, `:reduce`, `:then`) do not currently accept suffixes.
|
||||
|
||||
Also see: [[Interchangeable Filter Run Prefixes]]
|
||||
|
||||
@@ -21,10 +21,10 @@ If a run has:
|
||||
|
||||
* the prefix `-`, output titles are <<.em removed>> from the filter's output (if such tiddlers exist)
|
||||
|
||||
* the prefix `~`, if the filter output so far is an empty list then the output titles of the run are [[dominantly appended|Dominant Append]] to the filter's output. If the filter output so far is not an empty list then the run is ignored. <<.from-version "5.1.18">>
|
||||
* the prefix `~`, <<.from-version "5.1.18">> if the filter output so far is an empty list then the output titles of the run are [[dominantly appended|Dominant Append]] to the filter's output. If the filter output so far is not an empty list then the run is ignored.
|
||||
|
||||
* the prefix `=`, output titles are appended to the filter's output without de-duplication. <<.from-version "5.1.20">>
|
||||
* the prefix `=`, <<.from-version "5.1.20">> output titles are appended to the filter's output without de-duplication.
|
||||
|
||||
* the prefix `=>`, the input is assigned to the variable named with the output title. <<.from-version "5.4.0">>
|
||||
* the prefix `=>`, <<.from-version "5.4.0">> the accumulated results from all previous runs are assigned as a [[multi-valued variable|Multi-Valued Variables]] whose name is the first result of this run. The accumulated results are then cleared.
|
||||
|
||||
{{Interchangeable Filter Run Prefixes}}
|
||||
@@ -1,7 +1,7 @@
|
||||
created: 20210618133745003
|
||||
from-version: 5.3.0
|
||||
modified: 20230710074225410
|
||||
rp-input: <<.olink all>> tiddler titles
|
||||
rp-input: <<.olink all>> tiddler titles (only evaluated when the output from previous runs is non-empty)
|
||||
rp-output: the output of this filter run replaces the output of previous runs unless it is an empty list (see below)
|
||||
rp-purpose: replace any input to this filter run with its output, only evaluating the run when there is any input
|
||||
search:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
title: HelloThumbnail - Community Survey 2025
|
||||
tags: HelloThumbnail
|
||||
color: rgb(234, 205, 183)
|
||||
image: Community Survey 2025
|
||||
image: Community Survey 2025 Image
|
||||
caption: Community Survey
|
||||
link: Community Survey 2025
|
||||
ribbon-text: NEW
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
background-color: #EDB431
|
||||
caption: Intertwingled Innovations
|
||||
color: #ff0
|
||||
image: Intertwingled Innovations
|
||||
image: Intertwingled Innovations Image
|
||||
link: Intertwingled Innovations
|
||||
tags: HelloThumbnail
|
||||
title: HelloThumbnail - Intertwingled Innovations
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@@ -1,4 +1,4 @@
|
||||
alt-text: Shape the future by taking the TiddlyWiki Community Survey 2025
|
||||
tags: picture
|
||||
title: Community Survey 2025
|
||||
title: Community Survey 2025 Image
|
||||
type: image/webp
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,3 @@
|
||||
title: Intertwingled Innovations Image
|
||||
type: image/webp
|
||||
tags: picture
|
||||
@@ -1,3 +0,0 @@
|
||||
title: Intertwingled Innovations
|
||||
type: image/webp
|
||||
tags: picture
|
||||
@@ -12,3 +12,15 @@ eg="""<<tabs "[tag[sampletab]]" "SampleTabTwo" "$:/state/tab2" "tc-vertical">>""
|
||||
|
||||
<$macrocall $name=".example" n="3"
|
||||
eg="""<<tabs "[tag[sampletab]nsort[order]]" "SampleTabThree" "$:/state/tab3" "tc-vertical">>"""/>
|
||||
|
||||
The following example sets the default tab to be the first tiddler selected in the filter and makes the saved state non-persistent (by using "~$:/temp/"):
|
||||
|
||||
<$macrocall $name=".example" n="4"
|
||||
eg="""<$set name=tl filter="[tag[sampletab]nsort[order]]">
|
||||
<$transclude $variable=tabs tabsList=<<tl>> default={{{[enlist<tl>]}}} state="$:/temp/state/tab" class="tc-vertical"/>
|
||||
</$set>"""/>
|
||||
|
||||
<<.from-version "5.4.0">> Dynamic parameters can be used to specify the default tab:
|
||||
|
||||
<$macrocall $name=".example" n="5"
|
||||
eg="""<<tabs "[tag[sampletab]nsort[order]]" default={{{[tag[sampletab]nsort[order]]}}} state="$:/temp/state/tab" class="tc-vertical">>"""/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
caption: Calls
|
||||
created: 20221007130006705
|
||||
modified: 20260125212303316
|
||||
modified: 20260301030947969
|
||||
tags: WikiText Procedures Functions Macros
|
||||
title: Calls
|
||||
type: text/vnd.tiddlywiki
|
||||
@@ -25,6 +25,12 @@ Each parameter value can be enclosed in `'`single quotes`'`, `"`double quotes`"`
|
||||
|
||||
See the discussion about [[parser modes|WikiText parser mode: macro examples]]
|
||||
|
||||
!!! Examples
|
||||
|
||||
<<testcase TestCases/Calls/ProcedureStaticAttributes>>
|
||||
|
||||
<<testcase TestCases/Calls/ProcedureDynamicAttributes>>
|
||||
|
||||
!! Calls with <<.wlink TranscludeWidget>> Widget
|
||||
|
||||
The shortcut syntax expands to the <<.wlink TranscludeWidget>> widget with the `$variable` attribute specifying the name of the procedure to transclude.
|
||||
@@ -49,8 +55,10 @@ The text returned from a call can be directly assigned to an attribute of a widg
|
||||
|
||||
Calls can be used in filters. The text is not wikified which again means that the parameters will be ignored.
|
||||
|
||||
//Note that currently only literal string parameters are supported//
|
||||
|
||||
```
|
||||
<$list filter="[<my-procedure>]">
|
||||
...
|
||||
</$list>
|
||||
```
|
||||
```
|
||||
|
||||
10
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9280.tid
Normal file
10
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9280.tid
Normal file
@@ -0,0 +1,10 @@
|
||||
title: $:/changenotes/5.4.0/#9280
|
||||
description: Fixed :reverse suffix being ignored by version sort filter
|
||||
tags: $:/tags/ChangeNote
|
||||
release: 5.4.0
|
||||
change-type: bugfix
|
||||
change-category: filters
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9280
|
||||
github-contributors: Flibbles
|
||||
|
||||
When using the version suffix with the sort filter, the reverse flag had no effect. Fixed.
|
||||
12
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9297.tid
Normal file
12
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9297.tid
Normal file
@@ -0,0 +1,12 @@
|
||||
title: $:/changenotes/5.4.0/#9297
|
||||
description: Better support for downloading binary image tiddlers
|
||||
tags: $:/tags/ChangeNote
|
||||
release: 5.4.0
|
||||
change-type: enhancement
|
||||
change-category: usability
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9297
|
||||
github-contributors: Flibbles
|
||||
|
||||
TiddlyWiki used to convert to blob all tiddlers intended to be downloaded, which is efficient, but interfered with a browser's ability to download binary image data in a way that it recognized.
|
||||
|
||||
This isn't necessarily the best solution, but it makes image downloading possible in TiddlyWiki until we require a more sufficient solution.
|
||||
@@ -1,17 +0,0 @@
|
||||
title: $:/changenotes/5.4.0/#9397
|
||||
description: Fix critical freelinks bugs: first character loss and false positive matches in v5.4.0
|
||||
release: 5.4.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: plugin
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9084 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9397
|
||||
github-contributors: s793016
|
||||
|
||||
This note addresses two major bugs introduced in the Freelinks plugin with the v5.4.0 release:
|
||||
|
||||
Fixes:
|
||||
* First Character Loss: The first character of a matched word would incorrectly disappear (e.g., "The" became "he"). This was fixed by correctly timing the filtering of the current tiddler's title during match validation, ensuring proper substring handling.
|
||||
* False Positive Matches: Unrelated words (like "it is" or "Choose") would incorrectly link to a tiddler title. This was resolved by fixing wrong output merging in the Aho-Corasick failure-link handling, eliminating spurious matches from intermediate nodes, and adding cycle detection.
|
||||
|
||||
Impact:
|
||||
* Significantly improved correctness and reliability of automatic linking for all users, especially in multilingual and large wikis.
|
||||
@@ -1,9 +0,0 @@
|
||||
title: $:/changenotes/5.4.0/#9400/impacts/collator
|
||||
changenote: $:/changenotes/5.4.0/#9400
|
||||
created: 20251114102355243
|
||||
modified: 20251114102355243
|
||||
tags: $:/tags/ImpactNote
|
||||
description: Tiddlywiki no longer works on browsers that doesn't support [[Intl.Collator|https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator]]
|
||||
impact-type: compatibility-break
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
title: $:/changenotes/5.4.0/#9400
|
||||
description: Add locale support for sort operators
|
||||
release: 5.4.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: feature
|
||||
change-category: filters
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9400
|
||||
github-contributors: Leilei332
|
||||
|
||||
Add a new parameter for `sort`, `nsort`, `sortan`, `sortcs` and `nsortcs` to support sorting with a custom locale.
|
||||
48
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9676.tid
Normal file
48
editions/tw5.com/tiddlers/releasenotes/5.4.0/#9676.tid
Normal file
@@ -0,0 +1,48 @@
|
||||
title: $:/changenotes/5.4.0/#9676
|
||||
description: Fix critical freelinks bugs: first character loss and false positive matches in v5.4.0
|
||||
release: 5.4.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: plugin
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9084 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9397 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9676
|
||||
github-contributors: s793016
|
||||
|
||||
Fixes and optimizations to the Freelinks plugin's Aho-Corasick implementation following #9397.
|
||||
|
||||
Fixes:
|
||||
* Failure Links Non-Functional (Critical): The failure link map used a plain object `{}` with trie nodes as keys. Since all JavaScript objects coerce to the same string `[object Object]`, every node resolved to the same map entry. Failure links were silently broken for all overlapping patterns. Fixed by replacing with `WeakMap`.
|
||||
* Cache Rebuilt on Every UI Interaction (Performance): Any `$:/state/...` update (e.g. clicking tabs) would trigger a full Aho-Corasick rebuild, causing severe lag on large wikis. The `refresh` logic now ignores system tiddlers, with an explicit allowlist for plugin config tiddlers.
|
||||
* Short Match Blocking Longer Match: A shorter title appearing earlier (e.g. "The New") could prevent a longer overlapping title (e.g. "New York City") from matching. Replaced left-to-right greedy selection with global length-first sorting and interval occupation tracking.
|
||||
* Unicode Index Desync in ignoreCase Mode: Calling `toLowerCase()` on the full text before searching could change string length (e.g. Turkish "İ" expands), causing `substring()` to split Emoji surrogate pairs and produce garbage output. Case conversion is now done per-character during search.
|
||||
* Removed Vestigial Regex Escaping: `escapeRegExp()` was called during trie construction but Aho-Corasick operates on literal character transitions, not regex. Removed.
|
||||
|
||||
Impact:
|
||||
* Overlapping titles now match correctly for the first time.
|
||||
* No cache rebuilds during normal UI interactions on large wikis.
|
||||
* Correct longest-match behavior for titles sharing substrings.
|
||||
* Safe Emoji and complex Unicode handling in case-insensitive mode.
|
||||
|
||||
|
||||
#9397
|
||||
This note addresses two major bugs introduced in the Freelinks plugin with the v5.4.0 release:
|
||||
|
||||
Fixes:
|
||||
* First Character Loss: The first character of a matched word would incorrectly disappear (e.g., "The" became "he"). This was fixed by correctly timing the filtering of the current tiddler's title during match validation, ensuring proper substring handling.
|
||||
* False Positive Matches: Unrelated words (like "it is" or "Choose") would incorrectly link to a tiddler title. This was resolved by fixing wrong output merging in the Aho-Corasick failure-link handling, eliminating spurious matches from intermediate nodes, and adding cycle detection.
|
||||
|
||||
Impact:
|
||||
* Significantly improved correctness and reliability of automatic linking for all users, especially in multilingual and large wikis.
|
||||
|
||||
|
||||
#9084
|
||||
This change introduces a fully optimized override of the core text widget, integrating an enhanced Aho-Corasick algorithm for automatic linkification of tiddler titles within text (freelinks). The new implementation prioritizes performance for large wikis and correct support for non-Latin scripts such as Chinese.
|
||||
|
||||
Highlights:
|
||||
- Full switch from regex-based matching to a custom, robust Aho-Corasick engine dedicated to rapid, multi-pattern title detection—drastically decreasing linkification time (tested: 1–5s reduced to 100–500ms on ~12,000 tiddlers).
|
||||
- Handles extremely large title sets gracefully, including a chunked insertion process and use of a persistent cache (`$:/config/Freelinks/PersistAhoCorasickCache`) to further accelerate subsequent linking operations in large/active wikis.
|
||||
- Improvements for CJK and non-Latin text: supports linking using long or full-width symbol titles such as ':' (U+FF1A) with no split or mismatch.
|
||||
- Smart prioritization: longer titles are automatically matched before shorter, more ambiguous ones, preventing partial/incorrect linking.
|
||||
- Actively skips self-linking in the current tiddler and prevents overlapping matches for clean, deterministic linkification.
|
||||
- End users with large or multilingual wikis see massive performance boost and 100% accurate linking for complex, full-width, or multi-language titles.
|
||||
- New options for persistent match cache and word boundary checking (`$:/config/Freelinks/WordBoundary`), both can be tuned based on wiki size and content language needs.
|
||||
- Safe for gradual rollout: legacy behavior is preserved if the new freelinks override is not enabled.
|
||||
@@ -366,7 +366,7 @@ These are significant improvements that will benefit a broad range of users, and
|
||||
<$list filter="[enlist<release-github-contributors>sort[]]" variable="username">
|
||||
<li>
|
||||
<a href={{{ [[https://github.com/]addsuffix<username>] }}} class="tc-tiddlylink-external" target="_blank" rel="noopener noreferrer">
|
||||
<span class="doc-github-contributor-avatar"><img src={{{ [[https://github.com/]addsuffix<username>addsuffix[.png?size=64]] }}} width="64" height="64"/></span>
|
||||
<span class="doc-github-contributor-avatar"><img src={{{ [[https://avatars.githubusercontent.com/]addsuffix<username>addsuffix[?size=64]] }}} width="64" height="64"/></span>
|
||||
<span class="doc-github-contributor-username">@<$text text=<<username>>/></span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
created: 20130823203800000
|
||||
modified: 2020051619421803
|
||||
modified: 20260220093010752
|
||||
tags: About
|
||||
title: RoadMap
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TiddlyWiki 5 is now a mature, stable project that is relied upon by many people. Simultaneously, it is rapidly evolving in many directions thanks to the broad community of developers and users. This paradoxical situation is possible because the project strictly maintains backwards compatibility, adding new features alongside the existing ones.
|
||||
TiddlyWiki was first released in 2013, 9 years after the original [[TiddlyWiki Classic]]. Since then there have been 41 releases, making TiddlyWiki steadily more flexible and reliable. We have ambitious plans for the future as well: v5.4.x, v5.5.x, and eventually TWXX.
|
||||
|
||||
There is no formal roadmap, but quite a few areas that have yet to be fully implemented, such as search and replace, and rich text editing. Current work can be found on ~GitHub at https://github.com/TiddlyWiki/TiddlyWiki5/
|
||||
Our version numbering follows the form v5.x.0. ~TiddlyWiki is currently on v<<version>>, and so we are currently working towards v5.4.0.
|
||||
|
||||
There is a detailed project plan on GitHub, but the major milestones are planned as follows:
|
||||
|
||||
* TiddlyWiki v5.4.0 will be followed by further v5.4.x releases to fix bugs and incrementally improve features. If there are significant backwards compatibility concerns then those improvements will be pushed into v5.5.0, the next release to make minor backwards comatibility tweaks.
|
||||
* We'll go on to make v5.6.0, v5.7.0 etc releases until we're ready to release the next major redesign, that is not compromised by backwards compatibility. We are calling this release ~TiddlyWiki XX ("~TiddlyWiki Twenty", or TWXX).
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
created: 20260301005951330
|
||||
description: Procedure calls with dynamic attributes
|
||||
modified: 20260301013907635
|
||||
tags: $:/tags/wiki-test-spec
|
||||
title: TestCases/Calls/ProcedureDynamicAttributes
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
|
||||
title: Narrative
|
||||
|
||||
New-style equals sign named parameters support dynamic values
|
||||
+
|
||||
title: Output
|
||||
|
||||
\function f(a) foo [<a>] :and[join[ ]]
|
||||
\procedure p(a1) <$text text=<<a1>>/>
|
||||
|
||||
<$let v="foo 1" v2="foo 3">
|
||||
<<p a1=<<v>>>> -
|
||||
<<p a1=<<f 2>>>> -
|
||||
<<p a1=`$(v2)$`>> -
|
||||
<<p a1={{my tiddler}}>> -
|
||||
<<p a1={{my tiddler!!myfield}}>> -
|
||||
<<p a1={{{ foo 6 :and[join[ ]] }}}>>
|
||||
</$let>
|
||||
+
|
||||
title: my tiddler
|
||||
myfield: foo 5
|
||||
|
||||
foo 4
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>
|
||||
foo 1 -
|
||||
foo 2 -
|
||||
foo 3 -
|
||||
foo 4 -
|
||||
foo 5 -
|
||||
foo 6
|
||||
</p>
|
||||
@@ -0,0 +1,48 @@
|
||||
created: 20260301000418824
|
||||
description: Procedure calls with static attributes
|
||||
modified: 20260301031128810
|
||||
tags: $:/tags/wiki-test-spec
|
||||
title: TestCases/Calls/ProcedureStaticAttributes
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
|
||||
title: Narrative
|
||||
|
||||
Positional, old style colon named parameters, and new-style equals sign named parameters support static values
|
||||
+
|
||||
title: Output
|
||||
|
||||
\procedure p(a1) <$text text=<<a1>>/>
|
||||
|
||||
<<p foo1>> -
|
||||
<<p a1:foo2>> -
|
||||
<<p a1=foo3>>
|
||||
|
||||
<<p "foo 4">> -
|
||||
<<p a1:"foo 5">> -
|
||||
<<p a1="foo 6">>
|
||||
|
||||
<<p 'foo 7'>> -
|
||||
<<p a1:'foo 8'>> -
|
||||
<<p a1='foo 9'>>
|
||||
|
||||
<<p """foo 10""">> -
|
||||
<<p a1:"""foo 11""">> -
|
||||
<<p a1="""foo 12""">>
|
||||
|
||||
<<p [[foo 13]]>> -
|
||||
<<p a1:[[foo 14]]>> -
|
||||
<<p a1=[[foo 15]]>>
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>foo1 -
|
||||
foo2 -
|
||||
foo3</p><p>foo 4 -
|
||||
foo 5 -
|
||||
foo 6</p><p>foo 7 -
|
||||
foo 8 -
|
||||
foo 9</p><p>foo 10 -
|
||||
foo 11 -
|
||||
foo 12</p><p>foo 13 -
|
||||
foo 14 -
|
||||
foo 15</p>
|
||||
@@ -79,3 +79,14 @@ The following is a table with maximum width. It contains [[TextWidget]]s with ma
|
||||
| Cell1|<$edit-text tiddler="$:/temp/test-table-input" tag="input" field="test"/> |
|
||||
|^ [[Link to a tiddler]]<br>some more text|<$edit-text tiddler="$:/temp/test-table-input" field="text"/> |
|
||||
""">>
|
||||
|
||||
!! Table with Alternating Row Styles
|
||||
|
||||
You will need to create your own stylesheet. See [[Using Stylesheets]]. The idea is to create rules using `:nth-of-type(even/odd)`. For example:
|
||||
|
||||
```
|
||||
.myclass tbody tr:nth-of-type(even) { background-color: <<color tiddler-editor-fields-even>>; }
|
||||
.myclass tbody tr:nth-of-type(odd) { background-color: <<color tiddler-editor-fields-odd>>; }
|
||||
```
|
||||
|
||||
<<.note """~TiddlyWiki automatically applies classes `evenRow` and `oddRow` to table rows. However, use of these classes is not recommended. Rows start as 'even' (opposite to that expected) and all the rows of the table are considered to be a single combined set of rows, regardless of if they appear in the header, footer, or body.""">>
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
"tiddlywiki/browser-sniff",
|
||||
"tiddlywiki/confetti",
|
||||
"tiddlywiki/dynannotate",
|
||||
"tiddlywiki/tour",
|
||||
"tiddlywiki/internals",
|
||||
"tiddlywiki/menubar",
|
||||
"tiddlywiki/railroad"
|
||||
"tiddlywiki/railroad",
|
||||
"tiddlywiki/tour",
|
||||
"tiddlywiki/dom-to-image"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
|
||||
@@ -96,6 +96,10 @@ Plugins/PluginWillRequireReload: (rechargement requis)
|
||||
Plugins/Plugins/Caption: Plugins
|
||||
Plugins/Plugins/Hint: Plugins
|
||||
Plugins/Reinstall/Caption: réinstalle
|
||||
Plugins/Stability/Deprecated: DÉPRÉCIÉ
|
||||
Plugins/Stability/Experimental: EXPÉRIMENTAL
|
||||
Plugins/Stability/Legacy: LEGACY
|
||||
Plugins/Stability/Stable: STABLE
|
||||
Plugins/Themes/Caption: Thèmes
|
||||
Plugins/Themes/Hint: Plugins de thème
|
||||
Plugins/Update/Caption: mise à jour
|
||||
@@ -128,17 +132,16 @@ Saving/GitService/Gitea/Password: //Jeton d'accès// pour cette API (via l'inter
|
||||
Saving/TiddlySpot/Advanced/Heading: Paramètres avancés
|
||||
Saving/TiddlySpot/BackupDir: Dossier des //sauvegardes//
|
||||
Saving/TiddlySpot/Backups: Sauvegardes
|
||||
Saving/TiddlySpot/Caption: Enregistreur ~TiddlySpot
|
||||
Saving/TiddlySpot/ControlPanel: Panneau de contrôle ~TiddlySpot
|
||||
Saving/TiddlySpot/Description: Ces paramètres ne servent que lors de la sauvegarde vers [[TiddlySpot|http://tiddlyspot.com]], [[TiddlyHost|https://tiddlyhost.com]] ou vers un serveur distant compatible. Voir [[ici|https://github.com/simonbaird/tiddlyhost/wiki/TiddlySpot-Saver-configuration-for-Tiddlyhost-and-Tiddlyspot]] pour plus d'informations sur la configuration de la sauvegarde sur ~TiddlySpot et ~TiddlyHost.
|
||||
Saving/TiddlySpot/Caption: Enregistreur ~TiddlyHost
|
||||
Saving/TiddlySpot/ControlPanel: Panneau de contrôle ~TiddlyHost
|
||||
Saving/TiddlySpot/Description: Ces paramètres ne servent que lors de la sauvegarde vers [[TiddlyHost|https://tiddlyhost.com]] ou vers un serveur distant compatible. Voir [[ici|https://github.com/simonbaird/tiddlyhost/wiki/TiddlySpot-Saver-configuration-for-Tiddlyhost-and-Tiddlyspot]] pour plus d'informations sur la configuration de la sauvegarde sur ~TiddlyHost.
|
||||
Saving/TiddlySpot/Filename: Nom du fichier enregistré
|
||||
Saving/TiddlySpot/Heading: ~TiddlySpot
|
||||
Saving/TiddlySpot/Heading: ~TiddlyHost
|
||||
Saving/TiddlySpot/Hint: //L'URL par défaut est `http://<nom du wiki>.tiddlyspot.com/store.cgi`. Elle peut être remplacée par une adresse serveur personnalisée, comme `http://example.com/store.php`.//
|
||||
Saving/TiddlySpot/Password: Mot de passe
|
||||
Saving/TiddlySpot/ReadOnly: Notez que [[TiddlySpot|http://tiddlyspot.com]] n'autorise plus la création de nouveaux sites. Pour les nouveaux sites vous pouvez utiliser [[TiddlyHost|https://tiddlyhost.com]], un nouveau service d'hébergement qui remplace ~TiddlySpot.
|
||||
Saving/TiddlySpot/ServerURL: URL du serveur
|
||||
Saving/TiddlySpot/UploadDir: Dossier des dépôts
|
||||
Saving/TiddlySpot/UserName: Nom utilisé pour ce Wiki
|
||||
Saving/TiddlySpot/UserName: Nom du Wiki
|
||||
Settings/AutoSave/Caption: Sauvegarde automatique
|
||||
Settings/AutoSave/Disabled/Description: Pas de sauvegarde automatique des modifications
|
||||
Settings/AutoSave/Enabled/Description: Sauvegarde automatique des modifications
|
||||
@@ -184,6 +187,8 @@ Settings/DefaultSidebarTab/Caption: Onglet par défaut sur la barre latérale
|
||||
Settings/DefaultSidebarTab/Hint: Indique l'onglet de la barre latérale qui sera affiché par défaut
|
||||
Settings/DefaultMoreSidebarTab/Caption: Onglet par défaut sous le Plus de la barre latérale
|
||||
Settings/DefaultMoreSidebarTab/Hint: Indique quel onglet sera affiché par défaut sous le Plus de la barre latérale
|
||||
Settings/DefaultTiddlerInfoTab/Caption: Onglet par défaut pour les Infos du tiddler
|
||||
Settings/DefaultTiddlerInfoTab/Hint: Spécifie l'onglet affiché par défaut lorsque le panneau d'information du tiddler est ouvert
|
||||
Settings/LinkToBehaviour/Caption: Comportement à l'ouverture du tiddler
|
||||
Settings/LinkToBehaviour/InsideRiver/Hint: Navigation depuis un emplacement //interne// au déroulé
|
||||
Settings/LinkToBehaviour/OutsideRiver/Hint: Navigation depuis un emplacement //externe// au déroulé
|
||||
@@ -245,3 +250,6 @@ ViewTemplateSubtitle/Caption: Visualisation du sous-titre
|
||||
ViewTemplateSubtitle/Hint: Cette cascade de règles est utilisée par le template de visualisation par défaut pour choisir dynamiquement le template d'affichage du sous-titre d'un tiddler.
|
||||
ViewTemplateTags/Caption: Visualisation des tags
|
||||
ViewTemplateTags/Hint: Cette cascade de règles est utilisée par le template de visualisation par défaut pour choisir dynamiquement le template d'affichage de la zone de tags d'un tiddler.
|
||||
WikiInformation/Caption: Informations sur le Wiki
|
||||
WikiInformation/Hint: Cette page rassemble quelques informations générales sur la configuration de ce ~TiddlyWiki. Elle est conçue pour permettre de partager rapidement certains aspects de sa configuration avec d'autres, par exemple lorsqu'on demande de l'aide sur un forum. Elle ne contient aucune information privée ni personnelle, et rien n'est partagé sans être explicitement copié-collé ailleurs.
|
||||
WikiInformation/Drag/Caption: Déplacer ce lien pour copier cet outil sur un autre wiki
|
||||
@@ -1,5 +1,6 @@
|
||||
title: $:/language/
|
||||
|
||||
Alerts: Alertes
|
||||
AboveStory/ClassicPlugin/Warning: On dirait que vous essayez de charger un plugin conçu pour ~TiddlyWiki Classic. Merci de noter que [[ces plugins ne fonctionnent pas avec TiddlyWiki version 5.x.x|https://tiddlywiki.com/#TiddlyWikiClassic]]. Plugins ~TiddlyWiki Classic détectés :
|
||||
BinaryWarning/Prompt: Ce tiddler contient des données binaires
|
||||
ClassicWarning/Hint: Ce tiddler est écrit au format TiddlyWiki Classic, qui n'est pas entièrement compatible avec TiddlyWiki version 5. Pour en savoir plus, rendez-vous à l'adresse https://tiddlywiki.com/static/Upgrading.html.
|
||||
|
||||
@@ -645,3 +645,7 @@ Rishu kumar, @rishu-7549, 2025/10/25
|
||||
@peteratkins, 2025/12/29
|
||||
|
||||
@hsteve11, 2025/12/30
|
||||
|
||||
@kjharcombe, 2026/03/16
|
||||
|
||||
Himmel, @NotHimmel, 2026/03/19
|
||||
|
||||
@@ -40,6 +40,11 @@ module.exports = defineConfig({
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
{
|
||||
name: 'edge',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'msedge' },
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,85 +8,85 @@ dom-to-image initialisation
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
// Export name and synchronous status
|
||||
exports.name = "dom-to-image";
|
||||
exports.after = ["rootwidget"];
|
||||
exports.before = ["render"];
|
||||
exports.synchronous = true;
|
||||
// Export name and synchronous status
|
||||
exports.name = "dom-to-image";
|
||||
exports.after = ["rootwidget"];
|
||||
exports.before = ["render"];
|
||||
exports.synchronous = true;
|
||||
|
||||
exports.startup = function() {
|
||||
var getPropertiesWithPrefix = function(properties,prefix) {
|
||||
var result = Object.create(null);
|
||||
$tw.utils.each(properties,function(value,name) {
|
||||
if(name.indexOf(prefix) === 0) {
|
||||
result[name.substring(prefix.length)] = properties[name];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
$tw.rootWidget.addEventListener("tm-save-dom-to-image",function(event) {
|
||||
var self=this,
|
||||
params = event.paramObject || {},
|
||||
domToImage = require("$:/plugins/tiddlywiki/dom-to-image/dom-to-image-more.js"),
|
||||
domNode = document.querySelector(params.selector || "body.tc-body"),
|
||||
oncompletion = params.oncompletion,
|
||||
variables = getPropertiesWithPrefix(params,"var-");
|
||||
if(domNode) {
|
||||
var method = "toPng";
|
||||
switch(params.format) {
|
||||
case "jpeg":
|
||||
// Intentional fallthrough
|
||||
case "jpg":
|
||||
method = "toJpeg";
|
||||
break;
|
||||
case "svg":
|
||||
method = "toSvg";
|
||||
break;
|
||||
}
|
||||
domToImage[method](domNode,{
|
||||
height: $tw.utils.parseInt(params.height) || domNode.offsetHeight,
|
||||
width: $tw.utils.parseInt(params.width) || domNode.offsetWidth,
|
||||
quality: $tw.utils.parseNumber(params.quality),
|
||||
scale: $tw.utils.parseNumber(params.scale) || 1
|
||||
})
|
||||
.then(function(dataUrl) {
|
||||
// Save the image
|
||||
if(params["save-file"]) {
|
||||
var link = document.createElement("a");
|
||||
link.download = params["save-file"];
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
}
|
||||
// Save the tiddler
|
||||
if(params["save-title"]) {
|
||||
if(dataUrl.indexOf("data:image/svg+xml;") === 0) {
|
||||
var commaIndex = dataUrl.indexOf(",");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: "image/svg+xml",
|
||||
"text": decodeURIComponent(dataUrl.substring(commaIndex + 1))
|
||||
}));
|
||||
} else {
|
||||
var parts = dataUrl.split(";base64,");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: parts[0].split(":")[1],
|
||||
"text": parts[1]
|
||||
}));
|
||||
}
|
||||
}
|
||||
self.wiki.invokeActionString(oncompletion,self,variables,{parentWidget: $tw.rootWidget});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error("oops, something went wrong!", error);
|
||||
});
|
||||
exports.startup = function() {
|
||||
var getPropertiesWithPrefix = function(properties,prefix) {
|
||||
var result = Object.create(null);
|
||||
$tw.utils.each(properties,function(value,name) {
|
||||
if(name.indexOf(prefix) === 0) {
|
||||
result[name.substring(prefix.length)] = properties[name];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
$tw.rootWidget.addEventListener("tm-save-dom-to-image",function(event) {
|
||||
var self=this,
|
||||
params = event.paramObject || {},
|
||||
domToImage = require("$:/plugins/tiddlywiki/dom-to-image/dom-to-image-more.js"),
|
||||
domNode = document.querySelector(params.selector || "body.tc-body"),
|
||||
oncompletion = params.oncompletion,
|
||||
variables = getPropertiesWithPrefix(params,"var-");
|
||||
if(domNode) {
|
||||
var method = "toPng";
|
||||
switch(params.format) {
|
||||
case "jpeg":
|
||||
// Intentional fallthrough
|
||||
case "jpg":
|
||||
method = "toJpeg";
|
||||
break;
|
||||
case "svg":
|
||||
method = "toSvg";
|
||||
break;
|
||||
}
|
||||
domToImage[method](domNode,{
|
||||
height: $tw.utils.parseInt(params.height) || domNode.offsetHeight,
|
||||
width: $tw.utils.parseInt(params.width) || domNode.offsetWidth,
|
||||
quality: $tw.utils.parseNumber(params.quality),
|
||||
scale: $tw.utils.parseNumber(params.scale) || 1
|
||||
})
|
||||
.then(function(dataUrl) {
|
||||
// Save the image
|
||||
if(params["save-file"]) {
|
||||
var link = document.createElement("a");
|
||||
link.download = params["save-file"];
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
}
|
||||
// Save the tiddler
|
||||
if(params["save-title"]) {
|
||||
if(dataUrl.indexOf("data:image/svg+xml;") === 0) {
|
||||
var commaIndex = dataUrl.indexOf(",");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: "image/svg+xml",
|
||||
"text": decodeURIComponent(dataUrl.substring(commaIndex + 1))
|
||||
}));
|
||||
} else {
|
||||
var parts = dataUrl.split(";base64,");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: parts[0].split(":")[1],
|
||||
"text": parts[1]
|
||||
}));
|
||||
}
|
||||
}
|
||||
self.wiki.invokeActionString(oncompletion,self,variables,{parentWidget: $tw.rootWidget});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error("oops, something went wrong!", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -155,7 +155,7 @@ function updateAddressBar() {
|
||||
var top = findTopmostTiddler();
|
||||
if(top.element) {
|
||||
var hash = "#" + encodeURIComponent(top.title) + ":" + encodeURIComponent("[list[$:/StoryList]]");
|
||||
if(title && $tw.locationHash !== hash) {
|
||||
if(top.title && $tw.locationHash !== hash) {
|
||||
$tw.locationHash = hash;
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
/*\
|
||||
|
||||
title: $:/core/modules/utils/aho-corasick.js
|
||||
type: application/javascript
|
||||
module-type: utils
|
||||
|
||||
Optimized Aho-Corasick string matching algorithm implementation with enhanced performance and error handling for TiddlyWiki freelinking functionality.
|
||||
Optimized Aho-Corasick string matching algorithm implementation with enhanced performance
|
||||
and error handling for TiddlyWiki freelinking functionality.
|
||||
|
||||
Useage:
|
||||
- Uses WeakMap for failure links (required; plain object keys would collide).
|
||||
- search() converts case per character to avoid Unicode index desync.
|
||||
- Optional word boundary filtering: CJK always allowed; Latin requires non-word chars around.
|
||||
|
||||
Initialization:
|
||||
Create an AhoCorasick instance: var ac = new AhoCorasick();
|
||||
After initialization, the trie and failure structures are automatically created to store patterns and failure links.
|
||||
|
||||
Adding Patterns:
|
||||
Call addPattern(pattern, index) to add a pattern, e.g., ac.addPattern("[[Link]]", 0);.
|
||||
pattern is the string to match, and index is an identifier for tracking results.
|
||||
Multiple patterns can be added, stored in the trie structure.
|
||||
|
||||
Building Failure Links:
|
||||
Call buildFailureLinks() to construct failure links for efficient multi-pattern matching.
|
||||
Includes a maximum node limit (default 100,000 or 15 times the pattern count) to prevent excessive computation.
|
||||
|
||||
Performing Search:
|
||||
Use search(text, useWordBoundary) to find pattern matches in the text.
|
||||
text is the input string, and useWordBoundary (boolean) controls whether to enforce word boundary checks.
|
||||
Returns an array of match results, each containing pattern (matched pattern), index (start position), length (pattern length), and titleIndex (pattern identifier).
|
||||
|
||||
Word Boundary Check:
|
||||
If useWordBoundary is true, only matches surrounded by non-word characters (letters, digits, or underscores) are returned.
|
||||
|
||||
Cleanup and Statistics:
|
||||
Use clear() to reset the trie and failure links, freeing memory.
|
||||
Use getStats() to retrieve statistics, including node count (nodeCount), pattern count (patternCount), and failure link count (failureLinks).
|
||||
|
||||
Notes
|
||||
Performance Considerations: The Aho-Corasick trie may consume significant memory with a large number of patterns. Limit the number of patterns (e.g., <10,000) for optimal performance.
|
||||
Error Handling: The module includes maximum node and failure depth limits (maxFailureDepth) to prevent infinite loops or memory overflow.
|
||||
Word Boundary: Enabling useWordBoundary ensures more precise matches, ideal for link detection scenarios.
|
||||
Compatibility: Ensure compatibility with other TiddlyWiki modules (e.g., wikiparser.js) when processing WikiText.
|
||||
Debugging: Use getStats() to inspect the trie structure's size and ensure it does not overload browser memory.
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
function AhoCorasick() {
|
||||
this.trie = {};
|
||||
this.failure = {};
|
||||
this.failure = new WeakMap();
|
||||
this.maxFailureDepth = 100;
|
||||
this.patternCount = 0;
|
||||
}
|
||||
@@ -54,198 +26,164 @@ AhoCorasick.prototype.addPattern = function(pattern, index) {
|
||||
if(!pattern || typeof pattern !== "string" || pattern.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var node = this.trie;
|
||||
|
||||
for(var i = 0; i < pattern.length; i++) {
|
||||
var char = pattern[i];
|
||||
if(!node[char]) {
|
||||
node[char] = {};
|
||||
var ch = pattern[i];
|
||||
if(!node[ch]) {
|
||||
node[ch] = {};
|
||||
}
|
||||
node = node[char];
|
||||
node = node[ch];
|
||||
}
|
||||
|
||||
if(!node.$) {
|
||||
node.$ = [];
|
||||
}
|
||||
node.$.push({
|
||||
pattern: pattern,
|
||||
node.$.push({
|
||||
pattern: pattern,
|
||||
index: index,
|
||||
length: pattern.length
|
||||
});
|
||||
|
||||
this.patternCount++;
|
||||
};
|
||||
|
||||
AhoCorasick.prototype.buildFailureLinks = function() {
|
||||
var queue = [];
|
||||
var root = this.trie;
|
||||
this.failure[root] = root;
|
||||
|
||||
for(var char in root) {
|
||||
if(root[char] && char !== "$") {
|
||||
this.failure[root[char]] = root;
|
||||
queue.push(root[char]);
|
||||
var self = this;
|
||||
|
||||
this.failure = new WeakMap();
|
||||
this.failure.set(root, root);
|
||||
|
||||
for(var ch in root) {
|
||||
if(ch === "$") continue;
|
||||
if(root[ch] && typeof root[ch] === "object") {
|
||||
this.failure.set(root[ch], root);
|
||||
queue.push(root[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var processedNodes = 0;
|
||||
var maxNodes = Math.max(100000, this.patternCount * 15);
|
||||
|
||||
while(queue.length > 0 && processedNodes < maxNodes) {
|
||||
var node = queue.shift();
|
||||
processedNodes++;
|
||||
|
||||
for(var char in node) {
|
||||
if(node[char] && char !== "$") {
|
||||
var child = node[char];
|
||||
var fail = this.failure[node];
|
||||
var failureDepth = 0;
|
||||
|
||||
while(fail && !fail[char] && failureDepth < this.maxFailureDepth) {
|
||||
fail = this.failure[fail];
|
||||
failureDepth++;
|
||||
}
|
||||
|
||||
var failureLink = (fail && fail[char]) ? fail[char] : root;
|
||||
this.failure[child] = failureLink;
|
||||
|
||||
// Do not merge outputs from failure links during build
|
||||
// Instead, collect matches dynamically by traversing failure links during search
|
||||
|
||||
queue.push(child);
|
||||
while(queue.length > 0) {
|
||||
if(processedNodes++ >= maxNodes) {
|
||||
throw new Error("Aho-Corasick: buildFailureLinks exceeded maximum nodes (" + maxNodes + ")");
|
||||
}
|
||||
var node = queue.shift();
|
||||
|
||||
for(var edge in node) {
|
||||
if(edge === "$") continue;
|
||||
var child = node[edge];
|
||||
if(!child || typeof child !== "object") continue;
|
||||
|
||||
var fail = self.failure.get(node) || root;
|
||||
var depth = 0;
|
||||
|
||||
while(fail !== root && !fail[edge] && depth < self.maxFailureDepth) {
|
||||
fail = self.failure.get(fail) || root;
|
||||
depth++;
|
||||
}
|
||||
|
||||
var nextFail = (fail[edge] && fail[edge] !== child) ? fail[edge] : root;
|
||||
self.failure.set(child, nextFail);
|
||||
|
||||
if(nextFail.$) {
|
||||
if(!child.$) child.$ = [];
|
||||
child.$ = child.$.concat(nextFail.$);
|
||||
}
|
||||
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
if(processedNodes >= maxNodes) {
|
||||
throw new Error("Aho-Corasick: buildFailureLinks exceeded maximum nodes (" + maxNodes + ")");
|
||||
}
|
||||
};
|
||||
|
||||
AhoCorasick.prototype.search = function(text, useWordBoundary) {
|
||||
AhoCorasick.prototype.search = function(text, useWordBoundary, ignoreCase) {
|
||||
if(!text || typeof text !== "string" || text.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
var matches = [];
|
||||
var node = this.trie;
|
||||
var root = this.trie;
|
||||
var textLength = text.length;
|
||||
|
||||
var maxMatches = Math.min(textLength * 2, 10000);
|
||||
|
||||
|
||||
for(var i = 0; i < textLength; i++) {
|
||||
var char = text[i];
|
||||
var transitionCount = 0;
|
||||
|
||||
// Follow failure links to find a valid transition
|
||||
while(node && !node[char] && node !== this.trie && transitionCount < this.maxFailureDepth) {
|
||||
node = this.failure[node] || this.trie;
|
||||
transitionCount++;
|
||||
var ch = ignoreCase ? text[i].toLowerCase() : text[i];
|
||||
|
||||
while(node !== root && !node[ch]) {
|
||||
node = this.failure.get(node) || root;
|
||||
}
|
||||
|
||||
if(node && node[char]) {
|
||||
node = node[char];
|
||||
} else {
|
||||
node = this.trie;
|
||||
if(this.trie[char]) {
|
||||
node = this.trie[char];
|
||||
}
|
||||
if(node[ch]) {
|
||||
node = node[ch];
|
||||
}
|
||||
|
||||
// Traverse the current node and its failure link chain to gather all patterns
|
||||
var currentNode = node;
|
||||
var collectCount = 0;
|
||||
var visitedNodes = new Set();
|
||||
|
||||
while(currentNode && collectCount < 10) {
|
||||
// Prevent infinite loops
|
||||
if(visitedNodes.has(currentNode)) {
|
||||
break;
|
||||
}
|
||||
visitedNodes.add(currentNode);
|
||||
|
||||
// Only collect outputs from the current node (not merged ones)
|
||||
if(currentNode.$) {
|
||||
var outputs = currentNode.$;
|
||||
for(var j = 0; j < outputs.length && matches.length < maxMatches; j++) {
|
||||
var output = outputs[j];
|
||||
var matchStart = i - output.length + 1;
|
||||
var matchEnd = i + 1;
|
||||
|
||||
var matchedText = text.substring(matchStart, matchEnd);
|
||||
if(matchedText !== output.pattern) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(useWordBoundary && !this.isWordBoundaryMatch(text, matchStart, matchEnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
matches.push({
|
||||
pattern: output.pattern,
|
||||
index: matchStart,
|
||||
length: output.length,
|
||||
titleIndex: output.index
|
||||
});
|
||||
|
||||
if(node.$) {
|
||||
var outputs = node.$;
|
||||
for(var j = 0; j < outputs.length && matches.length < maxMatches; j++) {
|
||||
var out = outputs[j];
|
||||
var matchStart = i - out.length + 1;
|
||||
var matchEnd = i + 1;
|
||||
if(matchStart < 0) continue;
|
||||
|
||||
if(useWordBoundary && !this.isWordBoundaryMatch(text, matchStart, matchEnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
matches.push({
|
||||
pattern: out.pattern,
|
||||
index: matchStart,
|
||||
length: out.length,
|
||||
titleIndex: out.index
|
||||
});
|
||||
}
|
||||
|
||||
currentNode = this.failure[currentNode];
|
||||
if(currentNode === this.trie) break;
|
||||
collectCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
AhoCorasick.prototype.isWordBoundaryMatch = function(text, start, end) {
|
||||
var matchedText = text.substring(start, end);
|
||||
|
||||
if(/[\u3400-\u9FFF\uF900-\uFAFF]/.test(matchedText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var beforeChar = start > 0 ? text[start - 1] : "";
|
||||
var afterChar = end < text.length ? text[end] : "";
|
||||
|
||||
var isWordChar = function(char) {
|
||||
|
||||
var isLatinWordChar = function(char) {
|
||||
return /[a-zA-Z0-9_\u00C0-\u00FF]/.test(char);
|
||||
};
|
||||
|
||||
var beforeIsWord = beforeChar && isWordChar(beforeChar);
|
||||
var afterIsWord = afterChar && isWordChar(afterChar);
|
||||
|
||||
return !beforeIsWord && !afterIsWord;
|
||||
|
||||
return !isLatinWordChar(beforeChar) && !isLatinWordChar(afterChar);
|
||||
};
|
||||
|
||||
AhoCorasick.prototype.clear = function() {
|
||||
this.trie = {};
|
||||
this.failure = {};
|
||||
this.failure = new WeakMap();
|
||||
this.patternCount = 0;
|
||||
};
|
||||
|
||||
AhoCorasick.prototype.getStats = function() {
|
||||
var nodeCount = 0;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var patternCount = 0;
|
||||
var failureCount = 0;
|
||||
|
||||
function countNodes(node) {
|
||||
if(!node) return;
|
||||
nodeCount++;
|
||||
if(node.$) {
|
||||
patternCount += node.$.length;
|
||||
}
|
||||
for(var key in node) {
|
||||
if(node[key] && typeof node[key] === "object" && key !== "$") {
|
||||
if(key === "$") continue;
|
||||
if(node[key] && typeof node[key] === "object") {
|
||||
countNodes(node[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countNodes(this.trie);
|
||||
|
||||
failureCount += Object.keys(this.failure).length;
|
||||
|
||||
|
||||
return {
|
||||
nodeCount: nodeCount,
|
||||
patternCount: this.patternCount,
|
||||
failureLinks: failureCount
|
||||
failureLinks: this.patternCount
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/*\
|
||||
|
||||
title: $:/core/modules/widgets/text.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
An optimized override of the core text widget that automatically linkifies the text, with support for non-Latin languages like Chinese, prioritizing longer titles, skipping processed matches, excluding the current tiddler title from linking, and handling large title sets with enhanced Aho-Corasick algorithm.
|
||||
Optimized override of the core text widget that automatically linkifies text.
|
||||
- Supports non-Latin languages like Chinese.
|
||||
- Global longest-match priority, then removes overlaps.
|
||||
- Excludes current tiddler title from linking.
|
||||
- Uses Aho-Corasick for performance.
|
||||
|
||||
\*/
|
||||
|
||||
@@ -18,28 +23,6 @@ var Widget = require("$:/core/modules/widgets/widget.js").widget,
|
||||
ElementWidget = require("$:/core/modules/widgets/element.js").element,
|
||||
AhoCorasick = require("$:/core/modules/utils/aho-corasick.js").AhoCorasick;
|
||||
|
||||
var ESCAPE_REGEX = /[\\^$*+?.()|[\]{}]/g;
|
||||
|
||||
function escapeRegExp(str) {
|
||||
try {
|
||||
return str.replace(ESCAPE_REGEX, "\\$&");
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function FastPositionSet() {
|
||||
this.set = new Set();
|
||||
}
|
||||
|
||||
FastPositionSet.prototype.add = function(pos) {
|
||||
this.set.add(pos);
|
||||
};
|
||||
|
||||
FastPositionSet.prototype.has = function(pos) {
|
||||
return this.set.has(pos);
|
||||
};
|
||||
|
||||
var TextNodeWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
@@ -54,138 +37,121 @@ TextNodeWidget.prototype.render = function(parent,nextSibling) {
|
||||
};
|
||||
|
||||
TextNodeWidget.prototype.execute = function() {
|
||||
var self = this,
|
||||
ignoreCase = self.getVariable("tv-freelinks-ignore-case",{defaultValue:"no"}).trim() === "yes";
|
||||
|
||||
var self = this;
|
||||
var ignoreCase = self.getVariable("tv-freelinks-ignore-case",{defaultValue:"no"}).trim() === "yes";
|
||||
|
||||
var childParseTree = [{
|
||||
type: "plain-text",
|
||||
text: this.getAttribute("text",this.parseTreeNode.text || "")
|
||||
}];
|
||||
|
||||
|
||||
var text = childParseTree[0].text;
|
||||
|
||||
if(!text || text.length < 2) {
|
||||
this.makeChildWidgets(childParseTree);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.getVariable("tv-wikilinks",{defaultValue:"yes"}) !== "no" &&
|
||||
this.getVariable("tv-freelinks",{defaultValue:"no"}) === "yes" &&
|
||||
!this.isWithinButtonOrLink()) {
|
||||
|
||||
|
||||
if(this.getVariable("tv-wikilinks",{defaultValue:"yes"}) !== "no" &&
|
||||
this.getVariable("tv-freelinks",{defaultValue:"no"}) === "yes" &&
|
||||
!this.isWithinButtonOrLink()) {
|
||||
|
||||
var currentTiddlerTitle = this.getVariable("currentTiddler") || "";
|
||||
var useWordBoundary = self.wiki.getTiddlerText(WORD_BOUNDARY_TIDDLER, "no") === "yes";
|
||||
|
||||
var useWordBoundary = self.wiki.getTiddlerText(WORD_BOUNDARY_TIDDLER,"no") === "yes";
|
||||
|
||||
var cacheKey = "tiddler-title-info-" + (ignoreCase ? "insensitive" : "sensitive");
|
||||
|
||||
this.tiddlerTitleInfo = this.wiki.getGlobalCache(cacheKey, function() {
|
||||
return computeTiddlerTitleInfo(self, ignoreCase);
|
||||
this.tiddlerTitleInfo = this.wiki.getGlobalCache(cacheKey,function() {
|
||||
return computeTiddlerTitleInfo(self,ignoreCase);
|
||||
});
|
||||
|
||||
if(this.tiddlerTitleInfo.titles.length > 0) {
|
||||
var newParseTree = this.processTextWithMatches(text, currentTiddlerTitle, ignoreCase, useWordBoundary);
|
||||
if(newParseTree && newParseTree.length > 0 &&
|
||||
(newParseTree.length > 1 || newParseTree[0].type !== "plain-text")) {
|
||||
|
||||
if(this.tiddlerTitleInfo && this.tiddlerTitleInfo.titles && this.tiddlerTitleInfo.titles.length > 0 && this.tiddlerTitleInfo.ac) {
|
||||
var newParseTree = this.processTextWithMatches(text,currentTiddlerTitle,ignoreCase,useWordBoundary);
|
||||
if(newParseTree && newParseTree.length > 0 &&
|
||||
(newParseTree.length > 1 || newParseTree[0].type !== "plain-text")) {
|
||||
childParseTree = newParseTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.makeChildWidgets(childParseTree);
|
||||
};
|
||||
|
||||
TextNodeWidget.prototype.processTextWithMatches = function(text, currentTiddlerTitle, ignoreCase, useWordBoundary) {
|
||||
TextNodeWidget.prototype.processTextWithMatches = function(text,currentTiddlerTitle,ignoreCase,useWordBoundary) {
|
||||
if(!text || text.length === 0) {
|
||||
return [{type: "plain-text", text: text}];
|
||||
}
|
||||
|
||||
var searchText = ignoreCase ? text.toLowerCase() : text;
|
||||
|
||||
var matches;
|
||||
|
||||
try {
|
||||
matches = this.tiddlerTitleInfo.ac.search(searchText, useWordBoundary);
|
||||
matches = this.tiddlerTitleInfo.ac.search(text, useWordBoundary, ignoreCase);
|
||||
} catch(e) {
|
||||
return [{type: "plain-text", text: text}];
|
||||
}
|
||||
|
||||
|
||||
if(!matches || matches.length === 0) {
|
||||
return [{type: "plain-text", text: text}];
|
||||
}
|
||||
|
||||
matches.sort(function(a, b) {
|
||||
if(a.index !== b.index) {
|
||||
return a.index - b.index;
|
||||
}
|
||||
return b.length - a.length;
|
||||
|
||||
var titleToCompare = ignoreCase ?
|
||||
(currentTiddlerTitle ? currentTiddlerTitle.toLowerCase() : "") :
|
||||
currentTiddlerTitle;
|
||||
|
||||
matches.sort(function(a,b) {
|
||||
if(b.length !== a.length) return b.length - a.length;
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
var processedPositions = new FastPositionSet();
|
||||
|
||||
var occupied = new Uint8Array(text.length);
|
||||
var validMatches = [];
|
||||
|
||||
|
||||
for(var i = 0; i < matches.length; i++) {
|
||||
var match = matches[i];
|
||||
var matchStart = match.index;
|
||||
var matchEnd = matchStart + match.length;
|
||||
|
||||
if(matchStart < 0 || matchEnd > text.length) {
|
||||
continue;
|
||||
var m = matches[i];
|
||||
var start = m.index;
|
||||
var end = start + m.length;
|
||||
if(start < 0 || end > text.length) continue;
|
||||
|
||||
var matchedTitle = this.tiddlerTitleInfo.titles[m.titleIndex];
|
||||
if(!matchedTitle) continue;
|
||||
|
||||
var matchedTitleToCompare = ignoreCase ? matchedTitle.toLowerCase() : matchedTitle;
|
||||
if(titleToCompare && matchedTitleToCompare === titleToCompare) continue;
|
||||
|
||||
var overlapping = false;
|
||||
for(var j = start; j < end; j++) {
|
||||
if(occupied[j]) { overlapping = true; break; }
|
||||
}
|
||||
|
||||
var matchedTitle = this.tiddlerTitleInfo.titles[match.titleIndex];
|
||||
|
||||
var titleToCompare = ignoreCase ?
|
||||
(currentTiddlerTitle ? currentTiddlerTitle.toLowerCase() : "") :
|
||||
currentTiddlerTitle;
|
||||
var matchedTitleToCompare = ignoreCase ?
|
||||
(matchedTitle ? matchedTitle.toLowerCase() : "") :
|
||||
matchedTitle;
|
||||
|
||||
if(titleToCompare && matchedTitleToCompare === titleToCompare) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasOverlap = false;
|
||||
for(var pos = matchStart; pos < matchEnd && !hasOverlap; pos++) {
|
||||
if(processedPositions.has(pos)) {
|
||||
hasOverlap = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasOverlap) {
|
||||
for(var pos = matchStart; pos < matchEnd; pos++) {
|
||||
processedPositions.add(pos);
|
||||
}
|
||||
validMatches.push(match);
|
||||
if(overlapping) continue;
|
||||
|
||||
validMatches.push(m);
|
||||
for(var k = start; k < end; k++) {
|
||||
occupied[k] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(validMatches.length === 0) {
|
||||
return [{type: "plain-text", text: text}];
|
||||
}
|
||||
|
||||
|
||||
validMatches.sort(function(a,b){ return a.index - b.index; });
|
||||
|
||||
var newParseTree = [];
|
||||
var currentPos = 0;
|
||||
|
||||
for(var i = 0; i < validMatches.length; i++) {
|
||||
var match = validMatches[i];
|
||||
var matchStart = match.index;
|
||||
var matchEnd = matchStart + match.length;
|
||||
|
||||
if(matchStart > currentPos) {
|
||||
var beforeText = text.substring(currentPos, matchStart);
|
||||
newParseTree.push({
|
||||
type: "plain-text",
|
||||
text: beforeText
|
||||
});
|
||||
var curPos = 0;
|
||||
|
||||
for(var x = 0; x < validMatches.length; x++) {
|
||||
var mm = validMatches[x];
|
||||
var s = mm.index;
|
||||
var e = s + mm.length;
|
||||
|
||||
if(s > curPos) {
|
||||
newParseTree.push({ type: "plain-text", text: text.substring(curPos,s) });
|
||||
}
|
||||
|
||||
var matchedTitle = this.tiddlerTitleInfo.titles[match.titleIndex];
|
||||
var matchedText = text.substring(matchStart, matchEnd);
|
||||
|
||||
|
||||
var toTitle = this.tiddlerTitleInfo.titles[mm.titleIndex];
|
||||
var matchedText = text.substring(s,e);
|
||||
|
||||
newParseTree.push({
|
||||
type: "link",
|
||||
attributes: {
|
||||
to: {type: "string", value: matchedTitle},
|
||||
to: {type: "string", value: toTitle},
|
||||
"class": {type: "string", value: "tc-freelink"}
|
||||
},
|
||||
children: [{
|
||||
@@ -193,80 +159,63 @@ TextNodeWidget.prototype.processTextWithMatches = function(text, currentTiddlerT
|
||||
text: matchedText
|
||||
}]
|
||||
});
|
||||
|
||||
currentPos = matchEnd;
|
||||
|
||||
curPos = e;
|
||||
}
|
||||
|
||||
if(currentPos < text.length) {
|
||||
var remainingText = text.substring(currentPos);
|
||||
newParseTree.push({
|
||||
type: "plain-text",
|
||||
text: remainingText
|
||||
});
|
||||
|
||||
if(curPos < text.length) {
|
||||
newParseTree.push({ type: "plain-text", text: text.substring(curPos) });
|
||||
}
|
||||
|
||||
|
||||
return newParseTree;
|
||||
};
|
||||
|
||||
function computeTiddlerTitleInfo(self, ignoreCase) {
|
||||
function computeTiddlerTitleInfo(self,ignoreCase) {
|
||||
var targetFilterText = self.wiki.getTiddlerText(TITLE_TARGET_FILTER),
|
||||
titles = !!targetFilterText ?
|
||||
self.wiki.filterTiddlers(targetFilterText,$tw.rootWidget) :
|
||||
titles = targetFilterText ?
|
||||
self.wiki.filterTiddlers(targetFilterText,$tw.rootWidget) :
|
||||
self.wiki.allTitles();
|
||||
|
||||
|
||||
if(!titles || titles.length === 0) {
|
||||
return {
|
||||
titles: [],
|
||||
ac: new AhoCorasick()
|
||||
};
|
||||
return { titles: [], ac: new AhoCorasick() };
|
||||
}
|
||||
|
||||
|
||||
var validTitles = [];
|
||||
var ac = new AhoCorasick();
|
||||
|
||||
for(var i = 0; i < titles.length; i++) {
|
||||
var title = titles[i];
|
||||
if(title && title.length > 0 && title.substring(0,3) !== "$:/") {
|
||||
var escapedTitle = escapeRegExp(title);
|
||||
if(escapedTitle) {
|
||||
validTitles.push(title);
|
||||
}
|
||||
var t = titles[i];
|
||||
if(t && t.length > 0 && t.substring(0,3) !== "$:/") {
|
||||
validTitles.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
var sortedTitles = validTitles.sort(function(a,b) {
|
||||
var lenDiff = b.length - a.length;
|
||||
if(lenDiff !== 0) return lenDiff;
|
||||
|
||||
validTitles.sort(function(a,b) {
|
||||
var d = b.length - a.length;
|
||||
if(d !== 0) return d;
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
});
|
||||
|
||||
for(var i = 0; i < sortedTitles.length; i++) {
|
||||
var title = sortedTitles[i];
|
||||
|
||||
var ac = new AhoCorasick();
|
||||
for(var j = 0; j < validTitles.length; j++) {
|
||||
var title = validTitles[j];
|
||||
var pattern = ignoreCase ? title.toLowerCase() : title;
|
||||
ac.addPattern(pattern, i);
|
||||
ac.addPattern(pattern,j);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ac.buildFailureLinks();
|
||||
} catch(e) {
|
||||
return {
|
||||
titles: [],
|
||||
ac: new AhoCorasick()
|
||||
};
|
||||
return { titles: [], ac: new AhoCorasick() };
|
||||
}
|
||||
|
||||
return {
|
||||
titles: sortedTitles,
|
||||
ac: ac
|
||||
};
|
||||
|
||||
return { titles: validTitles, ac: ac };
|
||||
}
|
||||
|
||||
TextNodeWidget.prototype.isWithinButtonOrLink = function() {
|
||||
var widget = this.parentWidget;
|
||||
while(widget) {
|
||||
if(widget instanceof ButtonWidget ||
|
||||
widget instanceof LinkWidget ||
|
||||
((widget instanceof ElementWidget) && widget.parseTreeNode.tag === "a")) {
|
||||
if(widget instanceof ButtonWidget ||
|
||||
widget instanceof LinkWidget ||
|
||||
((widget instanceof ElementWidget) && widget.parseTreeNode.tag === "a")) {
|
||||
return true;
|
||||
}
|
||||
widget = widget.parentWidget;
|
||||
@@ -275,35 +224,56 @@ TextNodeWidget.prototype.isWithinButtonOrLink = function() {
|
||||
};
|
||||
|
||||
TextNodeWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var self = this,
|
||||
changedAttributes = this.computeAttributes(),
|
||||
titlesHaveChanged = false;
|
||||
|
||||
var self = this;
|
||||
var changedAttributes = this.computeAttributes();
|
||||
var titlesHaveChanged = false;
|
||||
|
||||
if(changedTiddlers) {
|
||||
$tw.utils.each(changedTiddlers,function(change,title) {
|
||||
if(change.isDeleted) {
|
||||
if(titlesHaveChanged) return;
|
||||
|
||||
if(title === WORD_BOUNDARY_TIDDLER || title === TITLE_TARGET_FILTER) {
|
||||
titlesHaveChanged = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(title.substring(0,3) === "$:/") {
|
||||
return;
|
||||
}
|
||||
|
||||
if(change && change.isDeleted) {
|
||||
if(self.tiddlerTitleInfo && self.tiddlerTitleInfo.titles && self.tiddlerTitleInfo.titles.indexOf(title) !== -1) {
|
||||
titlesHaveChanged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var tiddler = self.wiki.getTiddler(title);
|
||||
if(tiddler && tiddler.hasField("draft.of")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!self.tiddlerTitleInfo || !self.tiddlerTitleInfo.titles || self.tiddlerTitleInfo.titles.indexOf(title) === -1) {
|
||||
titlesHaveChanged = true;
|
||||
} else {
|
||||
titlesHaveChanged = titlesHaveChanged ||
|
||||
!self.tiddlerTitleInfo ||
|
||||
self.tiddlerTitleInfo.titles.indexOf(title) === -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(changedAttributes.text || titlesHaveChanged ||
|
||||
(changedTiddlers && changedTiddlers[WORD_BOUNDARY_TIDDLER])) {
|
||||
|
||||
var wordBoundaryChanged = !!(changedTiddlers && changedTiddlers[WORD_BOUNDARY_TIDDLER]);
|
||||
|
||||
if(changedAttributes.text || titlesHaveChanged || wordBoundaryChanged) {
|
||||
if(titlesHaveChanged) {
|
||||
var ignoreCase = self.getVariable("tv-freelinks-ignore-case",{defaultValue:"no"}).trim() === "yes";
|
||||
var cacheKey = "tiddler-title-info-" + (ignoreCase ? "insensitive" : "sensitive");
|
||||
self.wiki.clearCache(cacheKey);
|
||||
self.wiki.clearCache("tiddler-title-info-insensitive");
|
||||
self.wiki.clearCache("tiddler-title-info-sensitive");
|
||||
}
|
||||
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
|
||||
if(changedTiddlers) {
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.text = TextNodeWidget;
|
||||
|
||||
@@ -25,61 +25,6 @@ exports.startup = function() {
|
||||
require("$:/plugins/tiddlywiki/geospatial/leaflet.markercluster.js");
|
||||
}
|
||||
// Install geolocation message handler
|
||||
$tw.rootWidget.addEventListener("tm-save-dom-to-image",function(event) {
|
||||
var params = event.paramObject || {},
|
||||
domToImage = require("$:/plugins/tiddlywiki/geospatial/dom-to-image-more.js"),
|
||||
domNode = document.querySelector(params.selector || "body.tc-body");
|
||||
if(domNode) {
|
||||
var method = "toPng";
|
||||
switch(params.format) {
|
||||
case "jpeg":
|
||||
// Intentional fallthrough
|
||||
case "jpg":
|
||||
method = "toJpeg";
|
||||
break;
|
||||
case "svg":
|
||||
method = "toSvg";
|
||||
break;
|
||||
}
|
||||
domToImage[method](domNode,{
|
||||
height: $tw.utils.parseInt(params.height) || domNode.offsetHeight,
|
||||
width: $tw.utils.parseInt(params.width) || domNode.offsetWidth,
|
||||
quality: $tw.utils.parseNumber(params.quality),
|
||||
scale: $tw.utils.parseNumber(params.scale) || 1
|
||||
})
|
||||
.then(function(dataUrl) {
|
||||
// Save the image
|
||||
if(params["save-file"]) {
|
||||
var link = document.createElement("a");
|
||||
link.download = params["save-file"];
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
}
|
||||
// Save the tiddler
|
||||
if(params["save-title"]) {
|
||||
if(dataUrl.indexOf("data:image/svg+xml;") === 0) {
|
||||
var commaIndex = dataUrl.indexOf(",");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: "image/svg+xml",
|
||||
"text": decodeURIComponent(dataUrl.substring(commaIndex + 1))
|
||||
}));
|
||||
} else {
|
||||
var parts = dataUrl.split(";base64,");
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: params["save-title"],
|
||||
type: parts[0].split(":")[1],
|
||||
"text": parts[1]
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error("oops, something went wrong!", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
// Install geolocation message handler
|
||||
$tw.rootWidget.addEventListener("tm-request-geolocation",function(event) {
|
||||
var widget = event.widget,
|
||||
wiki = widget.wiki || $tw.wiki,
|
||||
|
||||
@@ -143,6 +143,16 @@ exports.runTests = function(callback,specFilter) {
|
||||
var env = jasmine.getEnv();
|
||||
var jasmineInterface = jasmineCore.interface(jasmine,env);
|
||||
context = $tw.utils.extend({},jasmineInterface,context);
|
||||
// Initialise the WikiParser prototype with the correct rule config
|
||||
// from the main wiki, before any test can trigger it from an empty wiki
|
||||
$tw.wiki.parseText("text/vnd.tiddlywiki","");
|
||||
// Set up test utilities available to all test specs
|
||||
$tw.test = {
|
||||
/** Create a test wiki instance, pre-configured with core settings */
|
||||
wiki: function() {
|
||||
return new $tw.Wiki();
|
||||
}
|
||||
};
|
||||
// Iterate through all the test modules
|
||||
var tests = $tw.wiki.filterTiddlers(TEST_TIDDLER_FILTER);
|
||||
$tw.utils.each(tests,evalInContext);
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("Wiki-based tests", function() {
|
||||
var tiddler = $tw.wiki.getTiddler(title);
|
||||
it(tiddler.fields.title + ": " + tiddler.fields.description, function() {
|
||||
// Add our tiddlers
|
||||
var wiki = new $tw.Wiki(),
|
||||
var wiki = $tw.test.wiki(),
|
||||
coreTiddler = $tw.wiki.getTiddler("$:/core");
|
||||
if(coreTiddler) {
|
||||
wiki.addTiddler(coreTiddler);
|
||||
|
||||
@@ -11,6 +11,7 @@ Wrapper for `katex.min.js` that provides a `<$latex>` widget. It is also availab
|
||||
|
||||
var katex = require("$:/plugins/tiddlywiki/katex/katex.min.js"),
|
||||
Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
require("$:/plugins/tiddlywiki/katex/mhchem.min.js");
|
||||
|
||||
katex.macros = {};
|
||||
katex.updateMacros = function() {
|
||||
|
||||
@@ -420,7 +420,7 @@ function extendInlineParse(thisArg,origFunc,twInlineRules) {
|
||||
/// post processing ///
|
||||
|
||||
function wikify(state) {
|
||||
var href, title, src;
|
||||
var href, title, src, alt;
|
||||
var tagStack = [];
|
||||
|
||||
state.tokens.forEach(function(blockToken) {
|
||||
@@ -458,6 +458,7 @@ function wikify(state) {
|
||||
token.tag = "$image";
|
||||
src = token.attrGet("src");
|
||||
alt = token.attrGet("alt");
|
||||
token.attrSet("alt",alt);
|
||||
title = token.attrGet("title");
|
||||
|
||||
token.attrs[token.attrIndex("src")][0] = "source";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
title $:/plugins/tiddlywiki/tw5.com-docs/readme
|
||||
title: $:/plugins/tiddlywiki/tw5.com-docs/readme
|
||||
|
||||
This is an experimental packaging of the documentation from tiddlywiki.com into a plugin.
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@ title: $:/themes/tiddlywiki/vanilla/options/
|
||||
|
||||
stickytitles: no
|
||||
sidebarlayout: fluid-fixed
|
||||
codewrapping: pre
|
||||
codewrapping: pre-wrap
|
||||
|
||||
Reference in New Issue
Block a user