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

Refactor the Tiddler object to enforce immutability

And in the process move the fields out of the fields member
This commit is contained in:
Jeremy Ruston 2012-01-17 13:01:55 +00:00
parent 2ff603da0e
commit b898afe3e5
12 changed files with 120 additions and 88 deletions

View File

@ -16,7 +16,7 @@ var BitmapParseTree = function() {
BitmapParseTree.prototype.compile = function(type) { BitmapParseTree.prototype.compile = function(type) {
if(type === "text/html") { if(type === "text/html") {
return "(function (tiddler,store,utils) {return '<img src=\"data:' + tiddler.fields.type + ';base64,' + tiddler.fields.text + '\">';})"; return "(function (tiddler,store,utils) {return '<img src=\"data:' + tiddler.type + ';base64,' + tiddler.text + '\">';})";
} else { } else {
return null; return null;
} }

View File

@ -132,7 +132,7 @@ Recipe.prototype.loadTiddlerFiles = function(recipeLine) {
filename = path.basename(filepath), // eg *.js filename = path.basename(filepath), // eg *.js
posStar = filename.indexOf("*"); posStar = filename.indexOf("*");
if(posStar !== -1) { if(posStar !== -1) {
var fileRegExp = new RegExp("^" + filename.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace("*",".*") + "$"); var fileRegExp = new RegExp("^" + filename.replace(/[\-\[\]{}()+?.,\\\^$|#\s]/g, "\\$&").replace("*",".*") + "$");
var files = fs.readdirSync(path.resolve(path.dirname(recipeLine.contextPath),filedir)); var files = fs.readdirSync(path.resolve(path.dirname(recipeLine.contextPath),filedir));
for(var f=0; f<files.length; f++) { for(var f=0; f<files.length; f++) {
if(fileRegExp.test(files[f])) { if(fileRegExp.test(files[f])) {
@ -341,7 +341,7 @@ Recipe.tiddlerOutputter = {
// Lines starting with //# are removed from javascript tiddlers // Lines starting with //# are removed from javascript tiddlers
for(var t=0; t<tiddlers.length; t++) { for(var t=0; t<tiddlers.length; t++) {
var tid = this.store.getTiddler(tiddlers[t]), var tid = this.store.getTiddler(tiddlers[t]),
text = tid.fields.text; text = tid.text;
// For compatibility with cook.rb, remove one trailing \n from tiddler // For compatibility with cook.rb, remove one trailing \n from tiddler
text = text.charAt(text.length-1) === "\n" ? text.substr(0,text.length-1) : text; text = text.charAt(text.length-1) === "\n" ? text.substr(0,text.length-1) : text;
var lines = text.split("\n"); var lines = text.split("\n");
@ -370,7 +370,7 @@ Recipe.tiddlerOutputter = {
tid = this.store.getTiddler(title); tid = this.store.getTiddler(title);
out.push("<" + "script type=\"application/javascript\">"); out.push("<" + "script type=\"application/javascript\">");
out.push("define(\"" + title + "\",function(require,exports,module) {"); out.push("define(\"" + title + "\",function(require,exports,module) {");
out.push(tid.fields.text); out.push(tid.text);
out.push("});"); out.push("});");
out.push("</" + "script>"); out.push("</" + "script>");
} }
@ -388,24 +388,24 @@ Recipe.prototype.cookRss = function() {
return title.indexOf(" ") == -1 ? title : "[[" + title + "]]"; return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
}, },
tiddlerToRssItem = function(tiddler,uri) { tiddlerToRssItem = function(tiddler,uri) {
var s = "<title" + ">" + utils.htmlEncode(tiddler.fields.title) + "</title" + ">\n"; var s = "<title" + ">" + utils.htmlEncode(tiddler.title) + "</title" + ">\n";
s += "<description>" + utils.htmlEncode(me.store.renderTiddler("text/html",tiddler.fields.title)) + "</description>\n"; s += "<description>" + utils.htmlEncode(me.store.renderTiddler("text/html",tiddler.title)) + "</description>\n";
var i; var i;
if(tiddler.fields.tags) { if(tiddler.tags) {
for(i=0; i<tiddler.fields.tags.length; i++) { for(i=0; i<tiddler.tags.length; i++) {
s += "<category>" + tiddler.fields.tags[i] + "</category>\n"; s += "<category>" + tiddler.tags[i] + "</category>\n";
} }
} }
s += "<link>" + uri + "#" + encodeURIComponent(encodeTiddlyLink(tiddler.fields.title)) + "</link>\n"; s += "<link>" + uri + "#" + encodeURIComponent(encodeTiddlyLink(tiddler.title)) + "</link>\n";
if(tiddler.fields.modified) { if(tiddler.modified) {
s +="<pubDate>" + tiddler.fields.modified.toUTCString() + "</pubDate>\n"; s +="<pubDate>" + tiddler.modified.toUTCString() + "</pubDate>\n";
} }
return s; return s;
}, },
getRssTiddlers = function(sortField,excludeTag) { getRssTiddlers = function(sortField,excludeTag) {
var r = []; var r = [];
me.store.forEachTiddler(sortField,excludeTag,function(title,tiddler) { me.store.forEachTiddler(sortField,excludeTag,function(title,tiddler) {
if(!tiddler.hasTag(excludeTag) && tiddler.fields.modified !== undefined) { if(!tiddler.hasTag(excludeTag) && tiddler.modified !== undefined) {
r.push(tiddler); r.push(tiddler);
} }
}); });

View File

@ -16,7 +16,7 @@ var SVGParseTree = function() {
SVGParseTree.prototype.compile = function(type) { SVGParseTree.prototype.compile = function(type) {
if(type === "text/html") { if(type === "text/html") {
return "(function (tiddler,store,utils) {return tiddler.fields.text;})"; return "(function (tiddler,store,utils) {return tiddler.text;})";
} else { } else {
return null; return null;
} }

View File

@ -21,25 +21,57 @@ var utils = require("./Utils.js"),
ArgParser = require("./ArgParser.js").ArgParser; ArgParser = require("./ArgParser.js").ArgParser;
var Tiddler = function(/* tiddler,fields */) { var Tiddler = function(/* tiddler,fields */) {
this.parseTree = null; // Caches the parse tree for the tiddler this.cache = {}; // Expose the cache object
this.renderers = {}; // Caches rendering functions for this tiddler (indexed by MIME type) var fields = {}, // Keep the fields private, later we'll expose getters for them
this.renditions = {}; // Caches the renditions produced by those functions (indexed by MIME type) tags, // Keep the tags separately because they're the only Array field
this.fields = {}; f,t,c,arg,src;
for(var c=0; c<arguments.length; c++) { // Accumulate the supplied fields
var arg = arguments[c], for(c=0; c<arguments.length; c++) {
src = null; arg = arguments[c];
src = null;
if(arg instanceof Tiddler) { if(arg instanceof Tiddler) {
src = arg.fields; src = arg.fields;
} else { } else {
src = arg; src = arg;
} }
for(var t in src) { for(t in src) {
var f = this.parseTiddlerField(t,src[t]); f = Tiddler.parseTiddlerField(t,src[t]);
if(f !== null) { if(f !== null) {
this.fields[t] = f; fields[t] = f;
} }
} }
} }
// Pull out the tags
if(fields.tags) {
tags = fields.tags;
delete fields.tags;
}
// Expose the fields as read only properties
for(f in fields) {
Object.defineProperty(this,f,{value: fields[f], writeable: false});
}
// Expose the tags as a getter
Object.defineProperty(this,"tags",{get: function() {return tags ? tags.slice(0) : [];}});
// Other methods that need access to the fields
this.getFields = function() {
var r = {};
for(var f in fields) {
var v = fields[f];
if(v instanceof Array) {
r[f] = v.slice(0);
} else {
r[f] = v;
}
}
if(tags) {
r.tags = tags;
}
return r;
};
};
Tiddler.prototype.hasTag = function(tag) {
return this.tags.indexOf(tag) !== -1;
}; };
Tiddler.standardFields = { Tiddler.standardFields = {
@ -57,18 +89,21 @@ Tiddler.isStandardField = function(name) {
return name in Tiddler.standardFields; return name in Tiddler.standardFields;
}; };
Tiddler.prototype.hasTag = function(tag) { Tiddler.compareTiddlerFields = function(a,b,sortField) {
if(this.tags) { var aa = a[sortField] || 0,
for(var t=0; t<this.tags.length; t++) { bb = b[sortField] || 0;
if(this.tags[t] === tag) { if(aa < bb) {
return true; return -1;
} } else {
if(aa > bb) {
return 1;
} else {
return 0;
} }
} }
return false;
}; };
Tiddler.prototype.parseTiddlerField = function(name,value) { Tiddler.parseTiddlerField = function(name,value) {
var type = Tiddler.specialTiddlerFields[name]; var type = Tiddler.specialTiddlerFields[name];
if(type) { if(type) {
return Tiddler.specialTiddlerFieldParsers[type](value); return Tiddler.specialTiddlerFieldParsers[type](value);

View File

@ -34,29 +34,30 @@ var outputTiddler = function(tid) {
var result = [], var result = [],
outputAttribute = function(name,value) { outputAttribute = function(name,value) {
result.push(name + ": " + value + "\n"); result.push(name + ": " + value + "\n");
}; },
for(var t in tid.fields) { fields = tid.getFields();
for(var t in fields) {
switch(t) { switch(t) {
case "text": case "text":
// Ignore the text field // Ignore the text field
break; break;
case "tags": case "tags":
// Output tags as a list // Output tags as a list
outputAttribute(t,tiddlerOutput.stringifyTags(tid.fields.tags)); outputAttribute(t,tiddlerOutput.stringifyTags(fields.tags));
break; break;
case "modified": case "modified":
case "created": case "created":
// Output dates in YYYYMMDDHHMM // Output dates in YYYYMMDDHHMM
outputAttribute(t,utils.convertToYYYYMMDDHHMM(tid.fields[t])); outputAttribute(t,utils.convertToYYYYMMDDHHMM(fields[t]));
break; break;
default: default:
// Output other attributes raw // Output other attributes raw
outputAttribute(t,tid.fields[t]); outputAttribute(t,fields[t]);
break; break;
} }
} }
result.push("\n"); result.push("\n");
result.push(tid.fields.text); result.push(fields.text);
return result.join(""); return result.join("");
}; };
@ -68,23 +69,19 @@ The fields are in the order title, creator, modifier, created, modified, tags, f
*/ */
var outputTiddlerDiv = function(tid) { var outputTiddlerDiv = function(tid) {
var result = [], var result = [],
attributes = {}, fields = tid.getFields(),
outputAttribute = function(name,transform,dontDelete) { text = fields.text,
if(name in attributes) { outputAttribute = function(name,transform) {
var value = attributes[name]; if(name in fields) {
var value = fields[name];
if(transform) if(transform)
value = transform(value); value = transform(value);
result.push(" " + name + "=\"" + value + "\""); result.push(" " + name + "=\"" + value + "\"");
if(!dontDelete) { delete fields[name];
delete attributes[name];
}
} }
}; };
for(var t in tid.fields) { if(fields.text) {
attributes[t] = tid.fields[t]; delete fields.text;
}
if(attributes.text) {
delete attributes.text;
} }
result.push("<div"); result.push("<div");
// Output the standard attributes in the correct order // Output the standard attributes in the correct order
@ -95,11 +92,11 @@ var outputTiddlerDiv = function(tid) {
outputAttribute("modified", function(v) {return utils.convertToYYYYMMDDHHMM(v);}); outputAttribute("modified", function(v) {return utils.convertToYYYYMMDDHHMM(v);});
outputAttribute("tags", function(v) {return stringifyTags(v);}); outputAttribute("tags", function(v) {return stringifyTags(v);});
// Output any other attributes // Output any other attributes
for(t in attributes) { for(var t in fields) {
outputAttribute(t,null,true); outputAttribute(t,null,true);
} }
result.push(">\n<pre>"); result.push(">\n<pre>");
result.push(utils.htmlEncode(tid.fields.text)); result.push(utils.htmlEncode(text));
result.push("</pre>\n</div>"); result.push("</pre>\n</div>");
return result.join(""); return result.join("");
}; };

View File

@ -1,6 +1,12 @@
/*\ /*\
title: js/WikiStore.js title: js/WikiStore.js
WikiStore uses the .cache member of tiddlers to store the following information:
parseTree: Caches the parse tree for the tiddler
renderers: Caches rendering functions for this tiddler (indexed by MIME type)
renditions: Caches the renditions produced by those functions (indexed by MIME type)
\*/ \*/
(function(){ (function(){
@ -125,7 +131,7 @@ WikiStore.prototype.getTiddler = function(title) {
WikiStore.prototype.getTiddlerText = function(title) { WikiStore.prototype.getTiddlerText = function(title) {
var t = this.getTiddler(title); var t = this.getTiddler(title);
return t instanceof Tiddler ? t.fields.text : null; return t instanceof Tiddler ? t.text : null;
}; };
WikiStore.prototype.deleteTiddler = function(title) { WikiStore.prototype.deleteTiddler = function(title) {
@ -143,8 +149,8 @@ WikiStore.prototype.tiddlerExists = function(title) {
}; };
WikiStore.prototype.addTiddler = function(tiddler) { WikiStore.prototype.addTiddler = function(tiddler) {
this.tiddlers[tiddler.fields.title] = tiddler; this.tiddlers[tiddler.title] = tiddler;
this.touchTiddler("modified",tiddler.fields.title); this.touchTiddler("modified",tiddler.title);
}; };
WikiStore.prototype.forEachTiddler = function(/* [sortField,[excludeTag,]]callback */) { WikiStore.prototype.forEachTiddler = function(/* [sortField,[excludeTag,]]callback */) {
@ -159,22 +165,10 @@ WikiStore.prototype.forEachTiddler = function(/* [sortField,[excludeTag,]]callba
for(t in this.tiddlers) { for(t in this.tiddlers) {
tiddlers.push(this.tiddlers[t]); tiddlers.push(this.tiddlers[t]);
} }
tiddlers.sort(function (a,b) { tiddlers.sort(function (a,b) {return Tiddler.compareTiddlerFields(a,b,sortField);});
var aa = a.fields[sortField] || 0,
bb = b.fields[sortField] || 0;
if(aa < bb) {
return -1;
} else {
if(aa > bb) {
return 1;
} else {
return 0;
}
}
});
for(t=0; t<tiddlers.length; t++) { for(t=0; t<tiddlers.length; t++) {
if(!tiddlers[t].hasTag(excludeTag)) { if(!tiddlers[t].hasTag(excludeTag)) {
callback.call(this,tiddlers[t].fields.title,tiddlers[t]); callback.call(this,tiddlers[t].title,tiddlers[t]);
} }
} }
} else { } else {
@ -286,10 +280,10 @@ WikiStore.prototype.parseTiddler = function(title) {
var tiddler = this.getTiddler(title); var tiddler = this.getTiddler(title);
if(tiddler) { if(tiddler) {
// Check the cache // Check the cache
if(!tiddler.parseTree) { if(!tiddler.cache.parseTree) {
tiddler.parseTree = this.parseText(tiddler.fields.type,tiddler.fields.text); tiddler.cache.parseTree = this.parseText(tiddler.type,tiddler.text);
} }
return tiddler.parseTree; return tiddler.cache.parseTree;
} else { } else {
return null; return null;
} }
@ -311,11 +305,14 @@ WikiStore.prototype.compileTiddler = function(title,type) {
/*jslint evil: true */ /*jslint evil: true */
var tiddler = this.getTiddler(title); var tiddler = this.getTiddler(title);
if(tiddler) { if(tiddler) {
if(!tiddler.renderers[type]) { if(!tiddler.cache.renderers) {
var tree = this.parseTiddler(title); tiddler.cache.renderers = {};
tiddler.renderers[type] = eval(tree.compile(type));
} }
return tiddler.renderers[type]; if(!tiddler.cache.renderers[type]) {
var tree = this.parseTiddler(title);
tiddler.cache.renderers[type] = eval(tree.compile(type));
}
return tiddler.cache.renderers[type];
} else { } else {
return null; return null;
} }
@ -343,10 +340,13 @@ WikiStore.prototype.renderTiddler = function(targetType,title,asTitle) {
var asTiddler = this.getTiddler(asTitle); var asTiddler = this.getTiddler(asTitle);
return fn(asTiddler,this,utils); return fn(asTiddler,this,utils);
} else { } else {
if(!tiddler.renditions[targetType]) { if(!tiddler.cache.renditions) {
tiddler.renditions[targetType] = fn(tiddler,this,utils); tiddler.cache.renditions = {};
} }
return tiddler.renditions[targetType]; if(!tiddler.cache.renditions[targetType]) {
tiddler.cache.renditions[targetType] = fn(tiddler,this,utils);
}
return tiddler.cache.renditions[targetType];
} }
} }
return null; return null;

View File

@ -16,7 +16,7 @@ exports.macro = {
}, },
handler: function(type,tiddler,store,params) { handler: function(type,tiddler,store,params) {
var encoder = type === "text/html" ? utils.htmlEncode : function(x) {return x;}, var encoder = type === "text/html" ? utils.htmlEncode : function(x) {return x;},
parseTree = store.parseTiddler(tiddler.fields.title); parseTree = store.parseTiddler(tiddler.title);
if(parseTree) { if(parseTree) {
var r = []; var r = [];
var d = parseTree.dependencies; var d = parseTree.dependencies;

View File

@ -52,8 +52,8 @@ exports.macro = {
handler, handler,
t; t;
if(template) { if(template) {
templateType = template.fields.type; templateType = template.type;
templateText = template.fields.text; templateText = template.text;
} }
handler = handlers[params.type]; handler = handlers[params.type];
handler = handler || handlers.all; handler = handler || handlers.all;

View File

@ -21,13 +21,13 @@ exports.macro = {
if(params["with"]) { if(params["with"]) {
// Parameterised transclusion // Parameterised transclusion
var targetTiddler = store.getTiddler(params.target), var targetTiddler = store.getTiddler(params.target),
text = targetTiddler.fields.text; text = targetTiddler.text;
var withTokens = [params["with"]]; var withTokens = [params["with"]];
for(var t=0; t<withTokens.length; t++) { for(var t=0; t<withTokens.length; t++) {
var placeholderRegExp = new RegExp("\\$"+(t+1),"mg"); var placeholderRegExp = new RegExp("\\$"+(t+1),"mg");
text = text.replace(placeholderRegExp,withTokens[t]); text = text.replace(placeholderRegExp,withTokens[t]);
} }
return store.renderText(targetTiddler.fields.type,text,type,tiddler.fields.title); return store.renderText(targetTiddler.type,text,type,tiddler.title);
} else { } else {
// There's no parameterisation, so we can just render the target tiddler directly // There's no parameterisation, so we can just render the target tiddler directly
return store.renderTiddler(type,params.target); return store.renderTiddler(type,params.target);

View File

@ -18,14 +18,14 @@ exports.macro = {
template: {byPos: 2, type: "text", optional: true} template: {byPos: 2, type: "text", optional: true}
}, },
handler: function(type,tiddler,store,params) { handler: function(type,tiddler,store,params) {
var v = tiddler.fields[params.field], var v = tiddler[params.field],
encoder = type === "text/html" ? utils.htmlEncode : function(x) {return x;}; encoder = type === "text/html" ? utils.htmlEncode : function(x) {return x;};
if(v) { if(v) {
switch(params.format) { switch(params.format) {
case "link": case "link":
return store.renderMacro("link",type,tiddler,{target: v},encoder(v)); return store.renderMacro("link",type,tiddler,{target: v},encoder(v));
case "wikified": case "wikified":
return store.renderTiddler(type,tiddler.fields.title); return store.renderTiddler(type,tiddler.title);
case "date": case "date":
var template = params.template || "DD MMM YYYY"; var template = params.template || "DD MMM YYYY";
return encoder(utils.formatDateString(v,template)); return encoder(utils.formatDateString(v,template));

View File

@ -2,6 +2,6 @@ title: SeventhTiddler
<<echo {{2+2}}>> <<echo {{2+2}}>>
<<echo {{tiddler.fields.title}}>> <<echo {{tiddler.title}}>>
<<echo {{"window"}}>> <<echo {{"window"}}>>

View File

@ -135,7 +135,7 @@ var commandLineSwitches = {
handler: function(args,callback) { handler: function(args,callback) {
var recipe = []; var recipe = [];
app.store.forEachTiddler(function(title,tiddler) { app.store.forEachTiddler(function(title,tiddler) {
var filename = encodeURIComponent(tiddler.fields.title.replace(/ /g,"_")) + ".tid"; var filename = encodeURIComponent(tiddler.title.replace(/ /g,"_")) + ".tid";
fs.writeFileSync(path.resolve(args[0],filename),tiddlerOutput.outputTiddler(tiddler),"utf8"); fs.writeFileSync(path.resolve(args[0],filename),tiddlerOutput.outputTiddler(tiddler),"utf8");
recipe.push("tiddler: " + filename + "\n"); recipe.push("tiddler: " + filename + "\n");
}); });