mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-08-25 11:06:45 +00:00
243 lines
6.8 KiB
JavaScript
243 lines
6.8 KiB
JavaScript
/*\
|
|
title: $:/core/modules/treenodes/macro.js
|
|
type: application/javascript
|
|
module-type: treenode
|
|
|
|
Macro node, used as the base class for macros
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jshint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
var Node = require("./node.js").Node;
|
|
|
|
/*
|
|
Construct a renderer node representing a macro invocation
|
|
macroName: name of the macro
|
|
options: see below
|
|
|
|
The options available are:
|
|
|
|
srcParams: a string or a hashmap of parameters (each can be a string, or a fn(tiddler,wiki) for evaluated parameters)
|
|
content: optional array of child nodes
|
|
wiki: reference to the WikiStore associated with this macro
|
|
dependencies: optional Dependencies object representing the dependencies of this macro
|
|
isBlock: true if this macro is being used as an HTML block
|
|
classes: array of classes to be assigned to the macro
|
|
|
|
Note that the dependencies will be evaluated if not provided.
|
|
*/
|
|
var Macro = function(macroName,options) {
|
|
options = options || {};
|
|
var MacroClass = options.wiki ? options.wiki.macros[macroName] : null; // Get the macro class
|
|
if(this instanceof Macro) {
|
|
// Save the details
|
|
this.macroName = macroName;
|
|
this.srcParams = options.srcParams || {};
|
|
this.content = options.content || [];
|
|
this.wiki = options.wiki;
|
|
this.dependencies = options.dependencies;
|
|
this.isBlock = options.isBlock;
|
|
this.classes = options.classes;
|
|
// Parse the macro parameters if required
|
|
if(typeof this.srcParams === "string") {
|
|
this.srcParams = this.parseMacroParamString(this.srcParams);
|
|
}
|
|
// Evaluate the dependencies if required
|
|
if(macroName && !this.dependencies) {
|
|
this.dependencies = this.evaluateDependencies();
|
|
}
|
|
// Get a reference to the static information about this macro
|
|
if(MacroClass) {
|
|
this.MacroClass = MacroClass;
|
|
this.info = MacroClass.prototype.info;
|
|
}
|
|
} else {
|
|
// If Macro() has been called without 'new' then instantiate the right macro class
|
|
if(!MacroClass) {
|
|
throw "Unknown macro '" + macroName + "'";
|
|
}
|
|
return new MacroClass(macroName,options);
|
|
}
|
|
};
|
|
|
|
Macro.prototype = new Node();
|
|
Macro.prototype.constructor = Macro;
|
|
|
|
/*
|
|
Evaluate the dependencies of this macro invocation. If the macro provides an `evaluateDependencies` method
|
|
then it is invoked to evaluate the dependencies. Otherwise it generates the dependencies based on the
|
|
macro parameters provided
|
|
*/
|
|
Macro.prototype.evaluateDependencies = function() {
|
|
// Figure out the dependencies from the metadata and parameters
|
|
var dependencies = new $tw.Dependencies();
|
|
if(this.info.dependentAll) {
|
|
dependencies.dependentAll = true;
|
|
}
|
|
if(this.info.dependentOnContextTiddler) {
|
|
dependencies.dependentOnContextTiddler = true;
|
|
}
|
|
for(var m in this.info.params) {
|
|
var paramInfo = this.info.params[m];
|
|
if(m in this.srcParams && paramInfo.type === "tiddler") {
|
|
if(typeof this.srcParams[m] === "function") {
|
|
dependencies.dependentAll = true;
|
|
} else {
|
|
dependencies.addDependency(this.srcParams[m],!paramInfo.skinny);
|
|
}
|
|
}
|
|
}
|
|
return dependencies;
|
|
};
|
|
|
|
Macro.prototype.parseMacroParamString = function(paramString) {
|
|
/*jslint evil: true */
|
|
var params = {},
|
|
args = new $tw.utils.ArgParser(paramString,{defaultName: "anon", cascadeDefaults: this.info.cascadeDefaults}),
|
|
self = this,
|
|
insertParam = function(name,arg) {
|
|
if(arg.evaluated) {
|
|
params[name] = eval("(function(tiddler,wiki) {return " + arg.string + ";})");
|
|
} else {
|
|
params[name] = arg.string;
|
|
}
|
|
};
|
|
for(var m in this.info.params) {
|
|
var param = this.info.params[m],
|
|
arg;
|
|
if("byPos" in param && args.byPos[param.byPos] && (args.byPos[param.byPos].n === "anon" || args.byPos[param.byPos].n === m)) {
|
|
arg = args.byPos[param.byPos].v;
|
|
insertParam(m,arg);
|
|
} else {
|
|
arg = args.getValueByName(m);
|
|
if(!arg && param.byName === "default") {
|
|
arg = args.getValueByName("anon");
|
|
}
|
|
if(arg) {
|
|
insertParam(m,arg);
|
|
}
|
|
}
|
|
}
|
|
return params;
|
|
};
|
|
|
|
Macro.prototype.hasParameter = function(name) {
|
|
return this.params[name] !== undefined;
|
|
};
|
|
|
|
Macro.prototype.cloneContent = function() {
|
|
var contentClones = [];
|
|
for(var t=0; t<this.content.length; t++) {
|
|
contentClones.push(this.content[t].clone());
|
|
}
|
|
return contentClones;
|
|
};
|
|
|
|
Macro.prototype.clone = function() {
|
|
return new this.MacroClass(this.macroName,{
|
|
srcParams: this.srcParams,
|
|
content: this.cloneContent(),
|
|
wiki: this.wiki,
|
|
isBlock: this.isBlock,
|
|
dependencies: this.dependencies,
|
|
classes: this.classes
|
|
});
|
|
};
|
|
|
|
Macro.prototype.execute = function(parents,tiddlerTitle) {
|
|
parents = parents || [];
|
|
// Evaluate macro parameters to get their values
|
|
this.params = {};
|
|
var tiddler = this.wiki.getTiddler(tiddlerTitle);
|
|
if(!tiddler) {
|
|
tiddler = {title: tiddlerTitle};
|
|
}
|
|
for(var p in this.srcParams) {
|
|
if(typeof this.srcParams[p] === "function") {
|
|
this.params[p] = this.srcParams[p](tiddler.fields,this.wiki);
|
|
} else {
|
|
this.params[p] = this.srcParams[p];
|
|
}
|
|
}
|
|
// Save the context info for use when we're refreshing it
|
|
this.tiddlerTitle = tiddlerTitle;
|
|
this.parents = parents;
|
|
// Execute the macro to generate its node and children, and recursively execute them
|
|
this.child = this.executeMacro.call(this);
|
|
};
|
|
|
|
Macro.prototype.render = function(type) {
|
|
return this.child ? this.child.render(type) : "";
|
|
};
|
|
|
|
Macro.prototype.renderInDom = function(parentDomNode,insertBefore) {
|
|
if(this.child) {
|
|
this.child.renderInDom(parentDomNode,insertBefore);
|
|
this.postRenderInDom();
|
|
}
|
|
};
|
|
|
|
Macro.prototype.postRenderInDom = function() {
|
|
// Do nothing, individual macros can override
|
|
};
|
|
|
|
Macro.prototype.refresh = function(changes) {
|
|
var t,
|
|
self = this;
|
|
// Check if any of the dependencies of this macro node have changed
|
|
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
|
// Re-execute the macro if so
|
|
this.execute(this.parents,this.tiddlerTitle);
|
|
} else {
|
|
// Refresh the child node
|
|
if(this.child) {
|
|
this.child.refresh(changes);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Macros that need special refreshing should override this function
|
|
*/
|
|
Macro.prototype.refreshInDom = function(changes) {
|
|
// Check if any of the dependencies of this macro node have changed
|
|
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
|
// Re-execute the macro if so
|
|
this.reexecuteInDom();
|
|
} else {
|
|
// Refresh any child
|
|
if(this.child) {
|
|
this.child.refreshInDom(changes);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Re-execute a macro in the DOM
|
|
*/
|
|
Macro.prototype.reexecuteInDom = function() {
|
|
// Macros can only auto-refresh if one of their descendent child has a DOM node
|
|
var child = this.child;
|
|
while(!child.domNode && child.child) {
|
|
child = child.child;
|
|
}
|
|
var parentDomNode = child.domNode.parentNode,
|
|
insertBefore = child.domNode.nextSibling;
|
|
parentDomNode.removeChild(child.domNode);
|
|
this.execute(this.parents,this.tiddlerTitle);
|
|
this.renderInDom(parentDomNode,insertBefore);
|
|
};
|
|
|
|
Macro.prototype.addClass = function(className) {
|
|
this.classes = this.classes || [];
|
|
$tw.utils.pushTop(this.classes,className.split(" "));
|
|
};
|
|
|
|
exports.Macro = Macro;
|
|
|
|
})();
|