1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-11-23 10:07:19 +00:00

Refactor the filter mechanism

Long overdue rewrite to make it simpler, and break the filter operators
out into individual modules.
This commit is contained in:
Jeremy Ruston 2013-05-25 17:26:22 +01:00
parent 6ada78daa2
commit 0cf5dc699e
24 changed files with 1089 additions and 271 deletions

View File

@ -12,274 +12,6 @@ Adds tiddler filtering to the $tw.Wiki object.
/*global $tw: false */
"use strict";
exports.filterTiddlers = function(filterString,currTiddlerTitle,tiddlerList) {
var fn = this.compileFilter(filterString);
return fn.call(this,tiddlerList || this.tiddlers,currTiddlerTitle);
};
/*
Compiling a filter gives a JavaScript function that is invoked as `this.filter(source)`, where `source` is a hashmap of source tiddler titles (the values don't matter, so it is possible to use a store or a changes object). It returns an array of tiddler titles that satisfy the filter
*/
exports.compileFilter = function(filterString) {
var filter = this.parseFilter(filterString),
output = [],
t,operation,operationInfo,type,p,operator,operatorInfo,fn;
output.push(this.filterFragments.prologue);
for(t=0; t<filter.length; t++) {
operation = filter[t];
operationInfo = this.filterFragments.operation.prefix[operation.prefix || ""];
output.push(operationInfo.prologue);
type = "selector";
if(operation.prefix === "+") {
type = "filter";
}
for(p=0; p<operation.operators.length; p++) {
operator = operation.operators[p];
operatorInfo = this.operators[operator.operator];
if(!operatorInfo) { // Check for it being a field operator
operatorInfo = this.operators.field;
}
output.push(operatorInfo[type](operator));
type = "filter";
}
output.push(operationInfo.epilogue);
}
output.push(this.filterFragments.epilogue);
try {
/*jslint evil: true */
fn = eval(output.join("\n"));
} catch(ex) {
throw "Error in filter expression: " + ex;
}
return fn;
};
exports.filterFragments = {
prologue: "(function(source,currTiddlerTitle) {\nvar results = [], subResults, subResultsTemp, title, r, t;",
epilogue: "return results;\n})",
operation: {
prefix: {
"": {
prologue: "subResults = [];",
epilogue: "$tw.utils.pushTop(results,subResults);"
},
"+": {
prologue: "subResults = results.slice(0);\nresults.splice(0,results.length);",
epilogue: "$tw.utils.pushTop(results,subResults);"
},
"-": {
prologue: "subResults = [];",
epilogue: "$tw.utils.removeArrayEntries(results,subResults);"
}
}
}
};
exports.operators = {
"title": { // Filter by title
selector: function(operator) {
if(operator.prefix === "!") {
return "for(title in source) {if(title !== \"" + $tw.utils.stringify(operator.operand) + "\") {$tw.utils.pushTop(subResults,title);}}";
} else {
return "$tw.utils.pushTop(subResults,\"" + $tw.utils.stringify(operator.operand) + "\");";
}
},
filter: function(operator) {
var op = operator.prefix === "!" ? "!" : "=";
return "if(subResults.indexOf(\"" + $tw.utils.stringify(operator.operand) + "\") " + op + "== -1) {subResults = [\"" + $tw.utils.stringify(operator.operand) + "\"];} else {subResults = [];}";
}
},
"prefix": { // Filter by title prefix
selector: function(operator) {
var op = operator.prefix === "!" ? "!" : "=";
return "for(title in source) {if(title.substr(0," + operator.operand.length + ")" + op + "==\"" + $tw.utils.stringify(operator.operand) + "\") {$tw.utils.pushTop(subResults,title);}}";
},
filter: function(operator) {
var op = operator.prefix === "!" ? "=" : "!";
return "for(r=subResults.length-1; r>=0; r--) {if(title.substr(0," + operator.operand.length + ")" + op + "==\"" + $tw.utils.stringify(operator.operand) + "\") {subResults.splice(r,1);}}";
}
},
"is": { // Filter by status
selector: function(operator) {
var op = operator.prefix === "!" ? "!" : "";
switch(operator.operand) {
case "current":
if(operator.prefix === "!") {
return "for(title in source) {if(title !== currTiddlerTitle) {$tw.utils.pushTop(subResults,title);}}";
} else {
return "$tw.utils.pushTop(subResults,currTiddlerTitle);";
}
break;
case "system":
return "for(title in source) {if(" + op + "this.isSystemTiddler(title)) {$tw.utils.pushTop(subResults,title);}}";
case "shadow":
if(operator.prefix === "!") {
return "for(title in source) {if(!this.isShadowTiddler(title)) {$tw.utils.pushTop(subResults,title);}}";
} else {
return "for(title in this.shadowTiddlers) {$tw.utils.pushTop(subResults,title);}";
}
case "missing":
if(operator.prefix === "!") {
return "for(title in source) {$tw.utils.pushTop(subResults,title);}";
} else {
return "var m = this.getMissingTitles(); for(t=0; t<m.length; t++) {$tw.utils.pushTop(subResults,m[t]);}";
}
case "orphan":
if(operator.prefix === "!") {
return "var m = this.getOrphanTitles(); for(title in source) {if(m.indexOf(title) === -1) {$tw.utils.pushTop(subResults,title);}}";
} else {
return "var m = this.getOrphanTitles(); for(t=0; t<m.length; t++) {$tw.utils.pushTop(subResults,m[t]);}";
}
default:
throw "Unknown operand for 'is' filter operator";
}
},
filter: function(operator) {
var op = operator.prefix === "!" ? "" : "!";
switch(operator.operand) {
case "current":
if(operator.prefix === "!") {
return "for(r=subResults.length-1; r>=0; r--) {if(subResults[r] === currTiddlerTitle) {subResults.splice(r,1);}}";
} else {
return "r = subResults.indexOf(currTiddlerTitle);\nif(r !== -1) {subResults = [currTiddlerTitle];} else {subResults = [];}";
}
break;
case "system":
return "for(r=subResults.length-1; r>=0; r--) {if(" + op + "this.isSystemTiddler(subResults[r])) {subResults.splice(r,1);}}";
case "shadow":
return "for(r=subResults.length-1; r>=0; r--) {if(" + op + "this.isShadowTiddler(subResults[r])) {subResults.splice(r,1);}}";
case "missing":
return "t = this.getMissingTitles(); for(r=subResults.length-1; r>=0; r--) {if(" + op + "!$tw.utils.hop(t,subResults[r])) {subResults.splice(r,1);}}";
case "orphan":
if(operator.prefix === "!") {
return "t = this.getOrphanTitles(); for(r=subResults.length-1; r>=0; r--) {if(t.indexOf(subResults[r]) === -1) {subResults.splice(r,1);}}";
} else {
return "t = this.getOrphanTitles(); for(r=subResults.length-1; r>=0; r--) {if(t.indexOf(subResults[r]) !== -1) {subResults.splice(r,1);}}";
}
default:
throw "Unknown operand for 'is' filter operator";
}
}
},
"tag": { // Filter by tag
selector: function(operator) {
var op = operator.prefix === "!" ? "!" : "";
return "for(title in source) {if(" + op + "this.getTiddler(title).hasTag(\"" + $tw.utils.stringify(operator.operand) + "\")) {$tw.utils.pushTop(subResults,title);}}";
},
filter: function(operator) {
var op = operator.prefix === "!" ? "" : "!";
return "for(r=subResults.length-1; r>=0; r--) {if(" + op + "this.getTiddler(subResults[r]).hasTag(\"" + $tw.utils.stringify(operator.operand) + "\")) {subResults.splice(r,1);}}";
}
},
"tags": { // Return all tags used on selected tiddlers
selector: function(operator) {
return "for(title in source) {r = this.getTiddler(title); if(r && r.fields.tags) {$tw.utils.pushTop(subResults,r.fields.tags);}}";
},
filter: function(operator) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {r = this.getTiddler(subResultsTemp[t]); if(r && r.fields.tags) {$tw.utils.pushTop(subResults,r.fields.tags);}}";
}
},
"tagging": { // Return all tiddlers tagged with any of the selected tags
selector: function(operator) {
return "for(title in source) {$tw.utils.pushTop(subResults,this.getTiddlersWithTag(title));}";
},
filter: function(operator) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {$tw.utils.pushTop(subResults,this.getTiddlersWithTag(subResultsTemp[t]));}";
}
},
"links": { // Return outgoing links on selected tiddlers
selector: function(operator) {
return "for(title in source) {r = this.getTiddlerLinks(title); $tw.utils.pushTop(subResults,r);}";
},
filter: function(operator) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {r = this.getTiddlerLinks(subResultsTemp[t]); $tw.utils.pushTop(subResults,r);}";
}
},
"backlinks": { // Return incoming links on selected tiddlers
selector: function(operator) {
return "for(title in source) {r = this.getTiddlerBacklinks(title); $tw.utils.pushTop(subResults,r);}";
},
filter: function(operator) {
return "subResultsTemp = subResults;\nsubResults = [];for(t=subResultsTemp.length-1; t>=0; t--) {r = this.getTiddlerBacklinks(subResultsTemp[t]); $tw.utils.pushTop(subResults,r);}";
}
},
"has": { // Filter by presence of a particular field
selector: function(operator) {
var op = operator.prefix === "!" ? "=" : "!";
return "for(title in source) {if(this.getTiddler(title).fields[\"" + $tw.utils.stringify(operator.operand) + "\"] " + op + "== undefined) {$tw.utils.pushTop(subResults,title);}}";
},
filter: function(operator) {
var op = operator.prefix === "!" ? "!" : "=";
return "for(r=subResults.length-1; r>=0; r--) {if(this.getTiddler(subResults[r]).fields[\"" + $tw.utils.stringify(operator.operand) + "\"] " + op + "== undefined) {subResults.splice(r,1);}}";
}
},
"sort": { // Sort selected tiddlers
selector: function(operator) {
throw "Cannot use sort operator at the start of a filter operation";
},
filter: function(operator) {
var desc = operator.prefix === "!" ? "true" : "false";
return "this.sortTiddlers(subResults,\"" + $tw.utils.stringify(operator.operand) + "\"," + desc + ");";
}
}, // Case insensitive sort of selected tiddlers
"sort-case-sensitive": {
selector: function(operator) {
throw "Cannot use sort operator at the start of a filter operation";
},
filter: function(operator) {
var desc = operator.prefix === "!" ? "true" : "false";
return "this.sortTiddlers(subResults,\"" + $tw.utils.stringify(operator.operand) + "\"," + desc + ",true);";
}
},
"limit": { // Limit number of members of selection
selector: function(operator) {
throw "Cannot use limit operator at the start of a filter operation";
},
filter: function(operator) {
var limit = parseInt(operator.operand,10),
base = operator.prefix === "!" ? 0 : limit;
return "if(subResults.length > " + limit + ") {subResults.splice(" + base + ",subResults.length-" + limit + ");}";
}
},
"list": { // Select all tiddlers that are listed (or not listed) in the specified tiddler
selector: function(operator) {
if(operator.prefix === "!") {
return "var list = this.getTiddlerList(\"" + $tw.utils.stringify(operator.operand) + "\");" +
"for(title in source) {if(list.indexOf(title) === -1) {$tw.utils.pushTop(subResults,title);}}";
} else {
return "$tw.utils.pushTop(subResults,this.getTiddlerList(\"" + $tw.utils.stringify(operator.operand) + "\"));";
}
},
filter: function(operator) {
var op = operator.prefix === "!" ? "!==" : "===";
return "var list = this.getTiddlerList(\"" + $tw.utils.stringify(operator.operand) + "\");" +
"for(r=subResults.length-1; r>=0; r--) {if(list.indexOf(title) " + op + " -1) {subResults.splice(r,1);}}";
}
},
"searchVia": { // Search for the string in the operand tiddler
selector: function(operator) {
var op = operator.prefix === "!" ? "true" : "false";
return "var term = this.getTiddler(\"" + $tw.utils.stringify(operator.operand) + "\").fields.text;" +
"$tw.utils.pushTop(subResults,this.search(term,{titles: source, invert: " + op + ", exclude: [\"" + $tw.utils.stringify(operator.operand) + "\"]}));";
},
filter: function(operator) {
var op = operator.prefix === "!" ? "true" : "false";
return "var term = this.getTiddler(\"" + $tw.utils.stringify(operator.operand) + "\").fields.text;" +
"subResults = this.search(term,{titles: subResults, invert: " + op + ", exclude: [\"" + $tw.utils.stringify(operator.operand) + "\"]});";
}
},
"field": { // Special handler for field comparisons
selector: function(operator) {
var op = operator.prefix === "!" ? "!" : "=";
return "for(title in source) {if(this.getTiddler(title).fields[\"" + $tw.utils.stringify(operator.operator) + "\"] " + op + "== \"" + operator.operand + "\") {$tw.utils.pushTop(subResults,title);}}";
},
filter: function(operator) {
var op = operator.prefix === "!" ? "=" : "!";
return "for(r=subResults.length-1; r>=0; r--) {if(this.getTiddler(subResults[r]).fields[\"" + $tw.utils.stringify(operator.operator) + "\"] " + op + "== \"" + operator.operand + "\") {subResults.splice(r,1);}}";
}
}
};
/*
Parses an operation within a filter string
results: Array of array of operator nodes into which results should be inserted
@ -376,4 +108,70 @@ exports.parseFilter = function(filterString) {
return results;
};
exports.getFilterOperators = function() {
if(!this.filterOperators) {
$tw.Wiki.prototype.filterOperators = {};
$tw.modules.applyMethods("filteroperator",this.filterOperators);
}
return this.filterOperators;
};
exports.filterTiddlers = function(filterString,currTiddlerTitle,tiddlerList) {
var fn = this.compileFilter(filterString);
return fn.call(this,tiddlerList || this.tiddlers,currTiddlerTitle);
};
exports.compileFilter = function(filterString) {
var filterParseTree = this.parseFilter(filterString);
// Get the hashmap of filter operator functions
var filterOperators = this.getFilterOperators();
// Assemble array of functions, one for each operation
var operationFunctions = [];
// Step through the operations
var self = this;
$tw.utils.each(filterParseTree,function(operation) {
// Create a function for the chain of operators in the operation
var operationSubFunction = function(source,currTiddlerTitle) {
var accumulator = source,
results = [];
$tw.utils.each(operation.operators,function(operator) {
var operatorFunction = filterOperators[operator.operator] || filterOperators.field || function(source,operator,operations) {
return ["Filter Error: unknown operator '" + operator.operator + "'"];
};
results = operatorFunction(accumulator,operator,{wiki: self, currTiddlerTitle: currTiddlerTitle});
accumulator = results;
});
return results;
};
// Wrap the operator functions in a wrapper function that depends on the prefix
operationFunctions.push((function() {
switch(operation.prefix || "") {
case "": // No prefix means that the operation is unioned into the result
return function(results,source,currTiddlerTitle) {
$tw.utils.pushTop(results,operationSubFunction(source,currTiddlerTitle));
};
case "-": // The results of this operation are removed from the main result
return function(results,source,currTiddlerTitle) {
$tw.utils.removeArrayEntries(results,operationSubFunction(source,currTiddlerTitle));
};
case "+": // This operation is applied to the main results so far
return function(results,source,currTiddlerTitle) {
// This replaces all the elements of the array, but keeps the actual array so that references to it are preserved
source = results.slice(0);
results.splice(0,results.length);
$tw.utils.pushTop(results,operationSubFunction(source,currTiddlerTitle));
};
}
})());
});
// Return a function that applies the operations to a source array/hashmap of tiddler titles
return function(source,currTiddlerTitle) {
var results = [];
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,currTiddlerTitle);
});
return results;
};
};
})();

View File

@ -0,0 +1,37 @@
/*\
title: $:/core/modules/filters/backlinks.js
type: application/javascript
module-type: filteroperator
Filter operator for returning all the backlinks from a tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.backlinks = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
$tw.utils.pushTop(results,options.wiki.getTiddlerBacklinks(title));
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/field.js
type: application/javascript
module-type: filteroperator
Filter operator for comparing fields for equality
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.field = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler) {
var match = tiddler.fields[operator.operator] === operator.operand;
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/has.js
type: application/javascript
module-type: filteroperator
Filter operator for checking if a tiddler has the specified field
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.has = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler) {
var match = $tw.utils.hop(tiddler.fields,operator.operand);
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,39 @@
/*\
title: $:/core/modules/filters/is.js
type: application/javascript
module-type: filteroperator
Filter operator for checking tiddler properties
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var isFilterOperators;
function getIsFilterOperators() {
if(!isFilterOperators) {
isFilterOperators = {};
$tw.modules.applyMethods("isfilteroperator",isFilterOperators);
}
return isFilterOperators;
};
/*
Export our filter function
*/
exports.is = function(source,operator,options) {
// Dispatch to the correct isfilteroperator
var isFilterOperators = getIsFilterOperators();
var isFilterOperator = isFilterOperators[operator.operand];
if(isFilterOperator) {
return isFilterOperator(source,operator.prefix,options);
} else {
return ["Filter Error: Unknown operand for the 'is' filter operator"];
}
};
})();

