Welcome to TiddlyWiki created by Jeremy Ruston; Copyright © 2004-2007 Jeremy Ruston, Copyright © 2007-2011 UnaMesa Association
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='commentBreadcrumb' macro='commentBreadcrumb'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<div class='comments' macro="comments fields:'server.workspace:bags/helpcomments' inheritedFields:'server.host,server.type'"></div>
<!--}}}-->
/***
|''Name:''|DiffFormatterPlugin|
|''Description:''|Extension of TiddlyWiki syntax to support Diff text formatting|
|''Author:''|Martin Budden (mjbudden (at) gmail (dot) com)|
|''Source:''|http://www.martinswiki.com/#DiffFormatterPlugin |
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/MartinBudden/formatters/DiffFormatterPlugin.js |
|''Version:''|0.0.3|
|''Date:''|Sep 11, 2009|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]] |
|''~CoreVersion:''|2.1.0|
This is an early release of the DiffFormatterPlugin, which extends the TiddlyWiki syntax to support Diff
text formatting.
The Diff formatter is different from the other formatters in that Tiddlers are not required to be
tagged: instead the Diff format adds formatting that augments TiddlyWiki's format.
The Diff formatter adds the following:
# ^+ for added
# ^- for removed
# ^"""@@ """, """--- """ and """+++ """ for special markers
Please report any defects you find at http://groups.google.co.uk/group/TiddlyWikiDev
!StyleSheet
.viewer .removed { background: #fdd; }
.viewer .added { background: #dfd; }
!Code
***/
//{{{
// Ensure that the DiffFormatterPlugin is only installed once.
if(!version.extensions.DiffFormatterPlugin) {
version.extensions.DiffFormatterPlugin = {installed:true};
if(version.major < 2 || (version.major == 2 && version.minor < 1)) {
alertAndThrow('DiffFormatterPlugin requires TiddlyWiki 2.1 or later.');
}
diffFormatter = {}; // 'namespace' for local functions
diffFormatter.init = function() {
var stylesheet = store.getTiddlerText(tiddler.title + "##StyleSheet");
if(stylesheet) { // check necessary because it happens more than once for some reason
config.shadowTiddlers["StyleSheetDiffFormatter"] = stylesheet;
store.addNotification("StyleSheetDiffFormatter", refreshStyles);
}
};
diffFormatter.added = {
name: 'diffAdded',
match: '^\\+',
termRegExp: /(\n)/mg,
handler: function(w)
{
var e = createTiddlyElement(w.output,'span',null,'added');
w.subWikifyTerm(e,this.termRegExp);
createTiddlyElement(w.output,'br');
}
};
diffFormatter.removed = {
name: 'diffRemoved',
match: '^-',
termRegExp: /(\n)/mg,
handler: function(w)
{
var e = createTiddlyElement(w.output,'span',null,'removed');
w.subWikifyTerm(e,this.termRegExp);
createTiddlyElement(w.output,'br');
}
};
diffFormatter.charDiff = {
name: 'diffChars',
match: '^(?:@@|[+-]{3}) ',
lookaheadRegExp: /^(?:@@|[+-]{3}) .*\n/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
};
// add new formatters
diffFormatter.init();
config.formatters.push(diffFormatter.added);
config.formatters.push(diffFormatter.removed);
diffFormatter.replaceFormatter = function()
{
for(var i=0; i<config.formatters.length; i++) {
if(config.formatters[i].name == 'characterFormat') {
config.formatters.splice(i,0,diffFormatter.charDiff);
break;
}
}
};
diffFormatter.replaceFormatter();
}// end of 'install only once'
//}}}
!Welcome
TiddlyHoster is a system for hosting ~TiddlyWikis in a multi-user environment that allows people to share and reuse content, called [[tiddlers|tiddler]], stored in [[bags|bag]]. It is built using TiddlyWeb.
TiddlyHoster exposes a web-based interface to the fundamentals of TiddlyWeb: [[tiddlers|tiddler]], [[bags|bag]], [[recipes|recipe]], [[policies|policy]] and [[filters|filter]].
Some of the content in this wiki is borrowed from a [[glossary at TiddlySpace|http://glossary.tiddlyspace.com/]]. TiddlySpace is a system similar to TiddlyHoster that uses TiddlyWiki as the primary interface for controlling and accessing tiddlers. Read [[TiddlyHoster and TiddlySpace]] for more.
For additional information see [[the TiddlyWeb documentation|http://tiddlyweb.peermore.com/wiki/]].
A recipe is an ordered list of [[bags|bag]] and [[filters|filter]] that generates a list of [[tiddlers|tiddler]].
They are used to create collections of [[tiddlers|tiddler]] with a set of data and content customized for a particular use case.
If processing a recipe finds more than one instance of a tiddler with the same name, the tiddler from the most recently processed [[bag]] in the recipe list is the one used. This process is called the recipe cascade.
[[See more|http://tiddlyweb.peermore.com/wiki/#recipe]]
[[TiddlyWiki|http://tiddlywiki.com]] is a single html file which has all the characteristics of a wiki - including all of the content, the functionality (including editing, saving, tagging and searching) and the style sheet. Because it's a single file, it's very portable - you can email it, put it on a web server or share it via a USB stick.
|''Name''|TPCTheme|
|''Description''|TiddlyWiki theme originating from [[TPC|http://tiddlyweb.peermore.com]]|
|''PageTemplate''|##PageTemplate|
|''StyleSheet''|##StyleSheet|
!PageTemplate
<!--{{{-->
<div id='header'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
<div id='searchBox' macro='search'></div>
</div>
<div id='mainMenu'>
<div id='submainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
</div>
<div id='sidebar'>
<div id='adhome'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
!StyleSheet
/*{{{*/
/*
* page layout
*/
body {
font-family: "Lucida Grande", Helvetica, sans-serif;
}
#header {
position: relative;
border-bottom: 2px solid [[ColorPalette::TertiaryDark]];
padding: 10px 10px 5px;
background-color: [[ColorPalette::Foreground]];
}
#header,
#header a {
color: [[ColorPalette::Background]];
}
#header a:hover {
color: [[ColorPalette::Foreground]];
background-color: [[ColorPalette::Background]];
}
#searchBox {
position: absolute;
bottom: 10px;
right: 10px;
}
#searchBox .searchButton {
margin-right: 5px;
font-weight: bold;
}
/*
* sidebars
*/
#mainMenu a {
color: [[ColorPalette::Foreground]];
}
#mainMenu a:hover {
color: [[ColorPalette::Background]];
background-color: [[ColorPalette::Foreground]];
}
#mainMenu .logos {
margin-top: 25px;
}
#mainMenu .logos img {
width: 100px;
margin: 2px;
}
#mainMenu .osmologo a {
display: block; /* adjust height */
float: right; /* shrink-wrap */
background-color: #000;
}
#mainMenu .logos a:hover {
background-color: transparent;
}
#sidebarOptions {
margin-top: 20px;
}
#sidebarOptions .button {
padding: 0 2px;
}
#sidebarOptions .button:hover {
border-color: [[ColorPalette::TertiaryDark]];
}
#sidebarTabs .tabset,
#sidebarTabs .tabContents,
#sidebarTabs .tabsetWrapper a {
color: [[ColorPalette::Foreground]];
background-color: [[ColorPalette::Background]];
}
#sidebarTabs .tabsetWrapper a:hover {
color: [[ColorPalette::Background]];
background-color: [[ColorPalette::Foreground]];
}
#sidebarTabs .tiddlyLinkNonExisting {
font-style: italic;
}
/*
* story
*/
.tiddler {
margin-bottom: 10px;
border: 1px solid [[ColorPalette::TertiaryPale]];
padding-bottom: 10px;
}
.selected {
border-color: [[ColorPalette::TertiaryLight]];
}
/*
* comments
*/
.comments {
margin: 5em 1em 1em;
border-top: 2px solid [[ColorPalette::TertiaryMid]];
padding: 2em;
}
.comments .comments {
margin: 1em;
border: none;
}
#adsense {
margin-top: 1em;
padding: .5em;
border: solid thin black;
background: [[ColorPalette::TertiaryPale]];
}
#twitter_div {
padding-bottom: .5em;
}
#twitter_div ul {
display: block;
padding-left: 0;
margin-left: 0;
}
#twitter_div li {
display: block;
}
#twitter_div span {
display: block;
}
/*}}}*/
The fundamental difference between TiddlyHoster and TiddlySpace comes down to where the UI is focused. In TiddlySpace nearly everything happens inside a TiddlyWiki. That TiddlyWiki contains lots of extra plugins and content to make that stuff happen. In TiddlyHoster the UI (for managing bags and recipes and such) is in more traditional web pages and the ~TiddlyWikis that you can access are entirely optional, and only have a very small number of extra plugins (those required to save content back to the server).
TiddlySpace is fairly friendly and user-oriented but (thus far) lacking in granular control over [[bags|bag]], [[recipes|recipe]], [[policies|policy]] and [[filters|policy]]. TiddlyHoster sacrifices some friendly for greater access to bags, recipes, policies and filters.
If you have to choose just one, for many people TiddlySpace is likely the better choice: it's easier to use and is receiving a lot of attention. However if you are someone who likes to twist the knobs or has special [[access control|policy]] and [[recipe]] management needs, TiddlyHoster might appeal to you.
A Tiddler is the fundamental unit of content in a TiddlyWiki. In TiddlyHoster, each tiddler exists in a [[bag]] and may be accessed through the TiddlyWeb [[HTTP API]]. For example there is a copy of this tiddler available at http://hoster.peermore.com/bags/help/tiddlers/tiddler.txt
A Tiddler may contain human readable content, binary data, or ~JavaScript code.
TiddlySpace is a service similar to TiddlyHoster, with a [[somewhat different focus|TiddlyHoster and TiddlySpace]].
TiddlySpace [[describes itself|http://glossary.tiddlyspace.com/#TiddlySpace]] as:
>TiddlySpace is a service for creating and curating small [[packets of content|tiddler]] on The Web. You may collaborate with others, assemble content, add cool features using plugins and mix your content with other services using the [[API|HTTP API]].
>TiddlySpace is [[Open Source|http://en.wikipedia.org/wiki/Open_source]] software from Osmosoft so you're free to host your own instance on your own domain and are encouraged to contribute to the [[project|http://github.com/TiddlySpace/tiddlyspace]].
TiddlyWeb is a server-side for TiddlyWiki. It provides a ~RESTful HTTP API for storing, managing and accessing tiddlers with flexible authentication and access control systems that make it possible to dynamically build custom collections of tiddlers.
TiddlyWeb has a home site at http://tiddlyweb.com/
/***
|''Name''|TiddlyWebConfig|
|''Description''|configuration settings for TiddlyWebWiki|
|''Author''|FND|
|''Version''|1.3.2|
|''Status''|stable|
|''Source''|http://svn.tiddlywiki.org/Trunk/association/plugins/TiddlyWebConfig.js|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''Requires''|TiddlyWebAdaptor ServerSideSavingPlugin|
|''Keywords''|serverSide TiddlyWeb|
!Code
***/
//{{{
(function($) {
if(!config.extensions.ServerSideSavingPlugin) {
throw "Missing dependency: ServerSideSavingPlugin";
}
if(!config.adaptors.tiddlyweb) {
throw "Missing dependency: TiddlyWebAdaptor";
}
if(window.location.protocol != "file:") {
config.options.chkAutoSave = true;
}
var adaptor = tiddler.getAdaptor();
var recipe = tiddler.fields["server.recipe"];
var workspace = recipe ? "recipes/" + recipe : "bags/common";
var plugin = config.extensions.tiddlyweb = {
host: tiddler.fields["server.host"].replace(/\/$/, ""),
username: null,
status: {},
getStatus: null, // assigned later
getUserInfo: function(callback) {
this.getStatus(function(status) {
callback({
name: plugin.username,
anon: plugin.username ? plugin.username == "GUEST" : true
});
});
},
hasPermission: function(type, tiddler) {
var perms = tiddler.fields["server.permissions"];
if(perms) {
return perms.split(", ").contains(type);
} else {
return true;
}
}
};
config.defaultCustomFields = {
"server.type": tiddler.getServerType(),
"server.host": plugin.host,
"server.workspace": workspace
};
// modify toolbar commands
config.shadowTiddlers.ToolbarCommands = config.shadowTiddlers.ToolbarCommands.
replace("syncing ", "revisions syncing ");
config.commands.saveTiddler.isEnabled = function(tiddler) {
return plugin.hasPermission("write", tiddler) && !tiddler.isReadOnly();
};
config.commands.deleteTiddler.isEnabled = function(tiddler) {
return !readOnly && plugin.hasPermission("delete", tiddler);
};
// hijack option macro to disable username editing
var _optionMacro = config.macros.option.handler;
config.macros.option.handler = function(place, macroName, params, wikifier,
paramString) {
if(params[0] == "txtUserName") {
params[0] = "options." + params[0];
var self = this;
var args = arguments;
args[0] = $("<span />").appendTo(place)[0];
plugin.getUserInfo(function(user) {
config.macros.message.handler.apply(self, args);
});
} else {
_optionMacro.apply(this, arguments);
}
};
// hijack isReadOnly to take into account permissions and content type
var _isReadOnly = Tiddler.prototype.isReadOnly;
Tiddler.prototype.isReadOnly = function() {
return _isReadOnly.apply(this, arguments) ||
!plugin.hasPermission("write", this);
};
var getStatus = function(callback) {
if(plugin.status.version) {
callback(plugin.status);
} else {
var self = getStatus;
if(self.pending) {
if(callback) {
self.queue.push(callback);
}
} else {
self.pending = true;
self.queue = callback ? [callback] : [];
var _callback = function(context, userParams) {
var status = context.serverStatus || {};
for(var key in status) {
if(key == "username") {
plugin.username = status[key];
config.macros.option.propagateOption("txtUserName",
"value", plugin.username, "input");
} else {
plugin.status[key] = status[key];
}
}
for(var i = 0; i < self.queue.length; i++) {
self.queue[i](plugin.status);
}
delete self.queue;
delete self.pending;
};
adaptor.getStatus({ host: plugin.host }, null, _callback);
}
}
};
(plugin.getStatus = getStatus)(); // XXX: hacky (arcane combo of assignment plus execution)
})(jQuery);
//}}}
[[TiddlyHoster]]
[[Back To Service|/]]
Hola ¿cómo puedo instalar tiddlyweb en servicio de hosting?
/***
|''Name''|TiddlyWebAdaptor|
|''Description''|adaptor for interacting with TiddlyWeb|
|''Author:''|FND|
|''Contributors''|Chris Dent, Martin Budden|
|''Version''|1.4.10|
|''Status''|stable|
|''Source''|http://svn.tiddlywiki.org/Trunk/association/adaptors/TiddlyWebAdaptor.js|
|''CodeRepository''|http://svn.tiddlywiki.org/Trunk/association/|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.5|
|''Keywords''|serverSide TiddlyWeb|
!Notes
This plugin includes [[jQuery JSON|http://code.google.com/p/jquery-json/]].
!To Do
* createWorkspace
* document custom/optional context attributes (e.g. filters, query, revision) and tiddler fields (e.g. server.title, origin)
!Code
***/
//{{{
(function($) {
var adaptor = config.adaptors.tiddlyweb = function() {};
adaptor.prototype = new AdaptorBase();
adaptor.serverType = "tiddlyweb";
adaptor.serverLabel = "TiddlyWeb";
adaptor.mimeType = "application/json";
adaptor.parsingErrorMessage = "Error parsing result from server";
adaptor.noBagErrorMessage = "no bag specified for tiddler";
adaptor.locationIDErrorMessage = "no bag or recipe specified for tiddler"; // TODO: rename
// retrieve current status (requires TiddlyWeb status plugin)
adaptor.prototype.getStatus = function(context, userParams, callback) {
context = this.setContext(context, userParams, callback);
var uriTemplate = "%0/status";
var uri = uriTemplate.format([context.host]);
var req = httpReq("GET", uri, adaptor.getStatusCallback, context,
null, null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getStatusCallback = function(status, context, responseText, uri, xhr) {
context.status = responseText ? status : false;
try {
context.statusText = xhr.statusText;
} catch(exc) { // offline (Firefox)
context.status = false;
context.statusText = null;
}
context.httpStatus = xhr.status;
if(context.status) {
context.serverStatus = $.evalJSON(responseText); // XXX: error handling!?
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// retrieve a list of workspaces
adaptor.prototype.getWorkspaceList = function(context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.workspaces = [];
var uriTemplate = "%0/recipes"; // XXX: bags?
var uri = uriTemplate.format([context.host]);
var req = httpReq("GET", uri, adaptor.getWorkspaceListCallback,
context, { accept: adaptor.mimeType }, null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getWorkspaceListCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(status) {
try {
var workspaces = $.evalJSON(responseText);
} catch(ex) {
context.status = false; // XXX: correct?
context.statusText = exceptionText(ex, adaptor.parsingErrorMessage);
if(context.callback) {
context.callback(context, context.userParams);
}
return;
}
context.workspaces = workspaces.map(function(itm) { return { title: itm }; });
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// retrieve a list of tiddlers
adaptor.prototype.getTiddlerList = function(context, userParams, callback) {
context = this.setContext(context, userParams, callback);
var uriTemplate = "%0/%1/%2/tiddlers%3";
var params = context.filters ? "?" + context.filters : "";
if(context.format) {
params = context.format + params;
}
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([context.host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name), params]);
var req = httpReq("GET", uri, adaptor.getTiddlerListCallback,
context, merge({ accept: adaptor.mimeType }, context.headers), null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getTiddlerListCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(status) {
context.tiddlers = [];
try {
var tiddlers = $.evalJSON(responseText); //# NB: not actual tiddler instances
} catch(ex) {
context.status = false; // XXX: correct?
context.statusText = exceptionText(ex, adaptor.parsingErrorMessage);
if(context.callback) {
context.callback(context, context.userParams);
}
return;
}
for(var i = 0; i < tiddlers.length; i++) {
var tiddler = adaptor.toTiddler(tiddlers[i], context.host);
context.tiddlers.push(tiddler);
}
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// perform global search
adaptor.prototype.getSearchResults = function(context, userParams, callback) {
context = this.setContext(context, userParams, callback);
var uriTemplate = "%0/search?q=%1%2";
var filterString = context.filters ? ";" + context.filters : "";
var uri = uriTemplate.format([context.host, context.query, filterString]); // XXX: parameters need escaping?
var req = httpReq("GET", uri, adaptor.getSearchResultsCallback,
context, { accept: adaptor.mimeType }, null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getSearchResultsCallback = function(status, context, responseText, uri, xhr) {
adaptor.getTiddlerListCallback(status, context, responseText, uri, xhr); // XXX: use apply?
};
// retrieve a particular tiddler's revisions
adaptor.prototype.getTiddlerRevisionList = function(title, limit, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
var uriTemplate = "%0/%1/%2/tiddlers/%3/revisions";
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([context.host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name), adaptor.normalizeTitle(title)]);
var req = httpReq("GET", uri, adaptor.getTiddlerRevisionListCallback,
context, merge({ accept: adaptor.mimeType }, context.headers), null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getTiddlerRevisionListCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(status) {
context.revisions = [];
try {
var tiddlers = $.evalJSON(responseText); //# NB: not actual tiddler instances
} catch(ex) {
context.status = false; // XXX: correct?
context.statusText = exceptionText(ex, adaptor.parsingErrorMessage);
if(context.callback) {
context.callback(context, context.userParams);
}
return;
}
for(var i = 0; i < tiddlers.length; i++) {
var tiddler = adaptor.toTiddler(tiddlers[i], context.host);
context.revisions.push(tiddler);
}
var sortField = "server.page.revision";
context.revisions.sort(function(a, b) {
return a.fields[sortField] < b.fields[sortField] ? 1 :
(a.fields[sortField] == b.fields[sortField] ? 0 : -1);
});
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// retrieve an individual tiddler revision -- XXX: breaks with standard arguments list -- XXX: convenience function; simply use getTiddler?
adaptor.prototype.getTiddlerRevision = function(title, revision, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.revision = revision;
return this.getTiddler(title, context, userParams, callback);
};
// retrieve an individual tiddler
//# context is an object with members host and workspace
//# callback is passed the new context and userParams
adaptor.prototype.getTiddler = function(title, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = title;
if(context.revision) {
var uriTemplate = "%0/%1/%2/tiddlers/%3/revisions/%4";
} else {
uriTemplate = "%0/%1/%2/tiddlers/%3";
}
if(!context.tiddler) {
context.tiddler = new Tiddler(title);
}
context.tiddler.fields["server.type"] = adaptor.serverType;
context.tiddler.fields["server.host"] = AdaptorBase.minHostName(context.host);
context.tiddler.fields["server.workspace"] = context.workspace;
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([context.host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name), adaptor.normalizeTitle(title),
context.revision]);
var req = httpReq("GET", uri, adaptor.getTiddlerCallback, context,
merge({ accept: adaptor.mimeType }, context.headers), null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getTiddlerCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(status) {
try {
var tid = $.evalJSON(responseText);
} catch(ex) {
context.status = false;
context.statusText = exceptionText(ex, adaptor.parsingErrorMessage);
if(context.callback) {
context.callback(context, context.userParams);
}
return;
}
var tiddler = adaptor.toTiddler(tid, context.host);
tiddler.title = context.tiddler.title;
tiddler.fields["server.etag"] = xhr.getResponseHeader("Etag");
// normally we'd assign context.tiddler = tiddler here - but we can't do
// that because of IE, which triggers getTiddler in putTiddlerCallback,
// and since ServerSideSavingPlugin foolishly relies on persistent
// object references, we need to merge the data into the existing object
$.extend(context.tiddler, tiddler);
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// retrieve tiddler chronicle (all revisions)
adaptor.prototype.getTiddlerChronicle = function(title, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = title;
var uriTemplate = "%0/%1/%2/tiddlers/%3/revisions?fat=1";
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([context.host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name), adaptor.normalizeTitle(title)]);
var req = httpReq("GET", uri, adaptor.getTiddlerChronicleCallback,
context, { accept: adaptor.mimeType }, null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.getTiddlerChronicleCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(status) {
context.responseText = responseText;
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// store an individual tiddler
adaptor.prototype.putTiddler = function(tiddler, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = tiddler.title;
context.tiddler = tiddler;
context.host = context.host || this.fullHostName(tiddler.fields["server.host"]);
var uriTemplate = "%0/%1/%2/tiddlers/%3";
try {
context.workspace = context.workspace || tiddler.fields["server.workspace"];
var workspace = adaptor.resolveWorkspace(context.workspace);
} catch(ex) {
return adaptor.locationIDErrorMessage;
}
var uri = uriTemplate.format([context.host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name),
adaptor.normalizeTitle(tiddler.title)]);
var etag = adaptor.generateETag(workspace, tiddler);
var headers = etag ? { "If-Match": etag } : null;
var payload = {
type: tiddler.fields["server.content-type"] || null,
text: tiddler.text,
tags: tiddler.tags,
fields: $.extend({}, tiddler.fields)
};
delete payload.fields.changecount;
$.each(payload.fields, function(key, value) {
if(key.indexOf("server.") == 0) {
delete payload.fields[key];
}
});
payload = $.toJSON(payload);
var req = httpReq("PUT", uri, adaptor.putTiddlerCallback,
context, headers, payload, adaptor.mimeType, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.putTiddlerCallback = function(status, context, responseText, uri, xhr) {
context.status = [204, 1223].contains(xhr.status);
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(context.status) {
var loc = xhr.getResponseHeader("Location");
var etag = xhr.getResponseHeader("Etag");
if(loc && etag) {
var bag = loc.split("/bags/").pop().split("/")[0];
context.tiddler.fields["server.bag"] = bag;
context.tiddler.fields["server.workspace"] = "bags/" + bag;
var rev = etag.split("/").pop().split(/;|:/)[0];
context.tiddler.fields["server.page.revision"] = rev;
context.tiddler.fields["server.etag"] = etag;
if(context.callback) {
context.callback(context, context.userParams);
}
} else { // IE
context.adaptor.getTiddler(context.tiddler.title, context,
context.userParams, context.callback);
}
} else if(context.callback) {
context.callback(context, context.userParams);
}
};
// store a tiddler chronicle
adaptor.prototype.putTiddlerChronicle = function(revisions, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = revisions[0].title;
var headers = null;
var uriTemplate = "%0/%1/%2/tiddlers/%3/revisions";
var host = context.host || this.fullHostName(tiddler.fields["server.host"]);
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name),
adaptor.normalizeTitle(context.title)]);
if(workspace.type == "bag") { // generate ETag
var etag = [adaptor.normalizeTitle(workspace.name),
adaptor.normalizeTitle(context.title), 0].join("/"); //# zero-revision prevents overwriting existing contents
headers = { "If-Match": '"' + etag + '"' };
}
var payload = $.toJSON(revisions);
var req = httpReq("POST", uri, adaptor.putTiddlerChronicleCallback,
context, headers, payload, adaptor.mimeType, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.putTiddlerChronicleCallback = function(status, context, responseText, uri, xhr) {
context.status = [204, 1223].contains(xhr.status);
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(context.callback) {
context.callback(context, context.userParams);
}
};
// store a collection of tiddlers (import TiddlyWiki HTML store)
adaptor.prototype.putTiddlerStore = function(store, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
var uriTemplate = "%0/%1/%2/tiddlers";
var host = context.host;
var workspace = adaptor.resolveWorkspace(context.workspace);
var uri = uriTemplate.format([host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name)]);
var req = httpReq("POST", uri, adaptor.putTiddlerStoreCallback,
context, null, store, "text/x-tiddlywiki", null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.putTiddlerStoreCallback = function(status, context, responseText, uri, xhr) {
context.status = [204, 1223].contains(xhr.status);
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(context.callback) {
context.callback(context, context.userParams);
}
};
// rename an individual tiddler or move it to a different workspace -- TODO: make {from|to}.title optional
//# from and to are objects with members title and workspace (bag; optional),
//# representing source and target tiddler, respectively
adaptor.prototype.moveTiddler = function(from, to, context, userParams, callback) { // XXX: rename parameters (old/new)?
var self = this;
var newTiddler = store.getTiddler(from.title) || store.getTiddler(to.title); //# local rename might already have occurred
var oldTiddler = $.extend(true, {}, newTiddler); //# required for eventual deletion
oldTiddler.title = from.title; //# required for original tiddler's ETag
var _getTiddlerChronicle = function(title, context, userParams, callback) {
return self.getTiddlerChronicle(title, context, userParams, callback);
};
var _putTiddlerChronicle = function(context, userParams) {
if(!context.status) {
return callback(context, userParams);
}
var revisions = $.evalJSON(context.responseText); // XXX: error handling?
// change current title while retaining previous location
for(var i = 0; i < revisions.length; i++) {
delete revisions[i].revision;
if(!revisions[i].fields.origin) { // NB: origin = "<workspace>/<title>"
revisions[i].fields.origin = ["bags", revisions[i].bag, revisions[i].title].join("/");
}
revisions[i].title = to.title;
}
// add new revision
var rev = $.extend({}, revisions[0]);
$.each(newTiddler, function(i, item) {
if(!$.isFunction(item)) {
rev[i] = item;
}
});
rev.title = to.title;
rev.created = rev.created.convertToYYYYMMDDHHMM();
rev.modified = new Date().convertToYYYYMMDDHHMM();
delete rev.fields.changecount;
revisions.unshift(rev);
if(to.workspace) {
context.workspace = to.workspace;
} else if(context.workspace.substring(0, 4) != "bags") { // NB: target workspace must be a bag
context.workspace = "bags/" + rev.bag;
}
var subCallback = function(context, userParams) {
if(!context.status) {
return callback(context, userParams);
}
context.adaptor.getTiddler(newTiddler.title, context, userParams, _deleteTiddler);
};
return self.putTiddlerChronicle(revisions, context, context.userParams, subCallback);
};
var _deleteTiddler = function(context, userParams) {
if(!context.status) {
return callback(context, userParams);
}
$.extend(true, newTiddler, context.tiddler);
context.callback = null;
return self.deleteTiddler(oldTiddler, context, context.userParams, callback);
};
callback = callback || function() {};
context = this.setContext(context, userParams);
context.host = context.host || oldTiddler.fields["server.host"];
context.workspace = from.workspace || oldTiddler.fields["server.workspace"];
return _getTiddlerChronicle(from.title, context, userParams, _putTiddlerChronicle);
};
// delete an individual tiddler
adaptor.prototype.deleteTiddler = function(tiddler, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = tiddler.title; // XXX: not required!?
var uriTemplate = "%0/bags/%1/tiddlers/%2";
var host = context.host || this.fullHostName(tiddler.fields["server.host"]);
var bag = tiddler.fields["server.bag"];
if(!bag) {
return adaptor.noBagErrorMessage;
}
var uri = uriTemplate.format([host, adaptor.normalizeTitle(bag),
adaptor.normalizeTitle(tiddler.title)]);
var etag = adaptor.generateETag({ type: "bag", name: bag }, tiddler);
var headers = etag ? { "If-Match": etag } : null;
var req = httpReq("DELETE", uri, adaptor.deleteTiddlerCallback, context, headers,
null, null, null, null, true);
return typeof req == "string" ? req : true;
};
adaptor.deleteTiddlerCallback = function(status, context, responseText, uri, xhr) {
context.status = [204, 1223].contains(xhr.status);
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
if(context.callback) {
context.callback(context, context.userParams);
}
};
// compare two revisions of a tiddler (requires TiddlyWeb differ plugin)
//# if context.rev1 is not specified, the latest revision will be used for comparison
//# if context.rev2 is not specified, the local revision will be sent for comparison
//# context.format is a string as determined by the TiddlyWeb differ plugin
adaptor.prototype.getTiddlerDiff = function(title, context, userParams, callback) {
context = this.setContext(context, userParams, callback);
context.title = title;
var tiddler = store.getTiddler(title);
try {
var workspace = adaptor.resolveWorkspace(tiddler.fields["server.workspace"]);
} catch(ex) {
return adaptor.locationIDErrorMessage;
}
var tiddlerRef = [workspace.type + "s", workspace.name, tiddler.title].join("/");
var rev1 = context.rev1 ? [tiddlerRef, context.rev1].join("/") : tiddlerRef;
var rev2 = context.rev2 ? [tiddlerRef, context.rev2].join("/") : null;
var uriTemplate = "%0/diff?rev1=%1";
if(rev2) {
uriTemplate += "&rev2=%2";
}
if(context.format) {
uriTemplate += "&format=%3";
}
var host = context.host || this.fullHostName(tiddler.fields["server.host"]);
var uri = uriTemplate.format([host, adaptor.normalizeTitle(rev1),
adaptor.normalizeTitle(rev2), context.format]);
if(rev2) {
var req = httpReq("GET", uri, adaptor.getTiddlerDiffCallback, context, null,
null, null, null, null, true);
} else {
var payload = {
title: tiddler.title,
text: tiddler.text,
modifier: tiddler.modifier,
tags: tiddler.tags,
fields: $.extend({}, tiddler.fields)
}; // XXX: missing attributes!?
payload = $.toJSON(payload);
req = httpReq("POST", uri, adaptor.getTiddlerDiffCallback, context,
null, payload, adaptor.mimeType, null, null, true);
}
return typeof req == "string" ? req : true;
};
adaptor.getTiddlerDiffCallback = function(status, context, responseText, uri, xhr) {
context.status = status;
context.statusText = xhr.statusText;
context.httpStatus = xhr.status;
context.uri = uri;
if(status) {
context.diff = responseText;
}
if(context.callback) {
context.callback(context, context.userParams);
}
};
// generate tiddler information
adaptor.prototype.generateTiddlerInfo = function(tiddler) {
var info = {};
var uriTemplate = "%0/%1/%2/tiddlers/%3";
var host = this.host || tiddler.fields["server.host"]; // XXX: this.host obsolete?
host = this.fullHostName(host);
var workspace = adaptor.resolveWorkspace(tiddler.fields["server.workspace"]);
info.uri = uriTemplate.format([host, workspace.type + "s",
adaptor.normalizeTitle(workspace.name),
adaptor.normalizeTitle(tiddler.title)]);
return info;
};
// create Tiddler instance from TiddlyWeb tiddler JSON
adaptor.toTiddler = function(json, host) {
var created = Date.convertFromYYYYMMDDHHMM(json.created);
var modified = Date.convertFromYYYYMMDDHHMM(json.modified);
var fields = json.fields;
fields["server.type"] = adaptor.serverType;
fields["server.host"] = AdaptorBase.minHostName(host);
fields["server.bag"] = json.bag;
fields["server.title"] = json.title;
if(json.recipe) {
fields["server.recipe"] = json.recipe;
}
if(json.type && json.type != "None") {
fields["server.content-type"] = json.type;
}
fields["server.permissions"] = json.permissions.join(", ");
fields["server.page.revision"] = json.revision;
fields["server.workspace"] = "bags/" + json.bag;
var tiddler = new Tiddler(json.title);
tiddler.assign(tiddler.title, json.text, json.modifier, modified, json.tags,
created, json.fields, json.creator);
return tiddler;
};
adaptor.resolveWorkspace = function(workspace) {
var components = workspace.split("/");
return {
type: components[0] == "bags" ? "bag" : "recipe",
name: components[1] || components[0]
};
};
adaptor.generateETag = function(workspace, tiddler) {
var revision = tiddler.fields["server.page.revision"];
var etag = revision == "false" ? null : tiddler.fields["server.etag"];
if(!etag && workspace.type == "bag") {
if(typeof revision == "undefined") {
revision = "0";
} else if(revision == "false") {
return null;
}
etag = [adaptor.normalizeTitle(workspace.name),
adaptor.normalizeTitle(tiddler.title), revision].join("/");
etag = '"' + etag + '"';
}
return etag;
};
adaptor.normalizeTitle = function(title) {
return encodeURIComponent(title);
};
})(jQuery);
/*
* jQuery JSON Plugin
* version: 1.3
* source: http://code.google.com/p/jquery-json/
* license: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(function($){function toIntegersAtLease(n)
{return n<10?'0'+n:n;}
Date.prototype.toJSON=function(date)
{return this.getUTCFullYear()+'-'+
toIntegersAtLease(this.getUTCMonth())+'-'+
toIntegersAtLease(this.getUTCDate());};var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g;var meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};$.quoteString=function(string)
{if(escapeable.test(string))
{return'"'+string.replace(escapeable,function(a)
{var c=meta[a];if(typeof c==='string'){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
return'"'+string+'"';};$.toJSON=function(o,compact)
{var type=typeof(o);if(type=="undefined")
return"undefined";else if(type=="number"||type=="boolean")
return o+"";else if(o===null)
return"null";if(type=="string")
{return $.quoteString(o);}
if(type=="object"&&typeof o.toJSON=="function")
return o.toJSON(compact);if(type!="function"&&typeof(o.length)=="number")
{var ret=[];for(var i=0;i<o.length;i++){ret.push($.toJSON(o[i],compact));}
if(compact)
return"["+ret.join(",")+"]";else
return"["+ret.join(", ")+"]";}
if(type=="function"){throw new TypeError("Unable to convert object of type 'function' to json.");}
var ret=[];for(var k in o){var name;type=typeof(k);if(type=="number")
name='"'+k+'"';else if(type=="string")
name=$.quoteString(k);else
continue;var val=$.toJSON(o[k],compact);if(typeof(val)!="string"){continue;}
if(compact)
ret.push(name+":"+val);else
ret.push(name+": "+val);}
return"{"+ret.join(", ")+"}";};$.compactJSON=function(o)
{return $.toJSON(o,true);};$.evalJSON=function(src)
{return eval("("+src+")");};$.secureEvalJSON=function(src)
{var filtered=src;filtered=filtered.replace(/\\["\\\/bfnrtu]/g,'@');filtered=filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']');filtered=filtered.replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered))
return eval("("+src+")");else
throw new SyntaxError("Error parsing JSON, source is not valid.");};})(jQuery);
//}}}
A policy is the TiddlyWeb method for controlling access to [[bags|bag]] and [[recipes|recipe]] on the system, thus controlling access to the contained [[tiddlers|tiddler]].
[[See more|http://tiddlyweb.peermore.com/wiki/#policy]]
How did you get commenting to work?
A filter is a tool for selecting some set of [[tiddlers|tiddler]] from a collection of tiddlers such a [[recipe]], [[bag]] or search results. They are primarily for use in the construction of recipes.
[[See more|http://tiddlyweb.peermore.com/wiki/#filter]]
This experiment with openID and TiddlyHoster
//{{{
config.options.chkAutoSave = true;
config.options.chkHttpReadOnly = true;
if (! tiddler.fields["server.permissions"].split(", ").contains("write")) {
readOnly = true;
config.options.chkHttpReadOnly = true;
}
config.defaultCustomFields["server.workspace"] = "bags/help";
config.options.txtTheme = "TPCTheme";
//}}}
Hola ¿cómo puedo instalar tiddlyweb en un servicio de hosting?
/*
TiddlyWiki Comments Plugin - Online demo at http://tiddlyguv.org/CommentsPlugin.html
TODO:
- Support Cascade comment delete when the top-level tiddler is deleted
- Support more than one < <comments> > per tiddler. This will probably entail creating an invisible root tiddler to
hold all the comments for a macro together. The user will need to provide an ID for this tiddler.
- Don't use global "macro" var (use "macro" param a la jquery)
*/
/***
|Name|CommentsPlugin|
|Description|Macro for nested comments, where each comment is a separate tiddler.|
|Source|http://tiddlyguv.org/CommentsPlugin.html#CommentsPlugin|
|Documentation|http://tiddlyguv.org/CommentsPlugin.html#CommentsPluginInfo|
|Version|0.1|
|Author|Michael Mahemoff, Osmosoft|
|''License:''|[[BSD open source license]]|
|~CoreVersion|2.2|
***/
/*{{{*/
if(!version.extensions.CommentsPlugin) {
version.extensions.CommentsPlugin = {installed:true};
(function(plugin) {
var cmacro = config.macros.comments = {
//##############################################################################
//# CONFIG
//##############################################################################
//################################################################################
//# MACRO INITIALISATION
//################################################################################
init: function() {
var stylesheet = store.getTiddlerText(tiddler.title + "##StyleSheet");
if (stylesheet) { // check necessary because it happens more than once for some reason
config.shadowTiddlers["StyleSheetCommentsPlugin"] = stylesheet;
store.addNotification("StyleSheetCommentsPlugin", refreshStyles);
}
if (!version.extensions.CommentsPlugin.retainViewTemplate) cmacro.enhanceViewTemplate();
},
enhanceViewTemplate: function() {
var template = config.shadowTiddlers.ViewTemplate;
if ((/commentBreadcrumb/g).test(template)) return; // already enhanced
var TITLE_DIV = "<div class='title' macro='view title'></div>";
var commentsDiv = "<div class='commentBreadcrumb' macro='commentBreadcrumb'></div>";
config.shadowTiddlers.ViewTemplate = template.replace(TITLE_DIV,commentsDiv+"\n"+TITLE_DIV);
},
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var macroParams = paramString.parseParams();
var tiddlerParam = getParam(macroParams, "tiddler");
tiddler = tiddlerParam ? store.getTiddler(tiddlerParam) : tiddler;
if (!tiddler || !store.getTiddler(tiddler.title)) return;
cmacro.buildCommentsArea(tiddler, place, macroParams);
// cmacro.refreshCommentsFromRoot(story.getTiddler(tiddler.title).commentsEl, tiddler, macroParams);
cmacro.refreshCommentsFromRoot(place.commentsEl, tiddler, macroParams);
},
//################################################################################
//# MACRO VIEW - RENDERING COMMENTS
//################################################################################
buildCommentsArea: function(rootTiddler, place, macroParams) {
var commentsArea = createTiddlyElement(place, "div", null, "comments");
var headingEl, heading = getParam(macroParams, "heading");
if (heading) heading = heading.replace("%c",
config.macros.comments.findCommentsFromRoot(rootTiddler).length);
if (heading) headingEl = createTiddlyElement(commentsArea, "h1", null, null, heading);
var comments = createTiddlyElement(commentsArea, "div", null, "");
place.commentsEl = comments;
var newCommentArea;
if (cmacro.editable(macroParams)) {
newCommentArea = createTiddlyElement(commentsArea, "div", null, "newCommentArea", "New comment:");
cmacro.forceLoginIfRequired(params, newCommentArea, function() {
var newCommentEl = cmacro.makeTextArea(newCommentArea, macroParams);
// var addComment = createTiddlyElement(newCommentArea, "button", null, "addComment button", "Add Comment");
var addComment = createTiddlyButton(newCommentArea, "Add Comment", null, function() {
var comment = cmacro.createComment(newCommentEl.value, rootTiddler, macroParams);
newCommentEl.value = "";
cmacro.refreshCommentsFromRoot(comments, rootTiddler, macroParams);
}, "addComment button");
});
}
var expandHeading = getParam(macroParams, "expandHeading");
if (heading && expandHeading && expandHeading.toLowerCase()=="true") {
var $expandPrompt, expandableElements;
var $expandables = jQuery([place.commentsEl,newCommentArea]).hide();
jQuery(headingEl)
.append($expandPrompt = "<span id='expandPrompt'> »</span>")
.addClass("expander").click(function() {
$expandables.toggle();
});
}
},
makeTextArea: function(container, macroParams) {
var textArea = createTiddlyElement(container, "textarea");
textArea.rows = getParam(macroParams, "textRows") || 4;
textArea.cols = getParam(macroParams, "textCols") || 20;
textArea.value = getParam(macroParams, "text") || "";
return textArea;
},
refreshCommentsFromRoot: function(rootCommentsEl, rootTiddler, macroParams) {
cmacro.treeifyComments(rootTiddler);
cmacro.refreshComments(rootCommentsEl, rootTiddler, macroParams);
},
refreshComments: function(daddyCommentsEl, tiddler, macroParams) {
var commentsEl;
if (tiddler.fields.daddy) {
var commentEl = cmacro.buildCommentEl(daddyCommentsEl, tiddler, macroParams);
daddyCommentsEl.appendChild(commentEl);
commentsEl = commentEl.commentsEl;
} else { // root element
removeChildren(daddyCommentsEl);
// refreshedEl = story.getTiddler(tiddler.title);
commentsEl = daddyCommentsEl;
}
for (var child = tiddler.firstChild; child; child = child.next) {
cmacro.refreshComments(commentsEl, child, macroParams);
}
},
// This has become more complex due to "confused comments" - multiple comments
// pointing back to the same daddy (which implies they all think they're the first
// child) or a single "2nd-last" sibling (which implies they all think they're the
// last sibling). This happens in the typical "atomic transaction 101" scenario -
// user A opens wiki, user B opens wiki, one of the users submits a comment,
// the other user submits a comment.
//
// Normally, each comment says "make my daddy's first child be me", or "make my prev
// sibling's next sibling be me". That's how the tree gets built. But to deal
// with confused comments, we now have to check if daddy/prev is already pointing
// to something. If so, we will have to walk through the list to find the right place
// for the new item.
//
// We begin by sorting by date; if we can assume we are walking through the comments by date,
// the confused comments will appear in the right order.
treeifyComments: function(rootTiddler) {
// First, clear the tree data
// We sort the comments to ensure "confused" comments
var comments = cmacro.findCommentsFromRoot(rootTiddler).sort(function(a,b) {
return a.modified > b.modified;
});
var nodes=comments.concat(rootTiddler);
for (var i=0; i<nodes.length; i++) {
delete nodes[i]["firstChild"];
delete nodes[i]["next"];
}
// Now walk through each comment
cmacro.forEach(comments, function(comment) {
var prev = comment.fields.prev;
var daddy = comment.fields.daddy;
if (prev) {
var prevTiddler = store.getTiddler(prev);
if (prevTiddler.next) {
for (var lastChild=prevTiddler.next; lastChild.next; lastChild=lastChild.next)
;
lastChild.next = comment;
// } else {
} else {
prevTiddler.next = comment;
}
} else {
var daddyTiddler = store.getTiddler(daddy);
if (daddyTiddler.firstChild) {
for (var lastChild=daddyTiddler.firstChild; lastChild.next; lastChild=lastChild.next)
;
lastChild.next = comment;
} else {
daddyTiddler.firstChild = comment;
}
}
});
for (var i=0; i<comments.length; i++) {
var c=comments.sort()[i];
}
},
logComments: function(comments) {
for (var i=0; i<comments.length; i++) {
var comment = comments[i];
}
},
findCommentsFromRoot: function(rootTiddler) {
var comments = [];
store.forEachTiddler(function(title,tiddler) {
if (tiddler.fields.root==rootTiddler.title) comments.push(tiddler);
});
return comments;
},
findChildren: function(daddyTiddler) {
var comments = [];
store.forEachTiddler(function(title,tiddler) {
if (tiddler.fields.daddy==daddyTiddler.title) comments.push(tiddler);
});
return comments;
},
buildCommentEl: function(daddyCommentsEl, comment, macroParams) {
// COMMENT ELEMENT
var commentEl = document.createElement("div");
commentEl.className = "comment";
// HEADING <- METAINFO AND DELETE
var headingEl = createTiddlyElement(commentEl, "div", null, "heading");
var metaInfoEl = createTiddlyElement(headingEl, "div", null, "commentTitle", comment.modifier + '@' + comment.modified.formatString(getParam(macroParams,"dateFormat") || "DDD, MMM DDth, YYYY hh12:0mm:0ss am"));
metaInfoEl.onclick = function() {
// story.closeAllTiddlers();
story.displayTiddler("top", comment.title, null, true);
// document.location.hash = "#" + comment.title;
};
var deleteEl = createTiddlyElement(headingEl, "div", null, "deleteComment", "X");
deleteEl.onclick = function() {
if (true || confirm("Delete this comment and all of its replies?")) {
cmacro.deleteTiddlerAndDescendents(comment);
commentEl.parentNode.removeChild(commentEl);
}
};
// TEXT
commentEl.text = createTiddlyElement(commentEl, "div", null, "commentText");
wikify(comment.text, commentEl.text);
// REPLY LINK
if (cmacro.editable(macroParams)) {
var replyLinkZone = createTiddlyElement(commentEl, "div", null, "replyLinkZone");
var replyLink = createTiddlyElement(replyLinkZone, "span", null, "replyLink", "reply to this comment");
replyLink.onclick = function() { cmacro.openReplyLink(comment, commentEl, replyLink, macroParams); };
}
// var clearance = createTiddlyElement(commentEl, "clearance", null, "clearance");
// clearance.innerHTML = " ";
// COMMENTS AREA
commentEl.commentsEl = createTiddlyElement(commentEl, "div", null, "comments");
// RETURN
return commentEl;
},
openReplyLink: function(commentTiddler, commentEl, replyLink, macroParams) {
if (commentEl.replyEl) {
commentEl.replyEl.style.display = "block";
return;
}
commentEl.replyEl = document.createElement("div");
commentEl.replyEl.className = "reply";
replyLink.style.display = "none";
var newReplyHeading = createTiddlyElement(commentEl.replyEl, "div", null, "newReply");
createTiddlyElement(newReplyHeading, "div", null, "newReplyLabel", "New Reply:");
var closeNewReply = createTiddlyElement(newReplyHeading, "div", null, "closeNewReply", "close");
closeNewReply.onclick = function() {
commentEl.replyEl.style.display = "none";
replyLink.style.display = "block";
};
cmacro.forceLoginIfRequired(params, commentEl.replyEl, function() {
var replyText = cmacro.makeTextArea(commentEl.replyEl, macroParams);
var submitReply = createTiddlyButton(commentEl.replyEl, "Reply", null, function() {
var newComment = cmacro.createComment(replyText.value, commentTiddler, macroParams);
replyText.value = "";
closeNewReply.onclick();
cmacro.refreshComments(commentEl.commentsEl, newComment, macroParams);
});
});
commentEl.insertBefore(commentEl.replyEl, commentEl.commentsEl);
},
//################################################################################
//# RELATIONSHIP MANAGEMENT
//#
//# Children are held in a singly linked list structure.
//#
//# The root tiddler (containing comments macro) and all of its comments have
//# one or more of the following custom fields:
//# - daddy: title of parent tiddler ("parent" is already used in DOM, hence "daddy")
//# - firstchild: title of first child
//# - nextchild: title of next child in the list (ie its sibling). New comments are always
//# appended to the list of siblings at the end, if it exists.
//#
//# Iff daddy is undefined, this is the root in the hierarchy (ie it's the thing that the
//# comments are about)
//# Iff firstchild is undefined, this tiddler has no children
//# Iff nextchild is undefined, this tiddler is the most
//#
//# Incidentally, the only redundancy with this structure is with "daddy" field. This field exists only
//# to give the comment some context in isolation. It's redundant as it could be derived
//# from inspecting all tiddlers' firstchild and nextchild properties. However,
//# that would be exceedingly slow, especially where the tiddlers live on a server.
//#
//################################################################################
createComment: function(text, daddy, macroParams) {
var rootTitle = daddy.fields.root ? daddy.fields.root : daddy.title;
// second case is the situation where daddy *is* root
var newComment = cmacro.createCommentTiddler(macroParams, rootTitle);
var fieldsParam = getParam(macroParams, "fields") || "";
var fields = fieldsParam.decodeHashMap();
var inheritedFields = (getParam(macroParams, "inheritedFields") || "").split(",");
cmacro.forEach(inheritedFields, function(field) {
if (field!="") fields[field] = daddy.fields[field];
});
var tagsParam = getParam(macroParams, "tags") || "comment";
var now = new Date();
newComment.set(null, text, config.options.txtUserName, now, tagsParam.split(","), now, fields);
var youngestSibling = cmacro.findYoungestChild(daddy)
if (youngestSibling) newComment.fields.prev = youngestSibling.title;
newComment.fields.daddy = daddy.title;
newComment.fields.root = rootTitle;
cmacro.saveTiddler(newComment.title);
autoSaveChanges(false);
return newComment;
},
findYoungestChild: function(daddy) {
var siblingCount = 0;
var elderSiblings = cmacro.mapize(cmacro.selectTiddlers(function(tiddler) {
isChild = (tiddler.fields.daddy==daddy.title);
if (isChild) siblingCount++;
return isChild;
}));
if (!siblingCount) return null;
// Find the only sibling that doesn't have a prev pointing at it
var youngestSiblings = cmacro.clone(elderSiblings) // as a starting point
cmacro.forEachMap(elderSiblings, function(tiddler) {
delete youngestSiblings[tiddler.fields.prev];
});
for (title in youngestSiblings) { return youngestSiblings[title]; }
},
// The recursive delete is run by a separate function (nested inside
// this one, for encapsulation purposes).
deleteTiddlerAndDescendents: function(tiddler) {
function deleteRecursively(tiddler) {
for (var child = tiddler.firstChild; child; child = child.next) {
deleteRecursively(child);
}
store.removeTiddler(tiddler.title);
}
cmacro.treeifyComments(store.getTiddler(tiddler.fields.root));
// save some info prior to deleting
var prev = tiddler.fields.prev;
var next = tiddler.next;
deleteRecursively(tiddler);
// used saved info
if (next) {
next.fields.prev = prev;
cmacro.saveTiddler(next.title);
}
autoSaveChanges(false);
},
//##############################################################################
//# COLLECTION CLOSURES
//##############################################################################
forEach: function(list, visitor) { for (var i=0; i<list.length; i++) visitor(list[i]); },
forEachMap: function(map, visitor) { for (var key in map) visitor(map[key]); },
select: function(list, selector) {
var selection = [];
cmacro.forEach(list, function(currentItem) {
if (selector(currentItem)) { selection.push(currentItem); }
});
return selection;
},
selectTiddlers: function(selector) {
var tiddlers = [];
store.forEachTiddler(function(title, tiddler) {
var wanted = selector(tiddler);
if (wanted) tiddlers.push(tiddler);
});
return tiddlers;
},
map: function(list, mapper) {
var mapped = [];
cmacro.forEach(list, function(currentItem) { mapped.push(mapper(currentItem)); });
return mapped;
},
remove: function(list, unwantedItem) {
return cmacro.select(list,
function(currentItem) { return currentItem!=unwantedItem; });
},
mapize: function(tiddlerList) {
var map = {};
cmacro.forEach(tiddlerList, function(tiddler) { map[tiddler.title] = tiddler; });
return map;
},
clone: function(map) { return merge({}, map); },
//##############################################################################
//# PARAMS
//##############################################################################
editable: function(params) {
var editable = getParam(params, "editable");
return (!editable || editable!="false");
},
needsLogin: function(params) {
var loginCheck = getParam(params, "loginCheck");
return loginCheck && !window[loginCheck]();
},
forceLoginIfRequired: function(params, loginPromptContainer, authenticatedBlock) {
if (cmacro.needsLogin(params)) wikify("<<"+getParam(macroParams, "loginPrompt")+">>", loginPromptContainer);
else authenticatedBlock();
},
//##############################################################################
//# GENERAL UTILS
//##############################################################################
mergeReadOnly: function(first, second) {
var merged = {};
for (var field in first) { merged[field] = first[field]; }
for (var field in second) { merged[field] = second[field]; }
return merged;
},
// callers may replace this with their own ID generation algorithm
createCommentTiddler: function(macroParams, rootTitle) {
// var titleFormat = getParam(macroParams, "titleFormat") || "%root%Comment";
var prefix = rootTitle+"Comment"; // was "_comment"
if (!store.createGuidTiddler) return store.createTiddler(prefix+((new Date()).getTime()));
return store.createGuidTiddler(prefix);
},
saveTiddler: function(tiddler) {
var tiddler = (typeof(tiddler)=="string") ? store.getTiddler(tiddler) : tiddler;
store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, cmacro.mergeReadOnly(config.defaultCustomFields, tiddler.fields), false, tiddler.created)
},
log: function() { if (console && console.firebug) console.log.apply(console, arguments); },
assert: function() { if (console && console.firebug) console.assert.apply(console, arguments); },
//##############################################################################
//# TIDDLYWIKI UTILS
//##############################################################################
copyFields: function(fromTiddler, toTiddler, field1, field2, fieldN) {
for (var i=2; i<arguments.length; i++) {
fieldKey = arguments[i];
if (fromTiddler.fields[fieldKey]) toTiddler.fields[fieldKey] = fromTiddler.fields[fieldKey];
}
}
}
config.macros.commentsCount = {
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var count = 0;
if (tiddler && store.getTiddler(tiddler.title)) {
var rootTiddler = paramString.length ? paramString : tiddler.title;
count = config.macros.comments.findCommentsFromRoot(store.getTiddler(rootTiddler)).length;
}
createTiddlyText(place, count);
}
},
config.macros.commentBreadcrumb = {
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if (!tiddler.fields.root) return;
var rootLink = createTiddlyElement(place, "span", null, null);
createTiddlyLink(rootLink, tiddler.fields.root, true);
var rootIsParent = tiddler.fields.daddy==tiddler.fields.root;
var rootIsGrandparent = (store.getTiddler(tiddler.fields.daddy)).fields.daddy==tiddler.fields.root;
if (!rootIsParent) {
if (!rootIsGrandparent) createTiddlyElement(place, "span", null, null, " > ... ");
createTiddlyElement(place, "span", null, null, " > ");
var daddyLink = createTiddlyElement(place, "span", null, null);
createTiddlyLink(daddyLink, tiddler.fields.daddy, true);
}
createTiddlyElement(place, "span", null, null, " > ");
// place.appendChild(createTiddlyLink(tiddler.fields.root));
}
}
config.macros.tiddlyWebComments = {};
config.macros.tiddlyWebComments.handler =
function(place,macroName,params,wikifier,paramString,tiddler) {
paramString = "fields:'server.workspace:bags/comments' inheritedFields:'server.host,server.type'";
config.macros.comments.handler(place,macroName,params,wikifier, paramString,tiddler);
};
function log() { if (console && console.firebug) console.log.apply(console, arguments); }
})(version.extensions.CommentsPlugin);
//################################################################################
//# CUSTOM STYLESHEET
//################################################################################
/***
!StyleSheet
.comments h1 { margin-bottom: 0; padding-bottom: 0; }
.comments { padding: 0; }
.comment .comments { margin-left: 1em; }
.comment { padding: 0; margin: 1em 0 0; }
.comment .comment { margin 0; }
.comment .toolbar .button { border: 0; color: #9a4; }
.comment .heading { background: [[ColorPalette::PrimaryPale]]; color: [[ColorPalette::PrimaryDark]]; border-bottom: 1px solid [[ColorPalette::PrimaryLight]]; border-right: 1px solid [[ColorPalette::PrimaryLight]]; padding: 0.5em; height: 1.3em; }
.commentTitle { float: left; }
.commentTitle:hover { text-decoration: underline; cursor: pointer; }
.commentText { clear: both; padding: 1em 1em; }
.deleteComment { float: right; cursor: pointer; text-decoration:underline; color:[[ColorPalette::SecondaryDark]]; padding-right: 0.3em; }
.comment .reply { margin-left: 1em; }
.comment .replyLink { color:[[ColorPalette::SecondaryDark]]; font-style: italic;
cursor: pointer; text-decoration: underline; margin: 0 1em; }
.comment .created { }
.comment .newReply { color:[[ColorPalette::SecondaryDark]]; margin-top: 1em; }
.newReplyLabel { float: left; }
.closeNewReply { cursor: pointer; float: right; text-decoration: underline; }
.comments textarea { width: 100%; padding: 0.3em; margin-bottom: 0.6em; }
.newCommentArea { margin-top: 0.5em; }
.clearance { clear: both; }
.expander { cursor: pointer; }
!(end of StyleSheet)
***/
config.macros.comments.init();
} // end of 'install only once'
/*}}}*/
// function log() { if (console && console.firebug) console.log.apply(console, arguments); }
The TiddlyWeb HTTP API provides access to the resources stored by a TiddlyWeb server.
[[HTTP API Documentation|http://tiddlyweb.peermore.com/wiki/#%5B%5BHTTP%20API%5D%5D]]
/***
|''Name''|ServerSideSavingPlugin|
|''Description''|server-side saving|
|''Author''|FND|
|''Version''|0.6.5|
|''Status''|stable|
|''Source''|http://svn.tiddlywiki.org/Trunk/association/plugins/ServerSideSavingPlugin.js|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.5.3|
|''Keywords''|serverSide|
!Notes
This plugin relies on a dedicated adaptor to be present.
The specific nature of this plugin depends on the respective server.
!Revision History
!!v0.1 (2008-11-24)
* initial release
!!v0.2 (2008-12-01)
* added support for local saving
!!v0.3 (2008-12-03)
* added Save to Web macro for manual synchronization
!!v0.4 (2009-01-15)
* removed ServerConfig dependency by detecting server type from the respective tiddlers
!!v0.5 (2009-08-25)
* raised CoreVersion to 2.5.3 to take advantage of core fixes
!!v0.6 (2010-04-21)
* added notification about cross-domain restrictions to ImportTiddlers
!To Do
* conflict detection/resolution
* rename to ServerLinkPlugin?
* document deletion/renaming convention
!Code
***/
//{{{
(function($) {
readOnly = false; //# enable editing over HTTP
var plugin = config.extensions.ServerSideSavingPlugin = {};
plugin.locale = {
saved: "%0 saved successfully",
saveError: "Error saving %0: %1",
saveConflict: "Error saving %0: edit conflict",
deleted: "Removed %0",
deleteError: "Error removing %0: %1",
deleteLocalError: "Error removing %0 locally",
removedNotice: "This tiddler has been deleted.",
connectionError: "connection could not be established",
hostError: "Unable to import from this location due to cross-domain restrictions."
};
plugin.sync = function(tiddlers) {
tiddlers = tiddlers && tiddlers[0] ? tiddlers : store.getTiddlers();
$.each(tiddlers, function(i, tiddler) {
var changecount = parseInt(tiddler.fields.changecount, 10);
if(tiddler.fields.deleted === "true" && changecount === 1) {
plugin.removeTiddler(tiddler);
} else if(tiddler.isTouched() && !tiddler.doNotSave() &&
tiddler.getServerType() && tiddler.fields["server.host"]) { // XXX: server.host could be empty string
delete tiddler.fields.deleted;
plugin.saveTiddler(tiddler);
}
});
};
plugin.saveTiddler = function(tiddler) {
try {
var adaptor = this.getTiddlerServerAdaptor(tiddler);
} catch(ex) {
return false;
}
var context = {
tiddler: tiddler,
changecount: tiddler.fields.changecount,
workspace: tiddler.fields["server.workspace"]
};
var serverTitle = tiddler.fields["server.title"]; // indicates renames
if(!serverTitle) {
tiddler.fields["server.title"] = tiddler.title;
} else if(tiddler.title != serverTitle) {
return adaptor.moveTiddler({ title: serverTitle },
{ title: tiddler.title }, context, null, this.saveTiddlerCallback);
}
var req = adaptor.putTiddler(tiddler, context, {}, this.saveTiddlerCallback);
return req ? tiddler : false;
};
plugin.saveTiddlerCallback = function(context, userParams) {
var tiddler = context.tiddler;
if(context.status) {
if(tiddler.fields.changecount == context.changecount) { //# check for changes since save was triggered
tiddler.clearChangeCount();
} else if(tiddler.fields.changecount > 0) {
tiddler.fields.changecount -= context.changecount;
}
plugin.reportSuccess("saved", tiddler);
store.setDirty(false);
} else {
if(context.httpStatus == 412) {
plugin.reportFailure("saveConflict", tiddler);
} else {
plugin.reportFailure("saveError", tiddler, context);
}
}
};
plugin.removeTiddler = function(tiddler) {
try {
var adaptor = this.getTiddlerServerAdaptor(tiddler);
} catch(ex) {
return false;
}
var context = {
host: tiddler.fields["server.host"],
workspace: tiddler.fields["server.workspace"],
tiddler: tiddler
};
var req = adaptor.deleteTiddler(tiddler, context, {}, this.removeTiddlerCallback);
return req ? tiddler : false;
};
plugin.removeTiddlerCallback = function(context, userParams) {
var tiddler = context.tiddler;
if(context.status) {
if(tiddler.fields.deleted === "true") {
store.deleteTiddler(tiddler.title);
} else {
plugin.reportFailure("deleteLocalError", tiddler);
}
plugin.reportSuccess("deleted", tiddler);
store.setDirty(false);
} else {
plugin.reportFailure("deleteError", tiddler, context);
}
};
plugin.getTiddlerServerAdaptor = function(tiddler) { // XXX: rename?
var type = tiddler.fields["server.type"] || config.defaultCustomFields["server.type"];
return new config.adaptors[type]();
};
plugin.reportSuccess = function(msg, tiddler) {
displayMessage(plugin.locale[msg].format([tiddler.title]));
};
plugin.reportFailure = function(msg, tiddler, context) {
var desc = (context && context.httpStatus) ? context.statusText :
plugin.locale.connectionError;
displayMessage(plugin.locale[msg].format([tiddler.title, desc]));
};
config.macros.saveToWeb = { // XXX: hijack existing sync macro?
locale: { // TODO: merge with plugin.locale?
btnLabel: "save to web",
btnTooltip: "synchronize changes",
btnAccessKey: null
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
createTiddlyButton(place, this.locale.btnLabel, this.locale.btnTooltip,
plugin.sync, null, null, this.locale.btnAccessKey);
}
};
// hijack saveChanges to trigger remote saving
var _saveChanges = saveChanges;
saveChanges = function(onlyIfDirty, tiddlers) {
if(window.location.protocol == "file:") {
_saveChanges.apply(this, arguments);
} else {
plugin.sync(tiddlers);
}
};
// override removeTiddler to flag tiddler as deleted -- XXX: use hijack to preserve compatibility?
TiddlyWiki.prototype.removeTiddler = function(title) { // XXX: should override deleteTiddler instance method?
var tiddler = this.fetchTiddler(title);
if(tiddler) {
tiddler.tags = ["excludeLists", "excludeSearch", "excludeMissing"];
tiddler.text = plugin.locale.removedNotice;
tiddler.fields.deleted = "true"; // XXX: rename to removed/tiddlerRemoved?
tiddler.fields.changecount = "1";
this.notify(title, true);
this.setDirty(true);
}
};
// hijack ImportTiddlers wizard to handle cross-domain restrictions
var _onOpen = config.macros.importTiddlers.onOpen;
config.macros.importTiddlers.onOpen = function(ev) {
var btn = $(resolveTarget(ev));
var url = btn.closest(".wizard").find("input[name=txtPath]").val();
if(window.location.protocol != "file:" && url.indexOf("://") != -1) {
var host = url.split("/")[2];
var macro = config.macros.importTiddlers;
if(host != window.location.host) {
btn.text(macro.cancelLabel).attr("title", macro.cancelPrompt);
btn[0].onclick = macro.onCancel;
$('<span class="status" />').text(plugin.locale.hostError).insertAfter(btn);
return false;
}
}
return _onOpen.apply(this, arguments);
};
})(jQuery);
//}}}
/***
|''Name''|RevisionsCommandPlugin|
|''Description''|provides access to tiddler revisions|
|''Author''|FND|
|''Contributors''|Martin Budden|
|''Version''|0.3.3|
|''Status''|@@beta@@|
|''Source''|http://svn.tiddlywiki.org/Trunk/association/plugins/RevisionsCommandPlugin.js|
|''CodeRepository''|http://svn.tiddlywiki.org/Trunk/association/plugins/|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Keywords''|serverSide|
!Usage
Extend [[ToolbarCommands]] with {{{revisions}}}.
!Revision History
!!v0.1 (2009-07-23)
* initial release (renamed from experimental ServerCommandsPlugin)
!!v0.2 (2010-03-04)
* suppressed wikification in diff view
!!v0.3 (2010-04-07)
* restored wikification in diff view
* added link to side-by-side diff view
!To Do
* strip server.* fields from revision tiddlers
* resolve naming conflicts
* i18n, l10n
* code sanitizing
* documentation
!Code
***/
//{{{
(function($) {
jQuery.twStylesheet(".diff { white-space: pre, font-family: monospace }",
{ id: "diff" });
var cmd = config.commands.revisions = {
type: "popup",
hideShadow: true,
text: "revisions",
tooltip: "display tiddler revisions",
revTooltip: "", // TODO: populate dynamically?
loadLabel: "loading...",
loadTooltip: "loading revision list",
selectLabel: "select",
selectTooltip: "select revision for comparison",
selectedLabel: "selected",
compareLabel: "compare",
linkLabel: "side-by-side view",
revSuffix: " [rev. #%0]",
diffSuffix: " [diff: #%0 #%1]",
dateFormat: "YYYY-0MM-0DD 0hh:0mm",
listError: "revisions could not be retrieved",
handlePopup: function(popup, title) {
title = this.stripSuffix("rev", title);
title = this.stripSuffix("diff", title);
var tiddler = store.getTiddler(title);
var type = _getField("server.type", tiddler);
var adaptor = new config.adaptors[type]();
var limit = null; // TODO: customizable
var context = {
host: _getField("server.host", tiddler),
workspace: _getField("server.workspace", tiddler)
};
var loading = createTiddlyButton(popup, cmd.loadLabel, cmd.loadTooltip);
var params = { popup: popup, loading: loading, origin: title };
adaptor.getTiddlerRevisionList(title, limit, context, params, this.displayRevisions);
},
displayRevisions: function(context, userParams) {
removeNode(userParams.loading);
if(context.status) {
var callback = function(ev) {
var e = ev || window.event;
var revision = resolveTarget(e).getAttribute("revision");
context.adaptor.getTiddlerRevision(tiddler.title, revision, context,
userParams, cmd.displayTiddlerRevision);
};
var table = createTiddlyElement(userParams.popup, "table");
for(var i = 0; i < context.revisions.length; i++) {
var tiddler = context.revisions[i];
var row = createTiddlyElement(table, "tr");
var timestamp = tiddler.modified.formatString(cmd.dateFormat);
var revision = tiddler.fields["server.page.revision"];
var cell = createTiddlyElement(row, "td");
createTiddlyButton(cell, timestamp, cmd.revTooltip, callback, null,
null, null, { revision: revision });
cell = createTiddlyElement(row, "td", null, null, tiddler.modifier);
cell = createTiddlyElement(row, "td");
createTiddlyButton(cell, cmd.selectLabel, cmd.selectTooltip,
cmd.revisionSelected, null, null, null,
{ index:i, revision: revision, col: 2 });
cmd.context = context; // XXX: unsafe (singleton)!?
}
} else {
$("<li />").text(cmd.listError).appendTo(userParams.popup);
}
},
revisionSelected: function(ev) {
var e = ev || window.event;
e.cancelBubble = true;
if(e.stopPropagation) {
e.stopPropagation();
}
var n = resolveTarget(e);
var index = n.getAttribute("index");
var col = n.getAttribute("col");
while(!index || !col) {
n = n.parentNode;
index = n.getAttribute("index");
col = n.getAttribute("col");
}
cmd.revision = n.getAttribute("revision");
var table = n.parentNode.parentNode.parentNode;
var rows = table.childNodes;
for(var i = 0; i < rows.length; i++) {
var c = rows[i].childNodes[col].firstChild;
if(i == index) {
if(c.textContent) {
c.textContent = cmd.selectedLabel;
} else {
c.text = cmd.selectedLabel;
}
} else {
if(c.textContent) {
c.textContent = cmd.compareLabel;
} else {
c.text = cmd.compareLabel;
}
c.onclick = cmd.compareSelected;
}
}
},
compareSelected: function(ev) {
var e = ev || window.event;
var n = resolveTarget(e);
var context = cmd.context;
context.rev1 = n.getAttribute("revision");
context.rev2 = cmd.revision;
context.tiddler = context.revisions[n.getAttribute("index")];
context.format = "unified";
context.adaptor.getTiddlerDiff(context.tiddler.title, context,
context.userParams, cmd.displayTiddlerDiffs);
},
displayTiddlerDiffs: function(context, userParams) {
var tiddler = context.tiddler;
tiddler.title += cmd.diffSuffix.format([context.rev1, context.rev2]);
tiddler.text = "{{diff{\n" + context.diff + "\n}}}";
tiddler.tags = ["diff"];
tiddler.fields.doNotSave = "true"; // XXX: correct?
if(!store.getTiddler(tiddler.title)) {
store.addTiddler(tiddler);
}
var src = story.getTiddler(userParams.origin);
var tiddlerEl = story.displayTiddler(src, tiddler);
var uri = context.uri.replace("format=unified", "format=horizontal");
var link = $('<a target="_blank" />').attr("href", uri).text(cmd.linkLabel);
$(".viewer", tiddlerEl).prepend(link);
},
displayTiddlerRevision: function(context, userParams) {
var tiddler = context.tiddler;
tiddler.title += cmd.revSuffix.format([tiddler.fields["server.page.revision"]]);
tiddler.fields.doNotSave = "true"; // XXX: correct?
if(!store.getTiddler(tiddler.title)) {
store.addTiddler(tiddler);
}
var src = story.getTiddler(userParams.origin);
story.displayTiddler(src, tiddler);
},
stripSuffix: function(type, title) {
var str = cmd[type + "Suffix"];
var i = str.indexOf("%0");
i = title.indexOf(str.substr(0, i));
if(i != -1) {
title = title.substr(0, i);
}
return title;
}
};
var _getField = function(name, tiddler) {
return tiddler.fields[name] || config.defaultCustomFields[name];
};
})(jQuery);
//}}}
<<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>
A Bag is the TiddlyWeb name for a collection of uniquely named [[tiddlers|tiddler]]. Each bag has a [[policy]] for access control. A bag may also have a description field, typically containing text stating the purpose of the bag. A TiddlyHoster user may create as many bags as they like.
/***
|''Name''|DiffFormatter|
|''Description''|highlighting of text comparisons|
|''Author''|FND|
|''Version''|0.9.0|
|''Status''|beta|
|''Source''|http://svn.tiddlywiki.org/Trunk/contributors/FND/formatters/DiffFormatter.js|
|''CodeRepository''|http://svn.tiddlywiki.org/Trunk/contributors/FND/|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''Keywords''|formatting|
!Description
Highlights changes in a unified [[diff|http://en.wikipedia.org/wiki/Diff#Unified_format]].
!Notes
Based on Martin Budden's [[DiffFormatterPlugin|http://svn.tiddlywiki.org/Trunk/contributors/MartinBudden/formatters/DiffFormatterPlugin.js]].
!Usage
The formatter is applied to blocks wrapped in <html><code>{{{diff{..}}}</code></html> within tiddlers tagged with "diff".
!Revision History
!!v0.9 (2010-04-07)
* initial release; fork of DiffFormatterPlugin
!StyleSheet
.diff { white-space: pre; font-family: monospace; }
.diff ins, .diff del { display: block; text-decoration: none; }
.diff ins { background-color: #dfd; }
.diff del { background-color: #fdd; }
.diff .highlight { background-color: [[ColorPalette::SecondaryPale]]; }
!Code
***/
//{{{
(function() {
config.shadowTiddlers.StyleSheetDiffFormatter = store.getTiddlerText(tiddler.title + "##StyleSheet");
store.addNotification("StyleSheetDiffFormatter", refreshStyles);
var formatters = [{
name: "diffWrapper",
match: "^\\{\\{diff\\{\n", // XXX: suboptimal
termRegExp: /(.*\}\}\})$/mg,
handler: function(w) {
var el = createTiddlyElement(w.output, "div", null, "diff");
w.subWikifyTerm(el, this.termRegExp);
}
}, {
name: "diffRange",
match: "^(?:@@|[+\\-]{3}) ",
lookaheadRegExp: /^(?:@@|[+\-]{3}) .*\n/mg,
handler: function(w) {
createTiddlyElement(w.output, "div", null, "highlight").
innerHTML = "…";
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
}, {
name: "diffAdded",
match: "^\\+",
termRegExp: /(\n)/mg,
handler: function(w) {
var el = createTiddlyElement(w.output, "ins", null, "added");
w.subWikifyTerm(el, this.termRegExp);
}
}, {
name: "diffRemoved",
match: "^-",
termRegExp: /(\n)/mg,
handler: function(w) {
var el = createTiddlyElement(w.output, "del", null, "removed");
w.subWikifyTerm(el, this.termRegExp);
}
}
];
config.parsers.diffFormatter = new Formatter(formatters);
config.parsers.diffFormatter.format = "diff";
config.parsers.diffFormatter.formatTag = "diff";
})();
//}}}
/***
|''Name''|BinaryTiddlersPlugin|
|''Description''|renders base64-encoded binary tiddlers as images or links|
|''Author''|FND|
|''Version''|0.3.2|
|''Status''|@@beta@@|
|''Source''|http://svn.tiddlywiki.org/Trunk/association/plugins/BinaryTiddlersPlugin.js|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.5|
!Code
***/
//{{{
(function($) {
var ctfield = "server.content-type";
var plugin = config.extensions.BinaryTiddlersPlugin = {
isWikiText: function(tiddler) {
var ctype = tiddler.fields[ctfield];
if(ctype) {
return !this.isBinary(tiddler) && !this.isTextual(ctype);
} else {
return true;
}
},
// NB: pseudo-binaries are considered non-binary here
isBinary: function(tiddler) {
var ctype = tiddler.fields[ctfield];
return ctype ? !this.isTextual(ctype) : false;
},
isTextual: function(ctype) {
return ctype.indexOf("text/") == 0
|| this.endsWith(ctype, "+xml")
|| ctype == 'application/json'
|| ctype == 'application/javascript';
},
endsWith: function(str, suffix) {
return str.length >= suffix.length &&
str.substr(str.length - suffix.length) == suffix;
},
isLink: function(tiddler) {
return this.isBinary(tiddler) && tiddler.text.indexOf("<html>") != -1
}
};
// Disable edit for linked tiddlers (for now)
// This will be changed to a GET then PUT
config.commands.editTiddler.isEnabled = function(tiddler) {
var existingTest = config.commands.editTiddler.isEnabled;
if (existingTest) {
return existingTest && !plugin.isLink(tiddler);
} else {
return !plugin.isLink(tiddler);
}
};
// hijack text viewer to add special handling for binary tiddlers
var _view = config.macros.view.views.wikified;
config.macros.view.views.wikified = function(value, place, params, wikifier,
paramString, tiddler) {
var ctype = tiddler.fields["server.content-type"];
if(params[0] == "text" && ctype && !tiddler.tags.contains("systemConfig") && !plugin.isLink(tiddler)) {
var el;
if(plugin.isBinary(tiddler)) {
var uri = "data:%0;base64,%1".format([ctype, tiddler.text]); // TODO: fallback for legacy browsers
if(ctype.indexOf("image/") == 0) {
el = $("<img />").attr("alt", tiddler.title).attr("src", uri);
} else {
el = $("<a />").attr("href", uri).text(tiddler.title);
}
} else {
el = $("<pre />").text(tiddler.text);
}
el.appendTo(place);
} else {
_view.apply(this, arguments);
}
};
// hijack edit macro to disable editing of binary tiddlers' body
var _editHandler = config.macros.edit.handler;
config.macros.edit.handler = function(place, macroName, params, wikifier,
paramString, tiddler) {
if(params[0] == "text" && plugin.isBinary(tiddler)) {
return false;
} else {
_editHandler.apply(this, arguments);
}
};
// hijack autoLinkWikiWords to ignore binary tiddlers
var _autoLink = Tiddler.prototype.autoLinkWikiWords;
Tiddler.prototype.autoLinkWikiWords = function() {
return plugin.isWikiText(this) ? _autoLink.apply(this, arguments) : false;
};
})(jQuery);
//}}}