mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-07-26 13:38:54 +00:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3815e6043 | ||
|
|
752d9f800e | ||
|
|
4fcd34a5b4 | ||
|
|
56e3aee20b | ||
|
|
f41ab866fb | ||
|
|
0239971000 | ||
|
|
cf4f7e0918 | ||
|
|
297d0d2002 | ||
|
|
ee45768ade | ||
|
|
24b433211d | ||
|
|
abf8ffdbca | ||
|
|
97e27d0dad | ||
|
|
855dc2fbb9 | ||
|
|
e1e34f4907 | ||
|
|
7196b9ee9b | ||
|
|
c0b3403f87 | ||
|
|
ed71c8262a | ||
|
|
2ececf21c7 | ||
|
|
7516d96eed | ||
|
|
4bf7e9d192 | ||
|
|
04cd3081e4 | ||
|
|
9a0bbf831b | ||
|
|
a30854a4c7 | ||
|
|
856057be81 |
@@ -217,6 +217,11 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
|
||||
fileInfo.isEditableFile = true;
|
||||
fileInfo.originalpath = options.fileInfo.originalpath;
|
||||
}
|
||||
// Propagate the pinFilepath flag (so generateTiddlerFilepath can short-circuit
|
||||
// FileSystemPaths and write back to the original location)
|
||||
if(options.fileInfo && !!options.fileInfo.pinFilepath) {
|
||||
fileInfo.pinFilepath = true;
|
||||
}
|
||||
// Check if the tiddler has any unsafe fields that can't be expressed in a .tid or .meta file: containing control characters, or leading/trailing whitespace
|
||||
var hasUnsafeFields = false;
|
||||
$tw.utils.each(tiddler.getFieldStrings(),function(value,fieldName) {
|
||||
@@ -319,9 +324,16 @@ exports.generateTiddlerFilepath = function(title,options) {
|
||||
extension = options.extension || "",
|
||||
originalpath = (options.fileInfo && options.fileInfo.originalpath) ? options.fileInfo.originalpath : "",
|
||||
overwrite = options.fileInfo && options.fileInfo.overwrite || false,
|
||||
pinFilepath = !!(options.fileInfo && options.fileInfo.pinFilepath),
|
||||
filepath;
|
||||
// If the tiddler's filepath is pinned via tiddlywiki.files, the originalpath
|
||||
// wins and the path filters are skipped entirely.
|
||||
if(pinFilepath && originalpath) {
|
||||
var pinnedExt = path.extname(originalpath);
|
||||
filepath = originalpath.substring(0,originalpath.length - pinnedExt.length);
|
||||
}
|
||||
// Check if any of the pathFilters applies
|
||||
if(options.pathFilters && options.wiki) {
|
||||
if(!filepath && options.pathFilters && options.wiki) {
|
||||
$tw.utils.each(options.pathFilters,function(filter) {
|
||||
if(!filepath) {
|
||||
var source = options.wiki.makeTiddlerIterator([title]),
|
||||
@@ -352,8 +364,10 @@ exports.generateTiddlerFilepath = function(title,options) {
|
||||
}
|
||||
// Replace any Unicode control codes
|
||||
filepath = filepath.replace(/[\x00-\x1f\x80-\x9f]/g,"_");
|
||||
// Replace any characters that can't be used in cross-platform filenames
|
||||
filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^/g,"_"));
|
||||
// Replace any characters that can't be used in cross-platform filenames.
|
||||
if(!pinFilepath) {
|
||||
filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^|\\/g,"_"));
|
||||
}
|
||||
// Replace any dots or spaces at the end of the extension with the same number of underscores
|
||||
extension = extension.replace(/[\. ]+$/, function (u) { return u.replace(/[\. ]/g, "_");});
|
||||
// Truncate the extension if it is too long
|
||||
|
||||
@@ -78,7 +78,6 @@ class BackgroundActionTracker {
|
||||
fnProcess: (changes) => {
|
||||
if(this.hasChanged) {
|
||||
this.hasChanged = false;
|
||||
console.log("Processing background action", this.title);
|
||||
const tiddler = this.wiki.getTiddler(this.title);
|
||||
let doActions = true;
|
||||
if(tiddler && tiddler.fields.platforms) {
|
||||
@@ -89,6 +88,7 @@ class BackgroundActionTracker {
|
||||
}
|
||||
}
|
||||
if(doActions) {
|
||||
console.log("Processing background action", this.title);
|
||||
this.wiki.invokeActionString(
|
||||
this.actions,
|
||||
null,
|
||||
|
||||
@@ -13,14 +13,15 @@ Export our filter prefix function
|
||||
exports.intersection = function(operationSubFunction) {
|
||||
return function(results,source,widget) {
|
||||
if(results.length !== 0) {
|
||||
var secondRunResults = operationSubFunction(source,widget);
|
||||
var firstRunResults = results.toArray();
|
||||
const secondRunResults = operationSubFunction(source,widget),
|
||||
secondRunSet = new Set(secondRunResults),
|
||||
firstRunResults = results.toArray();
|
||||
results.clear();
|
||||
$tw.utils.each(firstRunResults,function(title) {
|
||||
if(secondRunResults.indexOf(title) !== -1) {
|
||||
firstRunResults.forEach((title) => {
|
||||
if(secondRunSet.has(title)) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -13,33 +13,28 @@ Export our filter prefix function
|
||||
exports.sort = function(operationSubFunction,options) {
|
||||
return function(results,source,widget) {
|
||||
if(results.length > 0) {
|
||||
var suffixes = options.suffixes,
|
||||
const suffixes = options.suffixes,
|
||||
sortType = (suffixes[0] && suffixes[0][0]) ? suffixes[0][0] : "string",
|
||||
invert = suffixes[1] ? (suffixes[1].indexOf("reverse") !== -1) : false,
|
||||
isCaseSensitive = suffixes[1] ? (suffixes[1].indexOf("casesensitive") !== -1) : false,
|
||||
invert = suffixes[1] ? suffixes[1].includes("reverse") : false,
|
||||
isCaseSensitive = suffixes[1] ? suffixes[1].includes("casesensitive") : false,
|
||||
inputTitles = results.toArray(),
|
||||
sortKeys = [],
|
||||
indexes = new Array(inputTitles.length),
|
||||
compareFn;
|
||||
results.each(function(title) {
|
||||
var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),widget.makeFakeWidgetWithVariables({
|
||||
"currentTiddler": "" + title,
|
||||
"..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""})
|
||||
}));
|
||||
compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive});
|
||||
results.each((title) => {
|
||||
const key = operationSubFunction(
|
||||
options.wiki.makeTiddlerIterator([title]),
|
||||
widget.makeFakeWidgetWithVariables({
|
||||
"currentTiddler": "" + title,
|
||||
"..currentTiddler": widget.getVariable("currentTiddler",{defaultValue:""})
|
||||
})
|
||||
);
|
||||
sortKeys.push(key[0] || "");
|
||||
});
|
||||
results.clear();
|
||||
// Prepare an array of indexes to sort
|
||||
for(var t=0; t<inputTitles.length; t++) {
|
||||
indexes[t] = t;
|
||||
}
|
||||
// Sort the indexes
|
||||
compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive});
|
||||
indexes = indexes.sort(function(a,b) {
|
||||
return compareFn(sortKeys[a],sortKeys[b]);
|
||||
});
|
||||
// Add to results in correct order
|
||||
$tw.utils.each(indexes,function(index) {
|
||||
let indexes = Array.from(inputTitles.keys());
|
||||
indexes.sort((a,b) => compareFn(sortKeys[a],sortKeys[b]));
|
||||
indexes.forEach((index) => {
|
||||
results.push(inputTitles[index]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,56 +9,69 @@ Filter operators for manipulating the current selection list
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Fetch titles from the current list
|
||||
*/
|
||||
const prepare_results = (source) => {
|
||||
const results = [];
|
||||
source((tiddler,title) => results.push(title));
|
||||
return results;
|
||||
};
|
||||
|
||||
/*
|
||||
Order a list
|
||||
*/
|
||||
exports.order = function(source,operator,options) {
|
||||
const results = prepare_results(source);
|
||||
return operator.operand.toLowerCase() === "reverse" ?
|
||||
results.reverse() :
|
||||
results;
|
||||
var results = [];
|
||||
if(operator.operand.toLowerCase() === "reverse") {
|
||||
source(function(tiddler,title) {
|
||||
results.unshift(title);
|
||||
});
|
||||
} else {
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
/*
|
||||
Reverse list
|
||||
*/
|
||||
exports.reverse = function(source,operator,options) {
|
||||
return prepare_results(source).reverse();
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.unshift(title);
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
/*
|
||||
First entry/entries in list
|
||||
*/
|
||||
exports.first = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,1);
|
||||
return prepare_results(source).slice(0,count);
|
||||
var count = $tw.utils.getInt(operator.operand,1),
|
||||
results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results.slice(0,count);
|
||||
};
|
||||
|
||||
/*
|
||||
Last entry/entries in list
|
||||
*/
|
||||
exports.last = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,1);
|
||||
return count === 0 ?
|
||||
[] :
|
||||
prepare_results(source).slice(-count);
|
||||
var count = $tw.utils.getInt(operator.operand,1),
|
||||
results = [];
|
||||
if(count === 0) return results;
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results.slice(-count);
|
||||
};
|
||||
|
||||
/*
|
||||
All but the first entry/entries of the list
|
||||
*/
|
||||
exports.rest = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,1);
|
||||
return prepare_results(source).slice(count);
|
||||
var count = $tw.utils.getInt(operator.operand,1),
|
||||
results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results.slice(count);
|
||||
};
|
||||
exports.butfirst = exports.rest;
|
||||
exports.bf = exports.rest;
|
||||
@@ -67,9 +80,12 @@ exports.bf = exports.rest;
|
||||
All but the last entry/entries of the list
|
||||
*/
|
||||
exports.butlast = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,1),
|
||||
results = prepare_results(source),
|
||||
index = count === 0 ? results.length : -count;
|
||||
var count = $tw.utils.getInt(operator.operand,1),
|
||||
results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
var index = count === 0 ? results.length : -count;
|
||||
return results.slice(0,index);
|
||||
};
|
||||
exports.bl = exports.butlast;
|
||||
@@ -78,14 +94,22 @@ exports.bl = exports.butlast;
|
||||
The nth member of the list
|
||||
*/
|
||||
exports.nth = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,1);
|
||||
return prepare_results(source).slice(count - 1,count);
|
||||
var count = $tw.utils.getInt(operator.operand,1),
|
||||
results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results.slice(count - 1,count);
|
||||
};
|
||||
|
||||
/*
|
||||
The zero based nth member of the list
|
||||
*/
|
||||
exports.zth = function(source,operator,options) {
|
||||
const count = $tw.utils.getInt(operator.operand,0);
|
||||
return prepare_results(source).slice(count,count + 1);
|
||||
};
|
||||
var count = $tw.utils.getInt(operator.operand,0),
|
||||
results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results.slice(count,count + 1);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ Export our filter function
|
||||
exports.prefix = function(source,operator,options) {
|
||||
const results = [],
|
||||
suffixes = (operator.suffixes || [])[0] || [],
|
||||
caseInsensitive = suffixes.indexOf("caseinsensitive") !== -1,
|
||||
caseInsensitive = suffixes.includes("caseinsensitive"),
|
||||
negate = operator.prefix === "!";
|
||||
|
||||
const operand = caseInsensitive ?
|
||||
@@ -23,13 +23,12 @@ exports.prefix = function(source,operator,options) {
|
||||
operator.operand;
|
||||
|
||||
source((tiddler,title) => {
|
||||
const value = caseInsensitive ? title.toLowerCase() : title;
|
||||
const matches = value.startsWith(operand);
|
||||
|
||||
const value = caseInsensitive ? title.toLowerCase() : title,
|
||||
matches = value.startsWith(operand);
|
||||
if(negate ? !matches : matches) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,13 +14,13 @@ Export our filter function
|
||||
*/
|
||||
exports.sortsub = function(source,operator,options) {
|
||||
// Compile the subfilter
|
||||
var filterFn = options.wiki.compileFilter(operator.operand);
|
||||
let filterFn = options.wiki.compileFilter(operator.operand);
|
||||
// Collect the input titles and the corresponding sort keys
|
||||
var inputTitles = [],
|
||||
let inputTitles = [],
|
||||
sortKeys = [];
|
||||
source(function(tiddler,title) {
|
||||
inputTitles.push(title);
|
||||
var r = filterFn.call(options.wiki,function(iterator) {
|
||||
let r = filterFn.call(options.wiki,function(iterator) {
|
||||
iterator(options.wiki.getTiddler(title),title);
|
||||
},options.widget.makeFakeWidgetWithVariables({
|
||||
"currentTiddler": "" + title,
|
||||
@@ -29,17 +29,12 @@ exports.sortsub = function(source,operator,options) {
|
||||
sortKeys.push(r[0] || "");
|
||||
});
|
||||
// Rather than sorting the titles array, we'll sort the indexes so that we can consult both arrays
|
||||
var indexes = new Array(inputTitles.length);
|
||||
for(var t=0; t<inputTitles.length; t++) {
|
||||
indexes[t] = t;
|
||||
}
|
||||
let indexes = Array.from(inputTitles.keys());
|
||||
// Sort the indexes
|
||||
var compareFn = $tw.utils.makeCompareFunction(operator.suffix,{defaultType: "string",invert: operator.prefix === "!"});
|
||||
indexes = indexes.sort(function(a,b) {
|
||||
return compareFn(sortKeys[a],sortKeys[b]);
|
||||
});
|
||||
let compareFn = $tw.utils.makeCompareFunction(operator.suffix,{defaultType: "string",invert: operator.prefix === "!"});
|
||||
indexes = indexes.sort((a,b) => compareFn(sortKeys[a],sortKeys[b]));
|
||||
// Make the results array in order
|
||||
var results = [];
|
||||
let results = [];
|
||||
$tw.utils.each(indexes,function(index) {
|
||||
results.push(inputTitles[index]);
|
||||
});
|
||||
|
||||
@@ -91,22 +91,35 @@ function diffLineWordMode(text1,text2,mode) {
|
||||
return diffs;
|
||||
}
|
||||
|
||||
exports.makepatches = function(source,operator,options) {
|
||||
var suffix = operator.suffix || "",
|
||||
result = [];
|
||||
|
||||
source(function(tiddler,title) {
|
||||
let diffs, patches;
|
||||
if(suffix === "lines" || suffix === "words") {
|
||||
diffs = diffLineWordMode(title,operator.operand,suffix);
|
||||
patches = dmp.patchMake(title,diffs);
|
||||
exports.makepatches = function(source, operator, options) {
|
||||
const suffixes = operator.suffixes || [],
|
||||
[modeArg = [], formatArg = []] = suffixes,
|
||||
modeSuffix = modeArg[0] || operator.suffix || "",
|
||||
mode = ["lines", "words"].includes(modeSuffix) ? modeSuffix : "",
|
||||
isJson = formatArg[0] === "json",
|
||||
results = [];
|
||||
|
||||
source((tiddler, title) => {
|
||||
if (isJson) {
|
||||
const diffs = (mode === "lines" || mode === "words")
|
||||
? diffLineWordMode(title, operator.operand, mode)
|
||||
: dmp.diffMain(title, operator.operand);
|
||||
|
||||
const jsonOutput = diffs.map(([typeCode, text]) => ({
|
||||
type: typeCode === 1 ? "insert" : (typeCode === -1 ? "delete" : "equal"),
|
||||
text
|
||||
}));
|
||||
results.push(JSON.stringify(jsonOutput));
|
||||
} else {
|
||||
patches = dmp.patchMake(title,operator.operand);
|
||||
const patches = (mode === "lines" || mode === "words")
|
||||
? dmp.patchMake(title, diffLineWordMode(title, operator.operand, mode))
|
||||
: dmp.patchMake(title, operator.operand);
|
||||
|
||||
results.push(dmp.patchToText(patches));
|
||||
}
|
||||
Array.prototype.push.apply(result,[dmp.patchToText(patches)]);
|
||||
});
|
||||
|
||||
return result;
|
||||
return results;
|
||||
};
|
||||
|
||||
exports.applypatches = makeStringBinaryOperator(
|
||||
@@ -235,4 +248,4 @@ exports.charcode = function(source,operator,options) {
|
||||
}
|
||||
});
|
||||
return [chars.join("")];
|
||||
};
|
||||
};
|
||||
@@ -13,11 +13,12 @@ Filter operator returning its operand evaluated as a filter
|
||||
Export our filter function
|
||||
*/
|
||||
exports.subfilter = function(source,operator,options) {
|
||||
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source);
|
||||
const list = options.wiki.filterTiddlers(operator.operand,options.widget,source);
|
||||
if(operator.prefix === "!") {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
if(list.indexOf(title) === -1) {
|
||||
const results = [],
|
||||
listSet = new Set(list);
|
||||
source((tiddler,title) => {
|
||||
if(!listSet.has(title)) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,41 +13,27 @@ Filter operator for checking if a title ends with a suffix
|
||||
Export our filter function
|
||||
*/
|
||||
exports.suffix = function(source,operator,options) {
|
||||
var results = [],
|
||||
const results = [],
|
||||
suffixes = (operator.suffixes || [])[0] || [];
|
||||
|
||||
if(!operator.operand) {
|
||||
source(function(tiddler,title) {
|
||||
source((tiddler,title) => {
|
||||
results.push(title);
|
||||
});
|
||||
} else if(suffixes.indexOf("caseinsensitive") !== -1) {
|
||||
var operand = operator.operand.toLowerCase();
|
||||
if(operator.prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
if(title.toLowerCase().substr(-operand.length) !== operand) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
source(function(tiddler,title) {
|
||||
if(title.toLowerCase().substr(-operand.length) === operand) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if(operator.prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
if(title.substr(-operator.operand.length) !== operator.operand) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
source(function(tiddler,title) {
|
||||
if(title.substr(-operator.operand.length) === operator.operand) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const caseInsensitive = suffixes.indexOf("caseinsensitive") !== -1,
|
||||
negate = operator.prefix === "!",
|
||||
operand = caseInsensitive ? operator.operand.toLowerCase() : operator.operand;
|
||||
|
||||
source((tiddler,title) => {
|
||||
const value = caseInsensitive ? title.toLowerCase() : title,
|
||||
matches = value.endsWith(operand);
|
||||
if(negate ? !matches : matches) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -11,18 +11,20 @@ Extended filter operators to manipulate the current list.
|
||||
|
||||
/*
|
||||
Fetch titles from the current list
|
||||
*/
|
||||
const prepare_results = (source) => {
|
||||
const results = [];
|
||||
source((tiddler,title) => results.push(title));
|
||||
*/
|
||||
var prepare_results = function (source) {
|
||||
var results = [];
|
||||
source(function (tiddler, title) {
|
||||
results.push(title);
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
/*
|
||||
Moves a number of items from the tail of the current list before the item named in the operand
|
||||
*/
|
||||
exports.putbefore = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.putbefore = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand),
|
||||
count = $tw.utils.getInt(operator.suffix,1);
|
||||
return (index === -1) ?
|
||||
@@ -32,9 +34,9 @@ exports.putbefore = function(source,operator) {
|
||||
|
||||
/*
|
||||
Moves a number of items from the tail of the current list after the item named in the operand
|
||||
*/
|
||||
exports.putafter = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.putafter = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand),
|
||||
count = $tw.utils.getInt(operator.suffix,1);
|
||||
return (index === -1) ?
|
||||
@@ -44,9 +46,9 @@ exports.putafter = function(source,operator) {
|
||||
|
||||
/*
|
||||
Replaces the item named in the operand with a number of items from the tail of the current list
|
||||
*/
|
||||
exports.replace = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.replace = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand),
|
||||
count = $tw.utils.getInt(operator.suffix,1);
|
||||
return (index === -1) ?
|
||||
@@ -56,39 +58,39 @@ exports.replace = function(source,operator) {
|
||||
|
||||
/*
|
||||
Moves a number of items from the tail of the current list to the head of the list
|
||||
*/
|
||||
exports.putfirst = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.putfirst = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
count = $tw.utils.getInt(operator.suffix,1);
|
||||
return [...results.slice(-count), ...results.slice(0, -count)];
|
||||
return results.slice(-count).concat(results.slice(0, -count));
|
||||
};
|
||||
|
||||
/*
|
||||
Moves a number of items from the head of the current list to the tail of the list
|
||||
*/
|
||||
exports.putlast = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.putlast = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
count = $tw.utils.getInt(operator.suffix,1);
|
||||
return [...results.slice(count), ...results.slice(0, count)];
|
||||
return results.slice(count).concat(results.slice(0, count));
|
||||
};
|
||||
|
||||
/*
|
||||
Moves the item named in the operand a number of places forward or backward in the list
|
||||
*/
|
||||
exports.move = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.move = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand),
|
||||
count = $tw.utils.getInt(operator.suffix,1),
|
||||
marker = results.splice(index, 1),
|
||||
offset = (index + count) > 0 ? index + count : 0;
|
||||
offset = (index + count) > 0 ? index + count : 0;
|
||||
return results.slice(0, offset).concat(marker).concat(results.slice(offset));
|
||||
};
|
||||
|
||||
/*
|
||||
Returns the items from the current list that are after the item named in the operand
|
||||
*/
|
||||
exports.allafter = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.allafter = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand);
|
||||
return (index === -1) ? [] :
|
||||
(operator.suffix) ? results.slice(index) :
|
||||
@@ -97,9 +99,9 @@ exports.allafter = function(source,operator) {
|
||||
|
||||
/*
|
||||
Returns the items from the current list that are before the item named in the operand
|
||||
*/
|
||||
exports.allbefore = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
*/
|
||||
exports.allbefore = function (source, operator) {
|
||||
var results = prepare_results(source),
|
||||
index = results.indexOf(operator.operand);
|
||||
return (index === -1) ? [] :
|
||||
(operator.suffix) ? results.slice(0, index + 1) :
|
||||
@@ -108,9 +110,9 @@ exports.allbefore = function(source,operator) {
|
||||
|
||||
/*
|
||||
Appends the items listed in the operand array to the tail of the current list
|
||||
*/
|
||||
exports.append = function(source,operator) {
|
||||
const append = $tw.utils.parseStringArray(operator.operand,"true"),
|
||||
*/
|
||||
exports.append = function (source, operator) {
|
||||
var append = $tw.utils.parseStringArray(operator.operand, "true"),
|
||||
results = prepare_results(source),
|
||||
count = parseInt(operator.suffix) || append.length;
|
||||
return (append.length === 0) ? results :
|
||||
@@ -120,9 +122,9 @@ exports.append = function(source,operator) {
|
||||
|
||||
/*
|
||||
Prepends the items listed in the operand array to the head of the current list
|
||||
*/
|
||||
exports.prepend = function(source,operator) {
|
||||
const prepend = $tw.utils.parseStringArray(operator.operand,"true"),
|
||||
*/
|
||||
exports.prepend = function (source, operator) {
|
||||
var prepend = $tw.utils.parseStringArray(operator.operand, "true"),
|
||||
results = prepare_results(source),
|
||||
count = $tw.utils.getInt(operator.suffix,prepend.length);
|
||||
return (prepend.length === 0) ? results :
|
||||
@@ -132,19 +134,21 @@ exports.prepend = function(source,operator) {
|
||||
|
||||
/*
|
||||
Returns all items from the current list except the items listed in the operand array
|
||||
*/
|
||||
exports.remove = function(source, operator) {
|
||||
const array = $tw.utils.parseStringArray(operator.operand, "true"),
|
||||
results = prepare_results(source);
|
||||
|
||||
if(array.length === 0) {
|
||||
return results;
|
||||
}
|
||||
const count = parseInt(operator.suffix, 10) || array.length,
|
||||
targetItems = operator.prefix ? array.slice(-count).reverse() : array.slice(0, count);
|
||||
|
||||
for(const item of targetItems) {
|
||||
const index = results.indexOf(item);
|
||||
*/
|
||||
exports.remove = function (source, operator) {
|
||||
var array = $tw.utils.parseStringArray(operator.operand, "true"),
|
||||
results = prepare_results(source),
|
||||
count = parseInt(operator.suffix) || array.length,
|
||||
p,
|
||||
len,
|
||||
index;
|
||||
len = array.length - 1;
|
||||
for(p = 0; p < count; ++p) {
|
||||
if(operator.prefix) {
|
||||
index = results.indexOf(array[len - p]);
|
||||
} else {
|
||||
index = results.indexOf(array[p]);
|
||||
}
|
||||
if(index !== -1) {
|
||||
results.splice(index, 1);
|
||||
}
|
||||
@@ -154,32 +158,40 @@ exports.remove = function(source, operator) {
|
||||
|
||||
/*
|
||||
Returns all items from the current list sorted in the order of the items in the operand array
|
||||
*/
|
||||
exports.sortby = function(source,operator) {
|
||||
const results = prepare_results(source);
|
||||
*/
|
||||
exports.sortby = function (source, operator) {
|
||||
var results = prepare_results(source);
|
||||
if(!results || results.length < 2) {
|
||||
return results;
|
||||
}
|
||||
const lookup = $tw.utils.parseStringArray(operator.operand,"true");
|
||||
return results.sort((a,b) => lookup.indexOf(a) - lookup.indexOf(b));
|
||||
var lookup = $tw.utils.parseStringArray(operator.operand, "true");
|
||||
results.sort(function (a, b) {
|
||||
return lookup.indexOf(a) - lookup.indexOf(b);
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Removes all duplicate items from the current list
|
||||
*/
|
||||
exports.unique = function(source, operator) {
|
||||
return Array.from(new Set(prepare_results(source)));
|
||||
*/
|
||||
exports.unique = function (source, operator) {
|
||||
var results = prepare_results(source);
|
||||
var set = results.reduce(function (a, b) {
|
||||
if(a.indexOf(b) < 0) {
|
||||
a.push(b);
|
||||
}
|
||||
return a;
|
||||
}, []);
|
||||
return set;
|
||||
};
|
||||
|
||||
const cycleValueInArray = function(results,operands,stepSize) {
|
||||
let resultsIndex,
|
||||
var cycleValueInArray = function(results,operands,stepSize) {
|
||||
var resultsIndex,
|
||||
step = stepSize || 1,
|
||||
i = 0,
|
||||
opLength = operands.length,
|
||||
nextOperandIndex;
|
||||
const opLength = operands.length;
|
||||
|
||||
for(; i < opLength; i++) {
|
||||
for(i; i < opLength; i++) {
|
||||
resultsIndex = results.indexOf(operands[i]);
|
||||
if(resultsIndex !== -1) {
|
||||
break;
|
||||
@@ -201,19 +213,18 @@ const cycleValueInArray = function(results,operands,stepSize) {
|
||||
|
||||
/*
|
||||
Toggles an item in the current list.
|
||||
*/
|
||||
*/
|
||||
exports.toggle = function(source,operator) {
|
||||
return cycleValueInArray(prepare_results(source),operator.operands);
|
||||
};
|
||||
|
||||
exports.cycle = function(source,operator) {
|
||||
const results = prepare_results(source),
|
||||
operands = operator.operand.length ? $tw.utils.parseStringArray(operator.operand,"true") : [""];
|
||||
let step = $tw.utils.getInt(operator.operands[1] || "",1);
|
||||
|
||||
var results = prepare_results(source),
|
||||
operands = (operator.operand.length ? $tw.utils.parseStringArray(operator.operand, "true") : [""]),
|
||||
step = $tw.utils.getInt(operator.operands[1]||"",1);
|
||||
if(step < 0) {
|
||||
operands.reverse();
|
||||
step = Math.abs(step);
|
||||
}
|
||||
return cycleValueInArray(results,operands,step);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ exports.init = function(parser) {
|
||||
};
|
||||
|
||||
exports.parse = function() {
|
||||
var reEnd = /(\r?\n```$)/mg;
|
||||
var reEnd = /(^|\r?\n)```$/mg;
|
||||
var languageStart = this.parser.pos + 3,
|
||||
languageEnd = languageStart + this.match[1].length;
|
||||
// Move past the match
|
||||
|
||||
@@ -15,7 +15,8 @@ var getCellInfo = function(text, start, length, SEPARATOR) {
|
||||
var isCellQuoted = text.charAt(start) === QUOTE;
|
||||
var cellStart = isCellQuoted ? start + 1 : start;
|
||||
|
||||
if(text.charAt(i) === SEPARATOR) {
|
||||
// A quote licenses a separator inside the cell, so only an unquoted cell reads an immediate separator as empty
|
||||
if(!isCellQuoted && text.charAt(cellStart) === SEPARATOR) {
|
||||
return [cellStart, cellStart, false];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
title: $:/editions/dev/github-fork-ribbon
|
||||
tags: $:/tags/PageTemplate
|
||||
caption: ~GitHub ribbon
|
||||
description: ~GitHub ribbon for tw5.com/dev
|
||||
|
||||
<div class="github-fork-ribbon-wrapper right" style><div class="github-fork-ribbon" style="background-color:#DF4848;"><a href="https://github.com/TiddlyWiki/TiddlyWiki5" target="_blank" rel="noopener noreferrer">Find me on ~GitHub</a></div></div>
|
||||
@@ -0,0 +1,22 @@
|
||||
title: Filters/DiffMergePatch4
|
||||
description: Tests for diff-merge-patch derived operators
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
tags: [[$:/tags/wiki-test-spec]]
|
||||
|
||||
title: Output
|
||||
|
||||
\whitespace trim
|
||||
\define text1()
|
||||
The quick brown fox
|
||||
\end
|
||||
|
||||
\define text2()
|
||||
The fast brown fox
|
||||
\end
|
||||
|
||||
<$text text={{{ [<text1>makepatches::json<text2>] }}}/>
|
||||
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
[{"type":"equal","text":"The "},{"type":"delete","text":"quick"},{"type":"insert","text":"fast"},{"type":"equal","text":" brown fox"}]
|
||||
@@ -1,14 +0,0 @@
|
||||
title: Filters/ListOps
|
||||
description: Test listops operators
|
||||
type: text/vnd.tiddlywiki-multiple
|
||||
tags: [[$:/tags/wiki-test-spec]]
|
||||
|
||||
title: Output
|
||||
|
||||
\whitespace trim
|
||||
(<$text text={{{ =[[E]] =[[A]] =[[B]] =[[C]] =[[C]] =[[D]] =[[C]] +[unique[]join[]] }}}/>)
|
||||
|
||||
+
|
||||
title: ExpectedResult
|
||||
|
||||
<p>(EABCD)</p>
|
||||
@@ -0,0 +1,69 @@
|
||||
/*\
|
||||
title: test-codeblock-parser.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests the codeblock wikitext rule (#9047): an empty code block must parse
|
||||
cleanly, and a closing fence only counts when it stands alone on its line.
|
||||
|
||||
\*/
|
||||
"use strict";
|
||||
|
||||
describe("codeblock parser tests (#9047)", function() {
|
||||
|
||||
var wiki = new $tw.Wiki();
|
||||
|
||||
function parse(text) {
|
||||
return wiki.parseText("text/vnd.tiddlywiki",text).tree;
|
||||
}
|
||||
|
||||
it("parses an empty code block with a language", function() {
|
||||
// Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\n```").tree
|
||||
// Expected: one codeblock node, code "", language "bash".
|
||||
var tree = parse("```bash\n```");
|
||||
expect(tree.length).toBe(1);
|
||||
expect(tree[0].type).toBe("codeblock");
|
||||
expect(tree[0].attributes.code.value).toBe("");
|
||||
expect(tree[0].attributes.language.value).toBe("bash");
|
||||
});
|
||||
|
||||
it("handles CRLF line endings around both fences", function() {
|
||||
// Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\r\nfoo\r\n```").tree
|
||||
// Expected: one codeblock node with code "foo"; neither the CRLF after the
|
||||
// opening fence nor the one before the closing fence is part of the code.
|
||||
var tree = parse("```bash\r\nfoo\r\n```");
|
||||
expect(tree.length).toBe(1);
|
||||
expect(tree[0].type).toBe("codeblock");
|
||||
expect(tree[0].attributes.code.value).toBe("foo");
|
||||
});
|
||||
|
||||
it("keeps the content of a block and drops the delimiting newlines", function() {
|
||||
// Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```\nfoo\n```").tree
|
||||
// Expected: one codeblock node with code "foo"; the newlines around the
|
||||
// delimiter lines are not part of the code.
|
||||
var tree = parse("```\nfoo\n```");
|
||||
expect(tree.length).toBe(1);
|
||||
expect(tree[0].type).toBe("codeblock");
|
||||
expect(tree[0].attributes.code.value).toBe("foo");
|
||||
});
|
||||
|
||||
it("only closes the block when the fence stands alone on its line", function() {
|
||||
// A content line ending in ``` is code, not a closing fence, e.g. nested
|
||||
// markdown fences or template literals quoted inside a code block.
|
||||
// Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```\nabc```\ndef\n```").tree
|
||||
// Expected: one codeblock containing "abc```\ndef"; nothing leaks out as wikitext.
|
||||
var tree = parse("```\nabc```\ndef\n```");
|
||||
expect(tree.length).toBe(1);
|
||||
expect(tree[0].type).toBe("codeblock");
|
||||
expect(tree[0].attributes.code.value).toBe("abc```\ndef");
|
||||
});
|
||||
|
||||
it("swallows the rest of the tiddler when no closing fence exists", function() {
|
||||
// Browser console: $tw.wiki.parseText("text/vnd.tiddlywiki","```bash\nfoo").tree
|
||||
// Expected: one codeblock node with code "foo", extending to the end of the text.
|
||||
var tree = parse("```bash\nfoo");
|
||||
expect(tree.length).toBe(1);
|
||||
expect(tree[0].type).toBe("codeblock");
|
||||
expect(tree[0].attributes.code.value).toBe("foo");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*\
|
||||
title: test-csv.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests the CSV parser.
|
||||
|
||||
\*/
|
||||
|
||||
"use strict";
|
||||
|
||||
describe("CSV tests", function() {
|
||||
|
||||
it("parses a simple table", function() {
|
||||
expect($tw.utils.parseCsvString("a,b,c\n1,2,3")).toEqual([["a","b","c"],["1","2","3"]]);
|
||||
});
|
||||
|
||||
it("parses a table whose first cell reads empty", function() {
|
||||
expect($tw.utils.parseCsvString(",b,c\n1,2,3")).toEqual([["","b","c"],["1","2","3"]]);
|
||||
});
|
||||
|
||||
it("parses an empty cell in the middle of a row", function() {
|
||||
expect($tw.utils.parseCsvString("a,,c")).toEqual([["a","","c"]]);
|
||||
});
|
||||
|
||||
it("parses quoted cells", function() {
|
||||
expect($tw.utils.parseCsvString('a,"b,still b",c')).toEqual([["a","b,still b","c"]]);
|
||||
});
|
||||
|
||||
it("parses a quoted cell holding an escaped quote", function() {
|
||||
expect($tw.utils.parseCsvString('a,"b ""quoted""",c')).toEqual([["a",'b "quoted"',"c"]]);
|
||||
});
|
||||
|
||||
it("honours a custom separator", function() {
|
||||
expect($tw.utils.parseCsvString("a;b;c",{separator: ";"})).toEqual([["a","b","c"]]);
|
||||
});
|
||||
|
||||
it("parses a table with a header row into hashmaps", function() {
|
||||
expect($tw.utils.parseCsvStringWithHeader("a,b\n1,2")).toEqual([{a: "1", b: "2"}]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/*\
|
||||
title: test-filesystem.js
|
||||
type: application/javascript
|
||||
tags: [[$:/tags/test-spec]]
|
||||
|
||||
Tests for $:/core-server filesystem utilities.
|
||||
|
||||
\*/
|
||||
"use strict";
|
||||
|
||||
if($tw.node) {
|
||||
|
||||
var path = require("path");
|
||||
|
||||
describe("generateTiddlerFilepath", function() {
|
||||
|
||||
var directory = path.resolve("/tmp/tw5-test-filesystem");
|
||||
|
||||
// Characters stripped by the cross-platform-filename regex at
|
||||
// core-server/filesystem.js:356. Each entry is [char, description].
|
||||
// The forbidden set includes all characters disallowed by Windows
|
||||
// (< > : " | ? *), backslash (directory separator on Windows),
|
||||
// tilde (legacy 8.3 short-name marker), and caret (disallowed in
|
||||
// some shell/FS contexts).
|
||||
var forbiddenChars = [
|
||||
["<","less-than"],
|
||||
[">","greater-than"],
|
||||
["~","tilde"],
|
||||
[":","colon"],
|
||||
["\"","double-quote"],
|
||||
["|","pipe"],
|
||||
["?","question-mark"],
|
||||
["*","asterisk"],
|
||||
["^","caret"],
|
||||
["\\","backslash"]
|
||||
];
|
||||
|
||||
// Use originalpath so we exercise the line-356 regex directly.
|
||||
// The title-branch at line ~342 pre-strips "/" and "\" before the
|
||||
// main regex runs, which would mask the backslash case.
|
||||
function filepathFromOriginalpath(title,extension) {
|
||||
return $tw.utils.generateTiddlerFilepath(title,{
|
||||
extension: extension,
|
||||
directory: directory,
|
||||
fileInfo: {
|
||||
overwrite: true,
|
||||
originalpath: title + extension
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
forbiddenChars.forEach(function(entry) {
|
||||
var char = entry[0], name = entry[1];
|
||||
it("should replace " + name + " (" + char + ") with underscore", function() {
|
||||
var result = filepathFromOriginalpath("a" + char + "b",".tid");
|
||||
expect(path.dirname(result)).toBe(directory);
|
||||
expect(path.basename(result)).toBe("a_b.tid");
|
||||
});
|
||||
});
|
||||
|
||||
it("should replace every forbidden char in a dense string", function() {
|
||||
// Prefix with "x" so the sanitized result isn't all underscores,
|
||||
// which would trigger the charcode-fallback branch at line ~371.
|
||||
var title = "x" + forbiddenChars.map(function(e) { return e[0]; }).join("");
|
||||
var expected = "x" + forbiddenChars.map(function() { return "_"; }).join("");
|
||||
var result = filepathFromOriginalpath(title,".tid");
|
||||
expect(path.dirname(result)).toBe(directory);
|
||||
expect(path.basename(result)).toBe(expected + ".tid");
|
||||
});
|
||||
|
||||
it("should sanitize real-world 'Pragma: \\define' title without creating a subdirectory", function() {
|
||||
// Issue for editions/tw5.com titles like "Pragma: \define".
|
||||
// Before the fix, backslash survived sanitization and path.resolve
|
||||
// treated it as a separator on Windows, producing a "Pragma_ "
|
||||
// subdir containing "define.tid".
|
||||
var result = filepathFromOriginalpath("Pragma: \\define",".tid");
|
||||
expect(path.dirname(result)).toBe(directory);
|
||||
expect(path.basename(result)).toBe("Pragma_ _define.tid");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -173,5 +173,20 @@ describe("json filter tests", function() {
|
||||
expect(wiki.filterTiddlers("[{First}format:json[ ]]")).toEqual(["{\n \"a\": \"one\",\n \"b\": \"\",\n \"c\": 1.618,\n \"d\": {\n \"e\": \"four\",\n \"f\": [\n \"five\",\n \"six\",\n true,\n false,\n null\n ]\n }\n}"]);
|
||||
});
|
||||
|
||||
it("should support the makepatches operator with json output", function() {
|
||||
expect(wiki.filterTiddlers("[[The quick brown fox]makepatches::json[The fast brown fox]]")).toEqual([
|
||||
'[{"type":"equal","text":"The "},{"type":"delete","text":"quick"},{"type":"insert","text":"fast"},{"type":"equal","text":" brown fox"}]'
|
||||
]);
|
||||
|
||||
expect(wiki.filterTiddlers("[[The quick brown fox]makepatches:words:json[The fast brown fox]]")).toEqual([
|
||||
'[{"type":"equal","text":"The "},{"type":"delete","text":"quick "},{"type":"insert","text":"fast "},{"type":"equal","text":"brown fox"}]'
|
||||
]);
|
||||
|
||||
// Safely ignores missing/invalid second suffix and returns a standard patch string instead of JSON
|
||||
expect(wiki.filterTiddlers("[[The quick brown fox]makepatches:words[The fast brown fox]]")[0]).not.toContain(
|
||||
'"type":"equal"'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
created: 20230304160331362
|
||||
modified: 20230304160332927
|
||||
modified: 20260724082228670
|
||||
tags: [[makepatches Operator]] [[applypatches Operator]] [[Operator Examples]]
|
||||
title: makepatches and applypatches Operator (Examples)
|
||||
type: text/vnd.tiddlywiki
|
||||
@@ -40,4 +40,12 @@ The `lines` mode doesn't work as well in this application:
|
||||
|
||||
It is better suited as a very fast algorithm to detect line-wise incremental changes to texts and store only the changes instead of multiple versions of the whole texts.
|
||||
|
||||
The `json` suffix outputs a structured JSON array representing the differences instead of a patch string. To use the JSON output with the default character mode, skip the first suffix with a double colon (`::`).
|
||||
|
||||
<<.operator-example 8 "[{Hamlet##Shakespeare-old}makepatches::json{Hamlet##Shakespeare-new}]">>
|
||||
|
||||
The `json` suffix can also be combined with the `words` or `lines` modes:
|
||||
|
||||
<<.operator-example 9 "[{Hamlet##Shakespeare-old}makepatches:words:json{Hamlet##Shakespeare-new}]">>
|
||||
|
||||
</div>
|
||||
@@ -1,23 +1,28 @@
|
||||
caption: makepatches
|
||||
created: 20230304122354967
|
||||
modified: 20230304122400128
|
||||
op-purpose: returns a set of patches that transform the input to a given string
|
||||
modified: 20260724081502905
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-output: a set of patch instructions per input title to be used by the [[applypatches Operator]] to transform the input title(s) into the string <<.place S>>, or a structured JSON array representing the differences if the `json` suffix is used
|
||||
op-parameter: a string of characters
|
||||
op-parameter-name: S
|
||||
op-output: a set of patch instructions per input title to be used by the [[applypatches Operator]] to transform the input title(s) into the string <<.place S>>
|
||||
op-suffix: `lines` to operate in line mode, `words` to operate in word mode. If omitted (default), the algorithm operates in character mode. See notes below.
|
||||
op-suffix-name: T
|
||||
op-purpose: returns a set of patches that transform the input to a given string
|
||||
op-suffix: optional suffixes specifying the diff mode and output format
|
||||
op-suffix-name: T:F
|
||||
tags: [[Filter Operators]] [[String Operators]]
|
||||
title: makepatches Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
<<.from-version "5.2.6">>
|
||||
|
||||
The <<.op makepatches>> operator uses up to two suffixes:
|
||||
|
||||
* <<.place T>> (diff mode): `lines` to operate in line mode, `words` to operate in word mode. If omitted (default), the algorithm operates in character mode.
|
||||
* <<.place F>> (output format): <<.from-version "5.5.0">> `json` to output a structured JSON array of differences instead of a standard patch string. To use the JSON output with the default character mode, skip the first suffix with a double colon (e.g., `makepatches::json`).
|
||||
|
||||
The difference algorithm operates in character mode by default. This produces the most detailed diff possible. In `words` mode, each word in the input text is transformed into a meta-character, upon which the algorithm then operates. In the default character mode, the filter would find two patches between "ActionWidget" and "Action-Widgets" (the hyphen and the plural s), while in `words` mode, the whole word is found to be changed. In `lines` mode, the meta-character is formed from the whole line, delimited by newline characters, and is found to be changed independent of the number of changes within the line.
|
||||
|
||||
The different modes influence the result when the patches are applied to texts other than the original, as well as the runtime.
|
||||
|
||||
<<.tip "The calculation in `words` mode is roughly 10 times faster than the default character mode, while `lines` mode can be more than 100 times faster than the default.">>
|
||||
|
||||
<<.operator-examples "makepatches and applypatches">>
|
||||
<<.operator-examples "makepatches and applypatches">>
|
||||
@@ -4,7 +4,7 @@ created: 20131219100608529
|
||||
delivery: DIY
|
||||
description: Flexible hosting on your own machine or in the cloud
|
||||
method: sync
|
||||
modified: 20221115230831173
|
||||
modified: 20260712162730421
|
||||
tags: Saving [[TiddlyWiki on Node.js]] Windows Mac Linux
|
||||
title: Installing TiddlyWiki on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
@@ -39,4 +39,39 @@ The `-g` flag causes TiddlyWiki to be installed globally. Without it, TiddlyWiki
|
||||
|
||||
<<.warning "If you are using Debian or Debian-based Linux and you are receiving a `node: command not found` error though node.js package is installed, you may need to create a symbolic link between `nodejs` and `node`. Consult your distro's manual and `whereis` to correctly create a link. See github [[issue 1434|http://github.com/TiddlyWiki/TiddlyWiki5/issues/1434]]. <br><br>Example Debian v8.0: `sudo ln -s /usr/bin/nodejs /usr/bin/node`">>
|
||||
<br>
|
||||
<<.tip "You can also install prior versions like this: <br><code> npm install -g tiddlywiki@5.1.13</code>">>
|
||||
<<.tip "You can also install prior versions like this: <br><code> npm install -g tiddlywiki@5.1.13</code><br>Note: this will overwrite the installed version rather than installing it alongside.">>
|
||||
|
||||
!! Installing multiple Node.js verions alongside
|
||||
|
||||
There are multiple options:
|
||||
|
||||
!!! Local install
|
||||
|
||||
```
|
||||
mkdir -p /home/user/opt/tiddlywiki-5.1.13 && cd "$_" # works in Bash
|
||||
npm install tiddlywiki@5.1.13
|
||||
```
|
||||
|
||||
This installs the given version in the current directory, at the expense of taking some extra disk space. The binary is in `node_modules/.bin/tiddlywiki` (relative to the current directory). For convenience, it is possible to create a symlink (in Linux) containing explicit version number in the name, like this:
|
||||
|
||||
```
|
||||
ln -s /home/user/opt/tiddlywiki-5.1.13/node_modules/.bin/tiddlywiki /home/user/bin/tiddlywiki-5.1.13
|
||||
```
|
||||
|
||||
Having `~/bin` in `PATH` environment variable (usually at the beginning of it), makes it possible to start this version of TiddlyWiki as simple as `tiddlywiki-5.1.13`.
|
||||
|
||||
!!! Use `npx`
|
||||
|
||||
To run another version without overwriting the currently installed version, use npx:
|
||||
|
||||
```
|
||||
npx tiddlywiki@5.3.8 --version
|
||||
```
|
||||
|
||||
<<.warning "This comes with a security risk, since it downloads and executes a package from the internet. See https://talk.tiddlywiki.org/t/tiddlywiki-docs-gettingstarted-node-js/15423/4">>
|
||||
|
||||
!!! Run directly from the source code repository
|
||||
|
||||
# clone https://github.com/TiddlyWiki/TiddlyWiki5/ locally
|
||||
# checkout any version (using `git checkout` or `git switch` for modern versions of Git)
|
||||
# set up a link or a shell script for it
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
title: $:/changenotes/5.4.0/#8972/impacts/parenthesised-field-names
|
||||
changenote: $:/changenotes/5.4.0/#8972
|
||||
created: 20260609193636000
|
||||
modified: 20260609193636000
|
||||
tags: $:/tags/ImpactNote
|
||||
impact-type: compatibility-break
|
||||
description: A field name containing round brackets can no longer be used as a filter operator suffix, because `(` now starts a multi value variable operand.
|
||||
|
||||
Two filter operators take a field name in the suffix position: `regexp` and `search`. Up to 5.3.8 such a field name could contain round brackets, for example:
|
||||
|
||||
```
|
||||
[regexp:_cd-work(s)[(?i)suite]]
|
||||
[search:_cd-work(s)[suite]]
|
||||
```
|
||||
|
||||
In 5.4.0 the new `(varname)` operand syntax for [[Multi-Valued Variables]] makes `(` start an operand. The parser now splits such a field name at the `(`, so the filter no longer matches.
|
||||
|
||||
This will not be fixed. The two readings of `field(x)`, a field named `field(x)` versus the field `field` with the operand `(x)`, are genuinely ambiguous, so `(` and `)` are reserved in filter operator names and suffixes from 5.4.0 onwards.
|
||||
|
||||
Workaround: do not use `(` or `)` in field names that are referenced as a filter operator suffix. Rename the field, for example `_cd-works`.
|
||||
@@ -0,0 +1,13 @@
|
||||
change-category: hackability
|
||||
change-type: bugfix
|
||||
created: 20260711181412000
|
||||
description: Empty code blocks parse cleanly; a closing fence only counts alone on its line
|
||||
github-contributors: pmario
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9739
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
title: $:/changenotes/5.5.0/#9739
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
* An empty code block such as ```` ```bash ```` directly followed by the closing fence parses as an empty codeblock instead of swallowing the rest of the tiddler (fixes [[Issue #9047|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9047]])
|
||||
* A closing fence only ends the block when it stands alone on its line, so code lines ending in three backticks stay inside the block
|
||||
@@ -0,0 +1,13 @@
|
||||
change-category: nodejs
|
||||
change-type: bugfix
|
||||
created: 20260711175727000
|
||||
description: Tiddler titles containing a backslash no longer break generated file paths
|
||||
github-contributors: pmario
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9815
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
title: $:/changenotes/5.5.0/#9815
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
* Titles containing a backslash, such as `pragma: \define`, get the backslash replaced like other forbidden filename characters instead of it acting as a Windows path separator (fixes [[Issue #9814|https://github.com/TiddlyWiki/TiddlyWiki5/issues/9814]])
|
||||
* Tiddler files declared in `tiddlywiki.files` are pinned to their original location and skip the `$:/config/FileSystemPaths` filters and filename sanitising
|
||||
@@ -0,0 +1,10 @@
|
||||
title: $:/changenotes/5.5.0/#9891
|
||||
description: Move background action log inside platforms check to avoid spurious server-side logs
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: internal
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9891
|
||||
github-contributors: linonetwo
|
||||
|
||||
Background actions with `platforms: browser` were logging on the server even though execution was correctly skipped. The log now only prints when the action actually runs.
|
||||
@@ -0,0 +1,13 @@
|
||||
title: $:/changenotes/5.5.0/#9919
|
||||
created: 20260712000000000
|
||||
modified: 20260712000000000
|
||||
description: Fix the CSV parser returning every cell empty
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: bugfix
|
||||
change-category: internal
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9919
|
||||
github-contributors: joshuafontany
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
`getCellInfo` read its loop variable before the loop declared it, so hoisting made it undefined and the parser tested the first character of the whole text rather than the first character of the cell. Any CSV opening with a separator therefore returned every cell empty, and the table rendered blank.
|
||||
@@ -0,0 +1,10 @@
|
||||
title: $:/changenotes/5.5.0/#9933
|
||||
description: Update Chinese translations
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
change-type: enhancement
|
||||
change-category: translation
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9933
|
||||
github-contributors: BramChen
|
||||
|
||||
* Add `WikiInformation/Generate/Caption` in `ControlPanel.multids`
|
||||
@@ -0,0 +1,12 @@
|
||||
change-category: filters
|
||||
change-type: enhancement
|
||||
created: 20260724080519370
|
||||
description: The makepatches operator can now output structured JSON
|
||||
github-contributors: DesignThinkerer
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9940
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
title: $:/changenotes/5.5.0/#9940
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
* The [[makepatches|makepatches Operator]] operator now supports a `:json` suffix (e.g., `makepatches::json`) that outputs a structured JSON array of the differences, enabling easier and robust parsing with WikiText
|
||||
@@ -1,13 +0,0 @@
|
||||
change-category: filters
|
||||
change-type: enhancement
|
||||
created: 20260726124618748
|
||||
description: Optimizes listops filter operators
|
||||
github-contributors: saqimtiaz
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9942
|
||||
modified: 20260726125232992
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
title: $:/changenotes/5.5.0/#9942
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
* The listops filter operators have been rewritten using more modern JavaScript (ES2017). The `unique` and `remove` operators have improved performance in certain circumstances.
|
||||
@@ -0,0 +1,13 @@
|
||||
change-category: filters
|
||||
change-type: enhancement
|
||||
created: 20260726124618748
|
||||
description: Optimizes filterrun prefixes and select filters using modern JavaScript.
|
||||
github-contributors: saqimtiaz
|
||||
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9943
|
||||
modified: 20260726130207664
|
||||
release: 5.5.0
|
||||
tags: $:/tags/ChangeNote
|
||||
title: $:/changenotes/5.5.0/#9943
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
* The `intersection` and `sort` filterrun prefixes, and the `prefix`, `sortsub`, `subfilter` and `suffix` operators have been optimized using more modern JavaScript (ES2017).
|
||||
@@ -7,7 +7,7 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
!! Basic syntax
|
||||
|
||||
HTML description lists (<abbr title="also known as">AKA</abbr> definition lists) are created with this syntax:
|
||||
HTML description lists (<abbr title="also known as">AKA</abbr> definition lists) are created with this syntax:
|
||||
|
||||
<<wikitext-example src:"; Term being described
|
||||
: Description / Definition of that term
|
||||
@@ -17,7 +17,7 @@ HTML description lists (<abbr title="also known as">AKA</abbr> definition lists)
|
||||
|
||||
!! Multiple terms and descriptions
|
||||
|
||||
You can create multiple descriptions for a term, or multiple terms for a single description:
|
||||
You can create multiple descriptions for a term, or multiple terms with a single description:
|
||||
|
||||
<<wikitext-example src:"; Mouse
|
||||
: A rodent with a small body and a long tail
|
||||
@@ -41,4 +41,4 @@ Description lists may also be nested to create lists within lists:
|
||||
::: A coffee made with espresso and steamed milk
|
||||
; Tea
|
||||
: A beverage typically made from tea leaves
|
||||
">>
|
||||
">>
|
||||
@@ -256,4 +256,5 @@ ViewTemplateTags/Caption: 查看模板标签区
|
||||
ViewTemplateTags/Hint: 默认的查看模板使用此规则级联,动态选择模板以显示条目的标签区域。
|
||||
WikiInformation/Caption: 维基信息
|
||||
WikiInformation/Hint: 此页面总结了有关此 ~TiddlyWiki 配置的高阶信息。旨在让用户能够快速与他人共享其 ~TiddlyWiki 配置的相关信息,例如,在其中一个论坛中寻求帮助时。不包含隐私或个人信息,没有明确复制和粘贴在其他地方的情况
|
||||
WikiInformation/Drag/Caption: 拖动此链接可将此工具复制到另一个维基
|
||||
WikiInformation/Drag/Caption: 拖动此链接可将此工具复制到另一个维基
|
||||
WikiInformation/Generate/Caption: 生成维基信息报告
|
||||
@@ -256,4 +256,5 @@ ViewTemplateTags/Caption: 檢視範本標籤
|
||||
ViewTemplateTags/Hint: 預設的檢視範本使用此規則級聯,動態選擇範本以顯示條目的標籤。
|
||||
WikiInformation/Caption: 維基資訊
|
||||
WikiInformation/Hint: 此頁面總結了有關此 ~TiddlyWiki 配置的高階資訊。旨在讓使用者能夠快速與他人共享其 ~TiddlyWiki 配置的相關資訊,例如,在其中一個論壇中尋求幫助時。不包含隱私或個人資訊,沒有明確複製和粘貼在其他地方的情況
|
||||
WikiInformation/Drag/Caption: 拖動此連結可將此工具複製到另一個維基
|
||||
WikiInformation/Drag/Caption: 拖動此連結可將此工具複製到另一個維基
|
||||
WikiInformation/Generate/Caption: 產生維基資訊報告
|
||||
@@ -650,4 +650,5 @@ Rishu kumar, @rishu-7549, 2025/10/25
|
||||
|
||||
Himmel, @NotHimmel, 2026/03/19
|
||||
|
||||
@sean-clayton, 2026/05/16
|
||||
|
||||
@vuktw, 2026/07/12
|
||||
|
||||
Reference in New Issue
Block a user