NameTiddlyReconMacro
DescriptionTiddlyWeb data explorer
AuthorFND
Sourcehttp://tiddlyweb.com
CodeRepositoryhttp://github.com/FND/tiddlyrecon
LicenseBSD
CoreVersion2.5

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");