View File

@ -0,0 +1,49 @@
/*\
title: $:/core/modules/filters/is/current.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[current]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.current = function(source,prefix,options) {
var results = [];
// Function to check a tiddler
function checkTiddler(title) {
if(title !== options.currTiddlerTitle) {
results.push(title);
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
if(prefix === "!") {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
if(source.indexOf(options.currTiddlerTitle) !== -1) {
results.push(options.currTiddlerTitle);
}
}
} else {
if(prefix === "!") {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
} else {
results.push(options.currTiddlerTitle);
}
}
return results;
};
})();

View File

@ -0,0 +1,48 @@
/*\
title: $:/core/modules/filters/is/missing.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[missing]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.missing = function(source,prefix,options) {
var results = [],
missingTitles;
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
missingTitles = options.wiki.getMissingTitles();
$tw.utils.each(source,function(title) {
var match = missingTitles.indexOf(title) !== -1;
if(prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
});
} else {
if(prefix !== "!") {
missingTitles = options.wiki.getMissingTitles();
$tw.utils.each(missingTitles,function(title) {
results.push(title);
});
} else {
$tw.utils.each(source,function(element,title) {
results.push(title);
});
}
}
return results;
};
})();

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/is/orphan.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[orphan]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.orphan = function(source,prefix,options) {
var results = [],
orphanTitles = options.wiki.getOrphanTitles();
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
var match = orphanTitles.indexOf(title) !== -1;
if(prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
});
} else {
$tw.utils.each(source,function(element,title) {
var match = orphanTitles.indexOf(title) !== -1;
if(prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
});
}
return results;
};
})();

View File

@ -0,0 +1,49 @@
/*\
title: $:/core/modules/filters/is/shadow.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[shadow]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.shadow = function(source,prefix,options) {
var results = [];
// Function to check a tiddler
function checkTiddler(title) {
var match = options.wiki.isShadowTiddler(title);
if(prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
if(prefix !== "!") {
$tw.utils.each(options.wiki.shadowTiddlers,function(tiddler,title) {
results.push(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
}
return results;
};
})();

View File

@ -0,0 +1,43 @@
/*\
title: $:/core/modules/filters/is/system.js
type: application/javascript
module-type: isfilteroperator
Filter function for [is[system]]
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.system = function(source,prefix,options) {
var results = [];
// Function to check a tiddler
function checkTiddler(title) {
var match = options.wiki.isSystemTiddler(title);
if(prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,38 @@
/*\
title: $:/core/modules/filters/limit.js
type: application/javascript
module-type: filteroperator
Filter operator for chopping the results to a specified maximum number of entries
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.limit = function(source,operator,options) {
var results = [];
// Convert to an array if necessary
if(!$tw.utils.isArray(source)) {
var copy = [];
$tw.utils.each(source,function(element,title) {
copy.push(title);
});
source = copy;
}
// Slice the array if necessary
var limit = Math.min(source.length,parseInt(operator.operand,10));
if(operator.prefix === "!") {
results = source.slice(source.length - limit,limit);
} else {
results = source.slice(0,limit);
}
return results;
};
})();

View File

@ -0,0 +1,37 @@
/*\
title: $:/core/modules/filters/links.js
type: application/javascript
module-type: filteroperator
Filter operator for returning all the links from a tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.links = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
$tw.utils.pushTop(results,options.wiki.getTiddlerLinks(title));
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,47 @@
/*\
title: $:/core/modules/filters/list.js
type: application/javascript
module-type: filteroperator
Filter operator returning the tiddlers whose title is listed in the operand tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.list = function(source,operator,options) {
var results = [],
list = options.wiki.getTiddlerList(operator.operand);
function checkTiddler(title) {
var match = list.indexOf(title) !== -1;
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
if(operator.prefix !== "!") {
results = list;
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
}
return results;
};
})();

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/prefix.js
type: application/javascript
module-type: filteroperator
Filter operator for checking if a title starts with a prefix
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.prefix = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler) {
var match = tiddler.fields.title.substr(0,operator.operand.length) === operator.operand;
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,28 @@
/*\
title: $:/core/modules/filters/searchVia.js
type: application/javascript
module-type: filteroperator
Filter operator for searching for the text in the operand tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.searchVia = function(source,operator,options) {
var term = options.wiki.getTiddlerText(operator.operand,""),
invert = operator.prefix === "!";
return options.wiki.search(term,{
titles: source,
invert: invert,
exclude: [operator.operand]
});
};
})();

View File

@ -0,0 +1,32 @@
/*\
title: $:/core/modules/filters/sort.js
type: application/javascript
module-type: filteroperator
Filter operator for sorting
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.sort = function(source,operator,options) {
var results;
if($tw.utils.isArray(source)) {
results = source;
} else {
results = [];
$tw.utils.each(source,function(element,title) {
results.push(title);
});
}
options.wiki.sortTiddlers(results,operator.operand,operator.prefix === "!");
return results;
};
})();

View File

@ -0,0 +1,32 @@
/*\
title: $:/core/modules/filters/sortcs.js
type: application/javascript
module-type: filteroperator
Filter operator for case-sensitive sorting
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.sortcs = function(source,operator,options) {
var results;
if($tw.utils.isArray(source)) {
results = source;
} else {
results = [];
$tw.utils.each(source,function(element,title) {
results.push(title);
});
}
options.wiki.sortTiddlers(results,operator.operand,operator.prefix === "!",true);
return results;
};
})();

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/tag.js
type: application/javascript
module-type: filteroperator
Filter operator for checking for the presence of a tag
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.tag = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler) {
var match = tiddler.hasTag(operator.operand);
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,37 @@
/*\
title: $:/core/modules/filters/tagging.js
type: application/javascript
module-type: filteroperator
Filter operator returning all tiddlers that are tagged with the selected tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.tagging = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
$tw.utils.pushTop(results,options.wiki.getTiddlersWithTag(title));
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,40 @@
/*\
title: $:/core/modules/filters/tags.js
type: application/javascript
module-type: filteroperator
Filter operator returning all tiddlers tagging the selected tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.tags = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler && tiddler.fields.tags) {
$tw.utils.pushTop(results,tiddler.fields.tags);
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
$tw.utils.each(source,function(element,title) {
checkTiddler(title);
});
}
return results;
};
})();

View File

@ -0,0 +1,53 @@
/*\
title: $:/core/modules/filters/title.js
type: application/javascript
module-type: filteroperator
Filter operator for comparing title fields for equality
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.title = function(source,operator,options) {
var results = [];
// Function to check an individual title
function checkTiddler(title) {
var tiddler = options.wiki.getTiddler(title);
if(tiddler) {
var match = tiddler.fields[operator.operator] === operator.operand;
if(operator.prefix === "!") {
match = !match;
}
if(match) {
results.push(title);
}
}
};
// Iterate through the source tiddlers
if($tw.utils.isArray(source)) {
$tw.utils.each(source,function(title) {
checkTiddler(title);
});
} else {
// If we're filtering a hashmap we change the behaviour to pass through missing tiddlers
if(operator.prefix !== "!") {
results.push(operator.operand);
} else {
$tw.utils.each(source,function(element,title) {
if(title !== operator.operand) {
checkTiddler(title);
}
});
}
}
return results;
};
})();

View File

@ -0,0 +1,181 @@
/*\
title: test-filters.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests the filtering mechanism.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
describe("Filter tests", function() {
// Create a wiki
var wiki = new $tw.Wiki();
// Some helpers
var addShadowTiddler = function(fields) {
var tiddler = new $tw.Tiddler(fields);
wiki.shadowTiddlers[tiddler.fields.title] = {tiddler: tiddler};
};
// Add a few tiddlers
wiki.addTiddler({
title: "TiddlerOne",
text: "The quick brown fox in $:/TiddlerTwo",
tags: ["one"],
modifier: "JoeBloggs"});
wiki.addTiddler({
title: "$:/TiddlerTwo",
text: "The rain in Spain\nfalls mainly on the plain and [[a fourth tiddler]]",
tags: ["two"]});
wiki.addTiddler({
title: "Tiddler Three",
text: "The speed of sound in light\n\nThere is no TiddlerZero but TiddlerSix",
tags: ["one","two"]});
wiki.addTiddler({
title: "a fourth tiddler",
text: "The quality of mercy is not drained by [[Tiddler Three]]",
tags: []});
// And some shadows
addShadowTiddler({
title: "$:/TiddlerFive",
text: "Everything in federation",
tags: ["two"]});
addShadowTiddler({
title: "TiddlerSix",
text: "Missing inaction from TiddlerOne",
tags: []});
addShadowTiddler({
title: "TiddlerSeventh",
text: "TiddlerOne\nTiddler Three\na fourth tiddler\nMissingTiddler",
tags: []});
addShadowTiddler({
title: "Tiddler8",
text: "Tidd",
tags: []});
// Our tests
it("should handle the title operator", function() {
expect(wiki.filterTiddlers("TiddlerOne [title[$:/TiddlerTwo]] [[Tiddler Three]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three");
expect(wiki.filterTiddlers("[!title[Tiddler Three]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,a fourth tiddler");
expect(wiki.filterTiddlers("TiddlerOne [title[$:/TiddlerTwo]] [[Tiddler Three]] [[A Missing Tiddler]]").join(",")).toBe("TiddlerOne,$:/TiddlerTwo,Tiddler Three,A Missing Tiddler");
});
it("should handle the field operator", function() {
expect(wiki.filterTiddlers("[modifier[JoeBloggs]]").join(",")).toBe("TiddlerOne");
expect(wiki.filterTiddlers("[!modifier[JoeBloggs]]").join(",")).toBe("$:/TiddlerTwo,Tiddler Three,a fourth tiddler");
expect(wiki.filterTiddlers("[!is[system]!modifier[JoeBloggs]]").join(",")).toBe("Tiddler Three,a fourth tiddler");
});
it("should handle the prefix operator", function() {
expect(wiki.filterTiddlers("[prefix[Tiddler]]").join(",")).toBe("TiddlerOne,Tiddler Three");
expect(wiki.filterTiddlers("[prefix[nothing]]").join(",")).toBe("");
});
it("should handle the sort and sortcs operators", function() {
expect(wiki.filterTiddlers("[sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[!sort[title]]").join(",")).toBe("TiddlerOne,Tiddler Three,a fourth tiddler,$:/TiddlerTwo");
expect(wiki.filterTiddlers("[sortcs[title]]").join(",")).toBe("$:/TiddlerTwo,Tiddler Three,TiddlerOne,a fourth tiddler");
expect(wiki.filterTiddlers("[!sortcs[title]]").join(",")).toBe("a fourth tiddler,TiddlerOne,Tiddler Three,$:/TiddlerTwo");
});
it("should handle the tag operator", function() {
expect(wiki.filterTiddlers("[tag[one]sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[!tag[one]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler");
expect(wiki.filterTiddlers("[prefix[Tidd]tag[one]sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[!is[shadow]tag[two]sort[title]]").join(",")).toBe("$:/TiddlerTwo,Tiddler Three");
expect(wiki.filterTiddlers("[is[shadow]tag[two]sort[title]]").join(",")).toBe("$:/TiddlerFive");
});
it("should handle the tags operator", function() {
expect(wiki.filterTiddlers("[tags[]sort[title]]").join(",")).toBe("one,two");
expect(wiki.filterTiddlers("[[TiddlerOne]tags[]sort[title]]").join(",")).toBe("one");
});
it("should handle the tagging operator", function() {
expect(wiki.filterTiddlers("[[one]tagging[]sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[[two]tagging[]sort[title]]").join(",")).toBe("$:/TiddlerTwo,Tiddler Three");
expect(wiki.filterTiddlers("[is[current]tagging[]sort[title]]","one").join(",")).toBe("Tiddler Three,TiddlerOne");
});
it("should handle the links operator", function() {
expect(wiki.filterTiddlers("[!is[shadow]links[]sort[title]]").join(",")).toBe("a fourth tiddler,Tiddler Three,TiddlerSix,TiddlerTwo,TiddlerZero");
expect(wiki.filterTiddlers("[is[shadow]links[]sort[title]]").join(",")).toBe("MissingTiddler,TiddlerOne");
});
it("should handle the backlinks operator", function() {
expect(wiki.filterTiddlers("[!is[shadow]backlinks[]sort[title]]").join(",")).toBe("a fourth tiddler");
expect(wiki.filterTiddlers("[is[shadow]backlinks[]sort[title]]").join(",")).toBe("Tiddler Three");
});
it("should handle the has operator", function() {
expect(wiki.filterTiddlers("[has[modifier]sort[title]]").join(",")).toBe("TiddlerOne");
expect(wiki.filterTiddlers("[!has[modifier]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,Tiddler Three");
});
it("should handle the limit operator", function() {
expect(wiki.filterTiddlers("[!is[system]sort[title]limit[2]]").join(",")).toBe("a fourth tiddler,Tiddler Three");
expect(wiki.filterTiddlers("[prefix[Tid]sort[title]limit[1]]").join(",")).toBe("Tiddler Three");
});
it("should handle the list operator", function() {
expect(wiki.filterTiddlers("[list[TiddlerSeventh]sort[title]]").join(",")).toBe("a fourth tiddler,MissingTiddler,Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[tag[one]list[TiddlerSeventh]sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
});
it("should handle the searchVia operator", function() {
expect(wiki.filterTiddlers("[searchVia[Tiddler8]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,Tiddler Three,TiddlerOne");
});
describe("testing the is operator",function() {
it("should handle the '[is[current]]' operator", function() {
expect(wiki.filterTiddlers("[is[current]]","Tiddler Three").join(",")).toBe("Tiddler Three");
expect(wiki.filterTiddlers("[[Tiddler Three]is[current]]","Tiddler Three").join(",")).toBe("Tiddler Three");
expect(wiki.filterTiddlers("[[$:/TiddlerTwo]is[current]]","Tiddler Three").join(",")).toBe("");
expect(wiki.filterTiddlers("[!is[current]sort[title]]","Tiddler Three").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,TiddlerOne");
});
it("should handle the '[is[system]]' operator", function() {
expect(wiki.filterTiddlers("[is[system]]").join(",")).toBe("$:/TiddlerTwo");
expect(wiki.filterTiddlers("[!is[system]sort[title]]").join(",")).toBe("a fourth tiddler,Tiddler Three,TiddlerOne");
});
it("should handle the '[is[shadow]]' operator", function() {
expect(wiki.filterTiddlers("[is[shadow]sort[title]]").join(",")).toBe("$:/TiddlerFive,Tiddler8,TiddlerSeventh,TiddlerSix");
expect(wiki.filterTiddlers("[!is[shadow]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,Tiddler Three,TiddlerOne");
});
it("should handle the '[is[missing]]' operator", function() {
expect(wiki.filterTiddlers("[is[missing]]").join(",")).toBe("TiddlerZero,TiddlerSix,TiddlerTwo");
expect(wiki.filterTiddlers("[!is[missing]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[[TiddlerOne]is[missing]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[TiddlerZero]is[missing]]").join(",")).toBe("TiddlerZero");
expect(wiki.filterTiddlers("[!title[Tiddler Three]is[missing]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[!title[Tiddler Three]!is[missing]sort[title]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,TiddlerOne");
});
it("should handle the '[is[orphan]]' operator", function() {
expect(wiki.filterTiddlers("[is[orphan]sort[title]]").join(",")).toBe("a fourth tiddler,TiddlerOne");
expect(wiki.filterTiddlers("[!is[orphan]]").join(",")).toBe("$:/TiddlerTwo,Tiddler Three");
expect(wiki.filterTiddlers("[[TiddlerOne]is[orphan]]").join(",")).toBe("TiddlerOne");
expect(wiki.filterTiddlers("[[TiddlerOne]!is[orphan]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[!title[Tiddler Three]is[orphan]sort[title]]").join(",")).toBe("a fourth tiddler,TiddlerOne");
expect(wiki.filterTiddlers("[!title[Tiddler Three]!is[orphan]]").join(",")).toBe("$:/TiddlerTwo");
});
});
it("should handle the operand prefixes", function() {
expect(wiki.filterTiddlers("[prefix[Tiddler]] +[sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
});
});
})();

View File

@ -46,11 +46,11 @@ Welcome to TiddlyWiki created by Jeremy Ruston; Copyright &copy; 2004-2007 Jerem
<div id="contentWrapper"></div>
<div id="contentStash"></div>
<div id="shadowArea">
{{{ [prefix[{shadow}]] +[sort-case-sensitive[title]] ||$:/core/templates/html-div-tiddler-remove-prefix}}}
{{{ [prefix[{shadow}]] +[sortcs[title]] ||$:/core/templates/html-div-tiddler-remove-prefix}}}
</div>
<!--POST-SHADOWAREA-->
<div id="storeArea">
{{{ [prefix[{tiddler}]] +[sort-case-sensitive[title]] ||$:/core/templates/html-div-tiddler-remove-prefix}}}
{{{ [prefix[{tiddler}]] +[sortcs[title]] ||$:/core/templates/html-div-tiddler-remove-prefix}}}
{{{ [prefix[{plugin}]] ||$:/core/templates/plain-text-tiddler}}}
{{{ [prefix[{posttiddlers}]] ||$:/core/templates/plain-text-tiddler}}}
</div>

View File

@ -33,7 +33,7 @@ A filter string consists of one or more runs of filter operators that each look
* ''is'': tests whether a tiddler is a member of the system defined set named in the operand (see below)
* ''has'': tests whether a tiddler has the field specified in the operand
* ''sort'': sorts the tiddlers by the field specified in the operand
* ''sort-case-sensitive'': a case sensitive sort of the current tiddlers by the field specified in the operand
* ''sortcs'': a case sensitive sort of the current tiddlers by the field specified in the operand
* ''prefix'': tests whether a tiddlers title starts with the prefix specified in the operand
* ''limit'': limits the number of subresults to the integer specified in the operand
* ''tag'': tests whether a given tag is (`[tag[mytag]]`) or is not (`[!tag[mytag]]`) present on the tiddler