Usage
<<TiddlyRecon [host]>>
Code
/*
* TiddlyWeb adaptor
*
* TODO:
* * error handling in callbacks
*/
var tiddlyweb = {
host: "" // defaults to current domain -- XXX: lacks server_prefix -- TODO: document; expects no trailing slash
};
(function($) {
$.extend(tiddlyweb, {
/*
* container has members type ("bag" or "recipe") and name
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadTiddlers: function(container, callback) {
var uri = "/" + container.type + "s/" +
encodeURIComponent(container.name) + "/tiddlers"
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadTiddler: function(title, container, callback) {
var uri = "/" + container.type + "s/" +
encodeURIComponent(container.name) + "/tiddlers/" +
encodeURIComponent(title)
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadBags: function(callback) {
var uri = "/bags";
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadBag: function(name, callback) {
var uri = "/bags/" + encodeURIComponent(name);
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadRecipes: function(callback) {
var uri = "/recipes";
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* callback is passed data, status and error (if applicable)
* see jQuery.ajax for details
*/
loadRecipe: function(name, callback) {
var uri = "/recipes/" + encodeURIComponent(name);
callback = callback || console.log; // XXX: DEBUG
this.loadData(uri, callback);
},
/*
* policy is an object with members write, create, delete, manage and accept,
* each an array of users/roles
*/
saveBag: function(name, policy) {
var uri = "/bags/" + encodeURIComponent(name);
var data = {
policy: policy
};
this.saveData(uri, data, console.log);
},
/*
* bags is an array of bag names
* filters currently unsupported
*/
saveRecipe: function(name, bags) {
var uri = "/recipes/" + encodeURIComponent(name);
var data = {};
this.saveData(uri, data, console.log);
},
// generic utility methods
loadData: function(uri, callback) {
localAjax({
url: this.host + uri,
type: "GET",
dataType: "json",
success: callback,
error: callback
});
},
saveData: function(uri, data, callback) {
localAjax({
url: this.host + uri,
type: "PUT",
dataType: "json",
data: $.toJSON(data),
complete: callback
});
}
});
/*
* enable AJAX calls from a local file
* triggers regular jQuery.ajax call after requesting enhanced privileges
*/
var localAjax = function(args) { // XXX: not required!?
if(document.location.protocol.indexOf("file") == 0 && window.Components &&
window.netscape && window.netscape.security) {
window.netscape.security.PrivilegeManager.
enablePrivilege("UniversalBrowserRead");
}
return jQuery.ajax(args);
};
})(jQuery);
(function() {
var $ = jQuery;
var tw = tiddlyweb; // TODO: chrjs should provide an instance
$.TiddlyRecon = function(root, host) {
tw.host = host;
$.TiddlyRecon.root = $(root).empty(); // XXX: singleton, bad
notify("loading status");
loadStatus();
notify("loading recipes");
tw.loadRecipes(populateRecipes);
};
// display status
var loadStatus = function() {
var container = $('<dl id="status" />').hide().appendTo($.TiddlyRecon.root);
var populateStatus = function(data, status, error) {
container.
append("<dt>user</dt>\n").
create("<dd />\n").text(data.username).end().
append("<dt>server</dt>\n").
create("<dd />\n").
create("<a />").attr("href", tw.host).text(tw.host).end().
end().
show();
};
tw.loadData("/status", populateStatus);
};
// list recipes
var populateRecipes = function(data, status, error) {
notify("populating recipes");
$('<div id="recipes" class="collection container" />').
append("<h2>Recipes</h2>").
create('<ul class="listing" />').
create("<li><i>(none)</i></li>").click(loadRecipe).end().
append($.map(data, function(item, i) {
return $("<li />").text(item).click(loadRecipe)[0];
})).
end().
appendTo($.TiddlyRecon.root);
};
// display recipe
var loadRecipe = function(ev) {
var recipe_node = $(this);
setActive(recipe_node);
var recipe_name = recipe_node.text(); // TODO: special handling for "(none)";
notify("loading recipe", recipe_name);
var recipe_container = recipe_node.parent().parent(). // XXX: simpler way to do this?
find("#recipe").remove().end(). // clear existing selection -- TODO: allow for multiple recipes?
create('<div id="recipe" class="entity" />').
create("<h3 />").text(recipe_name).end();
var callback = function(data, status, error) {
populateBags(recipe_container, data, status, error);
};
tw.loadRecipe(recipe_name, callback);
};
// list bags
var populateBags = function(container, data, status, error) {
notify("populating bags");
$('<div id="bags" class="collection container" />').
append("<h2>Bags</h2>").
create('<ul class="listing" />').
create("<li><i>(all)</i></li>").click(loadBag).end().
append($.map(data.recipe, function(item, i) {
var bag_name = item[0];
var filter = item[1] || "(none)"; // XXX: bad default
return $("<li />").text(bag_name).attr("title", filter).click(loadBag)[0]; // XXX: using title to retain filter is hacky
})).
end().
appendTo(container);
};
// display bag
var loadBag = function(ev) {
var bag_node = $(this);
setActive(bag_node);
var bag_name = bag_node.text(); // TODO: special handling for "(all)";
notify("loading bag", bag_name);
var bag_container = bag_node.parent().parent(). // XXX: simpler way to do this?
find("#bag").remove().end(). // clear existing selection -- TODO: allow for multiple bags?
create('<div id="bag" class="entity" />').
create("<h3 />").text(bag_name).end();
var callback = function(data, status, error) {
populateTiddlers(bag_container, data, status, error);
};
var container = {
type: "bag",
name: bag_name
};
tw.loadTiddlers(container, callback);
};
var populateTiddlers = function(container, data, status, error) {
notify("populating tiddlers");
$('<div id="tiddlers" class="collection" />').
append("<h2>Tiddlers</h2>").
create('<ul class="listing" />').
append($.map(data, function(item, i) {
return $("<li />").text(item.title).attr("title", item.bag).click(loadTiddler)[0]; // XXX: using title to retain bag is hacky
})).
end().
appendTo(container);
};
var loadTiddler = function(ev) {
var tiddler_node = $(this);
setActive(tiddler_node);
var title = tiddler_node.text();
var bag = tiddler_node.attr("title");
notify("loading tiddler", title, bag);
var tiddler_container = tiddler_node.parent().parent(). // XXX: simpler way to do this?
find("#tiddler").remove().end(). // clear existing selection -- TODO: allow for multiple bags?
create('<div id="tiddler" class="entity" />').
create("<h3 />").text(title).end();
var callback = function(data, status, error) {
populateTiddler(tiddler_container, data, status, error);
};
var container = {
type: "bag",
name: bag
};
tw.loadTiddler(title, container, callback);
};
var populateTiddler = function(container, data, status, error) {
notify("populating tiddler");
$('<div class="content" />').text(data.text).appendTo(container); // XXX: request wikified text!?
};
var setActive = function(node) {
node.siblings().removeClass("active");
node.addClass("active");
};
// utility functions -- TODO: move into separate module
var notify = function(msg) { // TODO: use jQuery.notify
// XXX: DEBUG
if(window.console && console.log) {
console.log("notify:", msg);
}
};
// utility method to create and then select elements
// in combination with jQuery's end method, this is generally useful for
// dynamically generating nested elements within a chain of operations
$.fn.create = function(html) {
return this.append(html).children(":last");
};
})();
tiddlyweb.host = "http://tiddlyweb.peermore.com/wiki";
/*
* TiddlyWiki macro wrapper and backstage integration
*/
config.macros.TiddlyRecon = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var host = params[0] || config.defaultCustomFields["server.host"];
jQuery.TiddlyRecon(place, host);
}
};
config.tasks.server = {
text: "server",
tooltip: "TiddlyWeb",
content: "<<TiddlyRecon>>"
};
config.backstageTasks.push("server");