Welcome to TiddlyWiki created by Jeremy Ruston; Copyright © 2004-2007 Jeremy Ruston, Copyright © 2007-2011 UnaMesa Association
/***
|''Name''|RefreshTiddlerCommand|
|''Version''|0.3.0|
***/
//{{{
(function($) {
var cmd = config.commands.refreshTiddler = {
text: "refresh",
locale: {
refreshing: "Refreshing tiddler..."
},
tooltip: "refresh this tiddler to be the one on the server",
handler: function(ev, src, title) {
var tiddler = store.getTiddler(title);
if(!tiddler) {
tiddler = new Tiddler(title);
merge(tiddler.fields, config.defaultCustomFields);
}
$(story.getTiddler(title)).find(".viewer").
empty().text(cmd.locale.refreshing);
var dirtyStatus = store.isDirty();
story.loadMissingTiddler(title, {
"server.workspace": tiddler.fields["server.recipe"] ? "recipes/" + tiddler.fields["server.recipe"] :
tiddler.fields["server.workspace"] || "bags/"+tiddler.fields["server.bag"],
"server.host": tiddler.fields["server.host"],
"server.type": tiddler.fields["server.type"]
}, function() {
store.setDirty(dirtyStatus);
});
}
};
})(jQuery);
//}}}
<!--{{{-->
<div class='toolbar'
macro='toolbar [[ToolbarCommands::ViewToolbar]] icons:yes height:16 width:16 more:popup'>
</div>
<div class='heading'>
<span class='spaceSiteIcon'
macro='tiddlerOrigin label:no spaceLink:yes height:48 width:48 preserveAspectRatio:yes'>
</span>
<span class="titleBar">
<div class='title' macro='view title text'></div>
<span class="subtitle" macro='viewRevisions page:5'>
last modified on
<span macro="view modified date"></span>
</span>
<div macro="view title replyLink"></div>
</span>
<span class='followPlaceHolder' macro='followTiddlers'></span>
<span class='modifierIcon'
macro='view modifier SiteIcon label:no spaceLink:yes height:48 width:48 preserveAspectRatio:yes'>
</span>
<div class='tagClear'></div>
</div>
<div class='content'>
<div class='viewer' macro='view text wikified'></div>
</div>
<div class='tagInfo'>
<div class='tidTags' macro='tags'></div>
<div class='tagging' macro='tagging'></div>
</div>
<!--}}}-->
A [[SiteIcon|SiteIcon tiddler]]@glossary helps provide some identity to your space. Ideally it'd be a square and a minimum of 48*48 pixels size. You can upload your site icon using the uploader below.
<<binaryUploadPublic title:SiteIcon>>
<html><hr><html>
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
<!DOCTYPE html>
<html manifest="/bags/common/tiddlers/takenote_manifest.appcache">
<head>
<title>takenote</title>
<link rel="stylesheet" href="/bags/common/tiddlers/notabene.css" />
<link rel="apple-touch-icon" href="/bags/common/tiddlers/touchicon_takenote.png"/>
<link rel="apple-touch-icon-precomposed" href="/bags/common/tiddlers/touchicon_takenote.png"/>
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=0,initial-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes" />
</head>
<body>
<ul id="backstage">
<!-- no not add a newline between li elements or you will get a margin with inline blocks -->
<li><a href="/dashboard">dashboard</a></li><li><a href="/takenote">takenote</a></li>
<li><a class='connectionStatus'></a></li>
</ul>
<div class="takenotecontainer">
<div class='messageArea'></div>
<div id="note">
<div class="toolbar">
<a id="cancelnote" title="cancel this note">cancel</a>
<a id="deletenote" title="delete this note">delete</a>
<a id="newnote" title="complete this note">complete</a>
</div>
<div class="note_title_container">
<textarea class="note_title" placeholder="Note Title"></textarea>
</div>
<div id="notebody">
<span class="notedate"></span>
<textarea class="note_text" placeholder="Write here..."></textarea>
</div>
<div id="tips">
<div>Tips for cleverer notes:
''<span class="boldTip">bold</span>'' //<span class="italicTip">italic</span>// _<span class="underlineTip">underline</span>_ #tag</div>
<div>[[<a href="http://tiddlyspace.com">Link Name</a>|http://tiddlyspace.com]]
[[<a href="/Note title">Note title</a>]] <span class="imageTip">[img[/SiteIcon<img src="/SiteIcon" />]]</span></div>
</div>
<div id="notemeta"></div>
<div style="display:none">
<a class="syncButton"></a>
</div>
</div>
</div>
<noscript>
Takenote requires javascript to work correctly. Sorry!
</noscript>
<script src="/bags/common/tiddlers/backstage.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/bookmark_bubble.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/tiddlyspace/tiddlers/chrjs" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/chrjs-store.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/jquery-json.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/notabene.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
var takenote;
takenote = notes($(".takenotecontainer")[0], {
host: "/"
});
</script>
</body>
</html>
/***
|''Name''|TiddlyFileImporter|
|''Version''|0.3.8|
|''Author''|Ben Gillies|
|''Type''|plugin|
|''Description''|Upload a TiddlyWiki file to TiddlyWeb, and import the tiddlers.|
!Usage
Upload a TiddlyWiki file to TiddlyWeb, and import the tiddlers.
!Requires
tiddlyweb
tiddlywebplugins.reflector
!Code
***/
//{{{
(function($){
if(!version.extensions.TiddlyFileImporter)
{ //# ensure that the plugin is only installed once
version.extensions.TiddlyFileImporter = { installed: true };
}
config.macros.fileImport = {
reflectorURI: '/reflector?csrf_token=%0',
incorrectTypeError: 'Incorrect File Type. You must upload a TiddlyWiki',
uploadLabel: 'Upload',
uploadLabelPrompt: 'Import tiddlers from this TiddlyWiki',
step1FileText: 'File:',
step1URLText: 'URL:',
step1PostText: 'In the next screen you will select the tiddlers to import.',
step1Title: 'Step 1: Pick a TiddlyWiki to import',
step1TypeChooser: 'Import From:',
step3Html: ['<input type="hidden" name="markList" />',
'<input type="hidden" checked="true" name="chkSync" />',
'<input type="hidden" name="chkSave" />',
'<input type="hidden" name="txtSaveTiddler" />'].join(),
handler: function(place, macroName, params, wikifier, paramString) {
var wizard = new Wizard();
wizard.createWizard(place, 'Import a TiddlyWiki');
this.restart(wizard);
},
restart: function(wizard) {
var me = config.macros.fileImport;
wizard.addStep(me.step1Title, ['<input type="hidden" ',
'name="markList" />'].join(""));
var markList = wizard.getElement('markList');
var uploadWrapper = document.createElement('div');
markList.parentNode.insertBefore(uploadWrapper, markList);
uploadWrapper.setAttribute('refresh', 'macro');
uploadWrapper.getAttribute('macroName', 'fileImport');
var iframeName = 'reflectorImporter' + Math.random().toString();
me.createForm(uploadWrapper, wizard, iframeName);
$(uploadWrapper).append('<p>' + me.step1PostText + '</p>');
wizard.setValue('serverType', 'tiddlyweb');
wizard.setValue('adaptor', new config.adaptors.file());
wizard.setValue('host', config.defaultCustomFields['server.host']);
wizard.setValue('context', {});
var iframe = $(['<iframe name="' + iframeName + '" ',
'style="display: none" />'].join("")).appendTo(uploadWrapper);
var onSubmit = function(ev) {
var uploadType = $('select[name=uploadtype]', wizard.formElem).val();
if (uploadType == "file") {
// set an onload ready to hijack the form
me.setOnLoad(uploadWrapper, wizard, iframe[0]);
wizard.importType = 'file';
wizard.formElem.submit();
} else {
var csrf_token = config.extensions.tiddlyspace.getCSRFToken();
$.ajax({
url: "%0/reflector?csrf_token=%1".format(
config.defaultCustomFields["server.host"], csrf_token),
type: "POST",
dataType: "text",
data: {
uri: $("input", ".importFrom", wizard.formElem).val()
},
success: function(data, txtStatus, xhr) {
wizard.POSTResponse = data;
me.importTiddlers(uploadWrapper, wizard);
},
error: function(xhr, txtStatus, error) {
displayMessage(["There was an error fetching the ",
'url: ', txtStatus].join(""));
me.restart(wizard);
}
});
return false;
}
};
wizard.setButtons([{
caption: me.uploadLabel,
tooltip: me.uploadLabelPrompt,
onClick: onSubmit
}]);
$(wizard.formElem).submit(function(ev) {
onSubmit(ev);
ev.preventDefault();
});
},
createForm: function(place, wizard, iframeName) {
var form = wizard.formElem;
var me = config.macros.fileImport;
form.action = me.reflectorURI.format(
config.extensions.tiddlyspace.getCSRFToken());
form.enctype = 'multipart/form-data';
form.encoding = 'multipart/form-data';
form.method = 'POST';
form.target = iframeName;
onSelectChange = function(e) {
var changeTo = $(this).val();
if (changeTo == "file") {
$(".importFrom").html('%0 <input type="file" name="file" />'.
format(me.step1FileText));
} else {
$(".importFrom").html('%0 <input type="text" name="uri" />'.
format(me.step1URLText));
}
};
$(place).append('<span>%0</span>'.format(me.step1TypeChooser)).
append($(['<select name="uploadtype"><option value="file" selected="selected">file',
'<option value="uri">url</select>'].join("")).change(onSelectChange)).
append('<div class="importFrom">%0<input type="file" name="file" /></div>'.
format(me.step1FileText));
},
setOnLoad: function(place, wizard, iframe) {
var me = config.macros.fileImport;
var loadHandler = function() {
me.importTiddlers.apply(this, [place, wizard, iframe]);
};
iframe.onload = loadHandler;
completeReadyStateChanges = 0;
iframe.onreadystatechange = function() {
if (++(completeReadyStateChanges) == 5) {
loadHandler();
}
};
},
importTiddlers: function(place, wizard, iframe) {
var tmpStore = new TiddlyWiki();
var POSTedWiki = "";
if (wizard.importType == "file") {
try {
POSTedWiki= iframe.contentWindow
.document.documentElement.innerHTML;
} catch(e) {
displayMessage(config.macros.fileImport.incorrectTypeError);
config.macros.fileImport.restart(wizard);
return;
}
// now we are done, so remove the iframe
$(iframe).remove();
} else {
POSTedWiki = wizard.POSTResponse;
}
tmpStore.importTiddlyWiki(POSTedWiki);
var newTiddlers = tmpStore.getTiddlers();
var workspace = config.defaultCustomFields['server.workspace'];
var context = {
status: true,
statusText: 'OK',
httpStatus: 200,
adaptor: wizard.getValue('adaptor'),
tiddlers: newTiddlers
};
context.adaptor.store = tmpStore;
wizard.setValue('context', context);
wizard.setValue('workspace', workspace);
wizard.setValue('inFileImport', true);
config.macros.importTiddlers.onGetTiddlerList(context, wizard);
}
};
var _onGetTiddler = config.macros.importTiddlers.onGetTiddler;
config.macros.importTiddlers.onGetTiddler = function(context, wizard) {
if (wizard.getValue('inFileImport')) {
var me = config.macros.importTiddlers;
if(!context.status)
displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
var tiddler = context.tiddler;
var fields = tiddler.fields;
merge(fields, config.defaultCustomFields);
fields["server.workspace"] = wizard.getValue('workspace');
delete fields['server.permissions'];
delete fields['server.bag'];
fields['server.page.revision'] = 'false';
delete fields['server.recipe'];
fields.changecount = 1;
store.suspendNotifications();
store.saveTiddler(tiddler.title, tiddler.title, tiddler.text,
tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields,
false, tiddler.created);
store.resumeNotifications();
var remainingImports = wizard.getValue("remainingImports")-1;
wizard.setValue("remainingImports",remainingImports);
if(remainingImports === 0) {
if(context.isSynchronous) {
store.notifyAll();
refreshDisplay();
}
wizard.setButtons([
{caption: me.doneLabel, tooltip: me.donePrompt, onClick: me.onClose}
],me.statusDoneImport);
autoSaveChanges();
}
} else {
_onGetTiddler.apply(this, arguments);
}
};
var _onCancel = config.macros.importTiddlers.onCancel;
config.macros.importTiddlers.onCancel = function(e)
{
var wizard = new Wizard(this);
if (!wizard.getValue('inFileImport')) {
return _onCancel.apply(this, arguments);
}
var place = wizard.clear();
config.macros.fileImport.restart(wizard);
return false;
};
var _step3Html = config.macros.importTiddlers.step3Html;
var _onGetTiddlerList = config.macros.importTiddlers.onGetTiddlerList;
config.macros.importTiddlers.onGetTiddlerList = function(context, wizard) {
var fileImport = config.macros.fileImport;
var importTiddlers = config.macros.importTiddlers;
if (wizard.getValue('inFileImport')) {
importTiddlers.step3Html = fileImport.step3Html;
} else {
importTiddlers.step3Html = _step3Html;
}
_onGetTiddlerList.apply(this, arguments);
};
})(jQuery);
//}}}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Account</title>
<link href="/bags/common/tiddlers/profile.css" type='text/css' rel='stylesheet' >
<link href="/bags/common/tiddlers/admin.css" type='text/css' rel='stylesheet' >
<link href="/bags/common/tiddlers/jquery-ui.custom.css" type='text/css' rel='stylesheet' >
<script type="text/javascript" src="/bags/common/tiddlers/backstage.js"></script>
</head>
<body>
<div id="container">
<div class="main section">
<a class="app" href="/apps">back to apps</a>
<div class="left">
<div id="siteiconArea">
<h2>Site Icon</h2>
<div>
<img id="siteicon" class="siteicon">
<form id="upload" method="POST" enctype="multipart/form-data">
<input type="hidden" name="title" value="SiteIcon" />
<input type="hidden" name="tags" value="excludeLists">
<input type="hidden" name="csrf_token" class="csrf" />
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</div>
</div>
<h2>Find Space</h2>
<form class="spaceSearch">
<input class="inputBox" type="text" placeholder="find space" />
<a href="http://docs.tiddlyspace.com/What%20is%20a%20Space%3F" class="help"
title="What is a space?">What is a space?</a>
<button>view all</button>
</form>
<div class='list-container'>
You are a member of the following spaces:
<ul class='ts-space-search'>
</ul>
</div>
<h2>Create New Space</h2>
<form class="ts-spaces">
<input class="inputBox" type="text" name="spacename" placeholder="space name"><span class="hostSuffix">.tiddlyspace.com</span>
<input type="submit" value="Create Space" />
</form>
</div>
<div class="right">
<h2>Change Password</h2>
<form class="ts-password">
<input class="inputBox" placeholder="existing password" type="password" name="password">
<input class="inputBox" placeholder="new password" type="password" name="new_password">
<input class="inputBox" placeholder="new password" type="password" name="new_password_confirm">
<input type="submit" value="Change password">
</form>
<h2>OpenID</h2>
<h3>Why OpenID?</h3>
<a href="http://openid.net/"><img src="/bags/common/tiddlers/openid.png" alt="openid" ></a><br />
Use just one username and password across hundreds of OpenID-enabled sites.<br />
It's an open standard.<br />
<a href="http://openid.net/what/">learn more</a>
<ul class="ts-identities"></ul>
<form class="ts-openid" target="_top">
<div>
Add an openid:
</div>
<input class="inputBox" type="text" name="openid" placeholder="your openid" />
<input type="submit" value="Register" />
<a href="http://openid.net/get-an-openid/" class="help"
title="What is an open id?">What is an open id?</a>
</form>
</div>
<div class="clear"></div>
</div>
</div>
<script type='text/javascript' src='/bags/common/tiddlers/jquery.js'></script>
<script type='text/javascript' src='/bags/tiddlyspace/tiddlers/chrjs'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.space'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.users'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.identities'></script>
<script type='text/javascript' src="/bags/common/tiddlers/jquery-ui.custom.js"></script>
<script type='text/javascript' src='/bags/common/tiddlers/jquery-form.js'></script>
<script type='text/javascript' src="/bags/common/tiddlers/siteiconupload.js"></script>
<script type='text/javascript' src='/bags/common/tiddlers/ts.js'></script>
<script type="text/javascript">
/*
* jQuery UI Autocomplete HTML Extension
*
* Copyright 2010, Scott González (http://scottgonzalez.com)
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* http://github.com/scottgonzalez/jquery-ui-extensions
*/
(function( $ ) {
var proto = $.ui.autocomplete.prototype,
initSource = proto._initSource;
function filter( array, term ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
return $.grep( array, function(value) {
return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
});
}
$.extend( proto, {
_initSource: function() {
if ( this.options.html && $.isArray(this.options.source) ) {
this.source = function( request, response ) {
response( filter( this.options.source, request.term ) );
};
} else {
initSource.call( this );
}
},
_renderItem: function( ul, item) {
return $( "<li></li>" )
.data( "item.autocomplete", item )
.append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
.appendTo( ul );
}
});
})( jQuery );
/***
_accounts application specific javascript
***/
var link;
ts.init(function(ts) {
if(ts.user.anon) { // redirect to homepage when user not logged in
window.location = ts.getHost();
} else if(ts.user.name === ts.currentSpace){
initSiteIconUpload(ts.user.name);
} else {
link = $("<a />").attr("href", ts.getHost(ts.user.name) + "/_account").text("Change SiteIcon");
$("#siteiconArea div").empty().append(link);
}
$(".hostSuffix").text("." + ts.getHost("").split("//")[1]);
ts.getSpaces(function(spaces) {
$("<div class='info' />").text("You have " + spaces.length + " spaces.").insertBefore($(".spaceSearch")[0]);
$("form.spaceSearch input").autocomplete({
html: true,
source: function(req, response) {
ts.getSpaces(function(spaces) {
var selected = [];
for(var i = 0; i < spaces.length; i++) {
var space = spaces[i];
if(space.name.indexOf(req.term) > -1) {
var host = ts.getHost(space.name) ;
var img = host + "/SiteIcon";
selected.push({
value: space.name,
label: '<a href="' + host + '" target="_parent" class="autocompleteLink"><img src="' + img + '" style="height:24px;width:auto;max-height:24px;max-width:24px;"/>' + space.name + '</a>'
});
}
}
response(selected);
});
},
select: function(event, ui) {
window.top.location = ts.getHost(ui.item.value);
}
});
var $ul = $('.ts-space-search');
$.each(spaces, function(i, space) {
$ul.append($('<li/>').html($('<a/>').attr('href', space.uri)
.text(space.name)));
});
$('form.spaceSearch button').click(function(ev) {
$('.list-container').slideToggle('fast');
ev.preventDefault();
return false;
});
});
});
if(window != window.top) {
$("html").addClass("iframeMode");
$("a").live("click",function(ev) {
$(ev.target).attr("target", "_parent");
});
}
</script>
<!--[if lt IE 8]>
<script type="text/javascript" src="/bags/common/tiddlers/json2.js"></script>
<![endif]-->
</body>
</html>
/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.7.7|
|''Description''|TiddlySpace configuration|
|''Status''|stable|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin TiddlyFileImporter|
!Code
***/
//{{{
(function($) {
var tweb = config.extensions.tiddlyweb;
var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];
var currentSpace; // assigned later
var disabledTabs = [];
var coreBags = ["system", "tiddlyspace"];
var systemSpaces = ["plugins", "info", "images", "theme"];
systemSpaces = $.map(systemSpaces, function(item, i) {
return "system-%0_public".format(item);
});
// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
_search.apply(this, arguments);
$(".searchField:input", place).
attr({ autocapitalize: "off", autocorrect: "off" });
};
// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
if(typeof arg == "string") { // container name
var space = split(arg, "_", "r");
return ["public", "private"].contains(space.type) ? space : false;
} else if(arg) { // tiddler
var container = determineContainer(arg, fuzzy);
return container ? determineSpace(container.name, fuzzy) : false;
} else {
return false;
}
};
// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
var bag = tiddler.fields["server.bag"];
var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
if(bag) {
return { type: "bag", name: bag };
} else if(recipe) {
return { type: "recipe", name: recipe };
} else if(fuzzy) { // new tiddler
var workspace = tiddler.fields["server.workspace"];
if(workspace) {
var container = split(workspace, "/", "l");
return ["bags", "recipes"].contains(container.type) ? container : false;
} else {
return false;
}
} else {
return false;
}
};
// hijack removeTiddlerCallback to restore tiddler from recipe cascade -- TODO: move into TiddlyWebWiki?
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
var title = context.tiddler.title;
var recipe = context.tiddler.fields["server.recipe"];
_removeTiddlerCallback.apply(this, arguments);
if(recipe) {
context.workspace = "recipes/" + recipe;
var callback = function(context, userParams) {
if(context.status) {
var dirty = store.isDirty();
store.saveTiddler(context.tiddler).clearChangeCount();
store.setDirty(dirty);
} else {
store.notify(title, true);
}
};
context.adaptor.getTiddler(title, context, null, callback);
}
};
// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
var arr = str.split(sep);
var type = arr.length > 1 ? arr[mode]() : null;
return { type: type, name: arr.join(sep) };
};
var plugin = config.extensions.tiddlyspace = {
currentSpace: determineSpace(recipe),
coreBags: coreBags.concat(systemSpaces),
determineSpace: determineSpace,
isValidSpaceName: function(name) {
return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
},
getCurrentBag: function(type) {
return "%0_%1".format(currentSpace, type);
},
getCurrentWorkspace: function(type) {
return "bags/" + this.getCurrentBag(type);
},
// returns the URL for a space's avatar (SiteIcon) based on a server_host
// object and an optional space name
// optional nocors argument prevents cross-domain URLs from being generated
getAvatar: function(host, space, nocors) {
if(space && typeof space != "string") { // backwards compatibility -- XXX: deprecated
space = space.name;
}
var subdomain = nocors ? currentSpace : space;
host = host ? this.getHost(host, subdomain) : "";
var bag = space ? "%0_public".format(space) : "tiddlyspace";
return "%0/bags/%1/tiddlers/SiteIcon".format(host, bag);
},
// returns the URL based on a server_host object (scheme, host, port) and an
// optional subdomain
getHost: function(host, subdomain) {
if(host === undefined) { // offline
tweb.status.server_host = {}; // prevents exceptions further down the stack -- XXX: hacky workaround, breaks encapsulation
return null;
}
subdomain = subdomain ? subdomain + "." : "";
var url = "%0://%1%2".format(host.scheme, subdomain, host.host);
var port = host.port;
if(port && !["80", "443"].contains(port)) {
url += ":" + port;
}
return url;
},
disableTab: function(tabTiddler) {
if(typeof(tabTiddler) == "string") {
disabledTabs.push(tabTiddler);
} else {
for(var i = 0; i < tabTiddler.length; i++) {
plugin.disableTab(tabTiddler[i]);
}
}
},
isDisabledTab: function(tabTitle) {
var match = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])", "mg").exec(tabTitle);
var tabIdentifier = match ? match[1] : tabTitle;
return disabledTabs.contains(tabIdentifier);
},
getCSRFToken: window.getCSRFToken || null // this may not have been processed yet
};
currentSpace = plugin.currentSpace.name;
tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
var url = plugin.getHost(status.server_host);
tweb.status.server_host.url = url;
config.messages.tsVersion = status.version;
});
if(window.location.protocol == "file:") {
// enable AutoSave by default
config.options.chkAutoSave = config.options.chkAutoSave === undefined ?
true : config.options.chkAutoSave;
} else {
// set global read-only mode based on membership heuristics
var indicator = store.getTiddler("SiteTitle") || tiddler;
readOnly = !(recipe.split("_").pop() == "private" ||
tweb.hasPermission("write", indicator));
// replace TiddlyWiki's ImportTiddlers due to cross-domain restrictions
if(config.macros.fileImport) {
$.extend(config.macros.importTiddlers, config.macros.fileImport);
}
}
// hijack saveChanges to ensure SystemSettings is private by default
var _saveChanges = saveChanges;
saveChanges = function(onlyIfDirty, tiddlers) {
if(tiddlers && tiddlers.length == 1 &&
tiddlers[0] && tiddlers[0].title == "SystemSettings") {
var fields = tiddlers[0].fields;
delete fields["server.recipe"];
fields["server.bag"] = plugin.getCurrentBag("private");
fields["server.workspace"] = plugin.getCurrentWorkspace("private");
}
return _saveChanges.apply(this, arguments);
};
// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
init: function() {
showBackstage = true;
}
};
// disable evaluated macro parameters for security reasons
config.evaluateMacroParameters = "none";
var _parseParams = String.prototype.parseParams;
String.prototype.parseParams = function(defaultName, defaultValue, allowEval,
noNames, cascadeDefaults) {
if(config.evaluateMacroParameters == "none") {
arguments[2] = false;
}
return _parseParams.apply(this, arguments);
};
var _tabsMacro = config.macros.tabs.handler;
config.macros.tabs.handler = function(place, macroName, params) {
var newParams = [params[0]]; // keep cookie name
for(var i = 1; i < params.length; i += 3) {
var tabTitle = params[i + 2];
if(!plugin.isDisabledTab(tabTitle)){
newParams = newParams.concat(params[i], params[i + 1], tabTitle);
}
}
_tabsMacro.apply(this, [place, macroName, newParams]);
};
// disable ControlView for XHRs by default
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader("X-ControlView", "false");
}
});
// TiddlyWeb adaptor currently still uses httpReq, which needs extra magic -- XXX: obsolete this!
var _httpReq = httpReq;
httpReq = function(type, url, callback, params, headers, data, contentType,
username, password, allowCache) {
headers = headers || {};
headers["X-ControlView"] = "false";
_httpReq.apply(this, arguments);
};
// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);
// option for default privacy setting
config.optionsDesc.chkPrivateMode = "Set your default privacy mode to private";
config.optionsSource.chkPrivateMode = "setting";
config.options.chkPrivateMode = config.options.chkPrivateMode || false;
saveSystemSetting("chkPrivateMode", true);
config.defaultCustomFields["server.workspace"] = plugin.
getCurrentWorkspace(config.options.chkPrivateMode ? "private" : "public");
config.paramifiers.follow = {
onstart: function(v) {
if(!readOnly) {
var bag = "%0_public".format(currentSpace);
story.displayTiddler(null, v, DEFAULT_EDIT_TEMPLATE, null, null,
"server.bag:%0 server.workspace:bags/%0".format(bag));
story.setTiddlerTag(v, "follow", 1);
story.focusTiddler(v, "text");
}
}
};
var fImport = config.macros.fileImport;
if(fImport) {
fImport.uploadTo = "Upload to: ";
var _createForm = config.macros.fileImport.createForm;
config.macros.fileImport.createForm = function(place, wizard, iframeName) {
var container = $("<div />").text(fImport.uploadTo).appendTo(place);
var select = $('<select name="mode" />').appendTo(container)[0];
$('<option value="private" selected>private</a>').appendTo(select);
$('<option value="public">public</a>').appendTo(select);
wizard.setValue("importmode", select);
_createForm.apply(this, [place, wizard, iframeName]);
};
var _onGet = config.macros.importTiddlers.onGetTiddler;
config.macros.importTiddlers.onGetTiddler = function(context, wizard) {
var type = $(wizard.getValue("importmode")).val();
var ws = plugin.getCurrentWorkspace(type);
wizard.setValue("workspace", ws);
_onGet.apply(this, [context, wizard]);
};
}
})(jQuery);
//}}}
/***
|''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);
//}}}
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
/*{{{*/
.tiddler .originButton div {
display: inline-block;
}
.tiddler .spaceSiteIcon .siteIcon {
_display: inline; /* IE doesn't like inline-block */
}
.tiddler .originButton {
display: block;
}
.selected .tagging,
.selected .tagging:hover {
border: none;
background: none;
}
.tagging {
float: none;
background: none;
border: none;
}
.tagging li.listTitle {
margin-left: 0px;
}
.tagging li {
margin: 0 8px;
}
.tagging .tiddlyLink {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-o-border-radius: 3px;
border-radius: 3px;
padding: 1px 2px;
line-height: 1.2em;
}
/* for following */
#popup .siteIcon {
float: left;
height: 25px;
}
.content {
width: 100%; /* IE */
font-size: 0.9em;
}
.editorHeading {
height: 48px;
}
.heading {
left: 0;
margin-bottom: 40px;
position: relative;
top: 32px;
}
.followButton a {
display: block;
margin-top: -20px;
}
.tiddler .followPlaceHolder {
display: block;
position: absolute;
top: 16px;
right: 64px;
_right: 138px; // add width of modifierIcon
}
.tiddler .followButton {
position: relative;
height: 24px;
text-align: left;
color: #fff;
background: [[ColorPalette::PrimaryMid]];
padding: 10px 0px 0px 10px;
width: 38px;
margin: -16px -8px 24px 0;
}
/* creates the larger triangle */
.followButton:before {
content: "\00a0";
display: block; /* reduce the damage in FF3.0 */
position: relative;
bottom: -20px;
right: 0;
width: 0;
height: 0;
border-width: 0 0 20px 20px;
border-style: solid;
border-color: transparent [[ColorPalette::PrimaryMid]];
}
.toolbar svg {
height: 16px;
width: 16px;
}
.toolbar svg .glyph {
fill: #ccc;
}
.toolbar a:hover .glyph {
fill: black;
}
.toolbar a:active .glyph {
fill: [[ColorPalette::Background]];
}
.originButton,
.followPlaceHolder,
.tiddler .subtitle {
cursor: pointer;
}
.editSpaceSiteIcon .originButton {
cursor: auto;
}
.tiddler .subtitle:hover {
font-weight: bold;
background: none;
}
.originButton img,
.originButton svg {
margin-left: 0px;
}
.modifierIcon {
position: absolute;
width: 74px;
top: 0px;
right: 0px;
_right: 74px; /* in IE6 positioning works incorrectly so use -width instead */
text-align: right;
}
.modifierIcon img,
.modifierIcon svg {
margin-right: 8px;
}
.tiddler .viewer {
padding-bottom: 16px;
margin: 0 0 0 56px;
line-height: 1.4em;
}
.viewer pre {
margin-left: 0;
}
.siteIcon .label {
color: [[ColorPalette::TertiaryDark]];
}
.tiddler .spaceSiteIcon {
float: left;
margin-right: 0;
margin-top: 0;
position: relative;
display: block;
}
.tiddler .titleBar {
display: block;
margin-right: 136px;
margin-left: 56px;
}
.followButton a {
color: [[ColorPalette::Background]];
}
.tiddler {
position: relative;
padding: 0;
margin-bottom: 3em;
border-top: 3px solid [[ColorPalette::PrimaryMid]];
background: #fff;
}
.tiddler .editor {
padding: 0px 8px;
}
.tiddler .heading .title {
position: relative;
display: block;
word-wrap: break-word;
font-size: 32px;
line-height: 32px;
}
.tiddler .heading .editor.title {
font-size: 1.7em;
line-height: normal;
}
.tiddler .headingClear {
clear: both;
}
.tiddler .subtitle {
font-style: italic;
font-size: 0.9em;
color: #a6a59e;
margin-top: 0;
}
.toolbar {
position: absolute;
padding: 0;
top: 8px;
right: -8px;
}
.toolbar .moreCommand.highlight {
background: none;
}
.tiddler .toolbar .button {
border: none;
display: inline;
padding: 0px;
margin-right: 16px;
}
.tiddler .toolbar a:hover {
background: none;
}
.tiddler .tagged .listTitle {
display: none;
}
.revButton {
float: right;
}
/*! EditTemplate specific*/
.tiddler .privacySettings {
text-align: center;
}
.tiddler .privacySettings .originButton {
display: inline;
}
.editSpaceSiteIcon, .privacyEdit {
float: left;
}
.editSpaceSiteIcon svg,
.editSpaceSiteIcon img,
.editSpaceSiteIcon .roundelLabel {
float: left;
}
.tagTitle {
position: absolute;
text-align: center;
width: 48px;
top: 0px;
left: -56px;
}
.editSpaceSiteIcon .originButton img,
.editSpaceSiteIcon .originButton svg {
height: 16px;
margin-left: 24px;
margin-right: 32px;
width: 16px;
}
.tagAnnotation {
margin-top: 8px;
padding-bottom: 8px;
}
.annotationsBox {
margin-top: 8px;
}
.editorFooter {
position: relative;
padding: 0;
margin-top: 16px;
margin-left: 64px;
}
.tiddler .editorFooter .editor {
padding-left: 0px;
}
.heading .editor input {
width: 100%;
font-size: 1.5em;
}
.spaceSiteIcon .externalImage .image a:hover,
.modifierIcon .externalImage .image a:hover {
background: none;
}
div.toolbar {
visibility:hidden;
right:-16px;
}
.selected div.toolbar {
visibility: visible;
}
.followButton a:hover {
background: [[ColorPalette::PrimaryMid]];
text-decoration: underline;
}
a.image:hover {
background: transparent;
}
@media all and (max-device-width: 480px) {
div.toolbar {
visibility:visible;
}
}
@media only screen and (device-width: 768px) {
div.toolbar {
visibility:visible;
}
}
@media all and (max-width: 960px) {
.tiddler .titleBar {
margin-left: 36px;
margin-right: 80px;
}
.tiddler .heading {
margin-bottom: 48px;
}
.tiddler .heading .title {
font-size: 32px;
line-height: 32px;
}
.tiddler .modifierIcon img,
.tiddler .modifierIcon svg,
.tiddler .spaceSiteIcon .originButton img,
.originButton svg {
width: 32px;
height: 32px;
margin-left: 0px;
margin-right: 0px;
}
.tiddler .followPlaceHolder {
right: 48px;
}
.tiddler .followButton {
width: 24px;
}
.tiddler .viewer {
margin: 0px 0px 0px 36px;
padding-top: 0;
}
br {
line-height: 0.5em;
}
}
/*}}}*/
ColorPalette
StyleSheet
SiteSubtitle
GettingStarted
SiteTitle
MainMenu
SiteIcon
DefaultTiddlers
ViewTemplate
PageTemplate
SideBarOptions
EditTemplate
SiteInfo
SideBarTabs
ToolbarCommands
You can change the title and subtitle of your space, this will be visible to people visiting your space as well as being what is shown the browser tabs. The content these are stored in two tiddlers, clicking on the links below will open up these tiddlers which you can edit to make changes.
* [[SiteTitle]]
* [[SiteSubtitle]]
<html><hr><html>
This space provides the EditTemplateFieldsPlugin which make fields much more useful in TiddlySpace/TiddlyWiki by allowing you edit them and filter on them.
Provides the following macros
{{{
<<editFields>>
}}}
which should be added to your EditTemplate.
[img[http://static.flickr.com/159/357980729_f9f09cc4b5_m.jpg][http://www.flickr.com/photos/cdent/357980729/]]
! Product Development Conflict
When you make software that will be sold and bought, there's a conflict that never seems to go away: Do we create the thing that customers are asking for, in the way customers describe it, or do we create the thing you presume/expect/hope the customer will want once they know it exists.
The former is a safer bet but like all safe bets, the lower risk can result in lessened returns. People will argue that the thing to do is to make what the customers want better than anyone else. With more panache, style, usability.
The latter, IMHO, is //way// more fun and has far greater opportunity to change how people do things. It has the stank of inspired innovation. A designer or group of designers that is well informed through experience and interaction with the audience they intend to serve can create something new that is in response to but not constrained by existing experiences. There's way more risk here, but more chances to shift things around.
So that's interesting: You don't want constraints initially, but it's been shown time and time again that once you decide what you are doing embracing constraints makes things easier, cleaner and more clear.
! Too Much Work Heuristics
I'm always looking for ways to make a simple determination if I have too much work to do. Not that I need to know: I always know; I always do. But a simple heuristic is easier to communicate than a vague sense of unease.
Today's idea is: There's some limit of code branches to which I can be giving active attention. If that limit is crossed then there's trouble. I don't know where that limit is (yet). If the limit is crossed it's time to stop and either merge some branches down or get rid of them.
One of the good things about these sorts of heuristics is that they don't rely on a human to make a value judgement. Instead a red flag is raised by what amounts to math. It's a sort of appeal to external authority.
[img[http://static.flickr.com/55/146038095_c20739ec0b_m.jpg][http://www.flickr.com/photos/cdent/146038095/]]
! Socialtext Unplugged, Reloaded
We're starting to talk about what to do with the next version of [[Socialtext Unplugged]]. The first version got the concept out under some eyes, now we need to stabilize the existing features and get things tweaked to make it a bit more usable.
One feature that's not present in the current release that we hope to get to is some form of edit contention detection when synching. And if there's time some form of intelligently handling that contention.
There are many ways to handle contention detection, but my preferred way would be to use the lessons described in [[Detecting the Lost Update Problem Using Unreserved Checkout|http://www.w3.org/1999/04/Editing/]]. Using Etags you can uniquely identify a particular version of a page. If a known Etag is provided in an `If-Match` header when PUTting a page back to the server, the server can respond with an HTTP 412 if the version of the page on the server has changed since the last sync or otherwise accept the `PUT`. This means you can have a simple set of steps for handling contention that does not require an initial `GET` of the server resource at the time of synch:
{{{
DO PUT a_page
IF 412
DO CONTENTION HANDLING (such as telling the user or a smart merge)
IF SUCCESS
WOO, WE DID IT, MOVE ON
ELSE
OTHER ERROR
UNTIL no pages to synch
}}}
Yay HTTP!
! Peer to Peer Wiki
I've been wondering lately what a peer to peer decentralized wiki would look like. One where there is no server on which the content lives: it's just out there in the network of peers participating in the wiki. Technologically this doesn't seem much of a big deal: it can be done, things like it have already been done. What's more interesting is how such a thing would behave for people using it.
To keep it simple, let's say this is a closed network of voluntary peers that confirm one another: a team of people can create their wiki and don't need to worry about intruders or scaling problems. How would such a thing work? Would it be any different from a server hosted wiki? Would there be any benefit?
I can imagine advanced versions of such things having built in synching and unplug powers. Any client could leave and rejoin the network (or any network?) as they liked.
In my perfect universe the content would be stored in a granular fashion (each block, paragraph, list item, etc would have it's own unique ID (like a [[Purple Number]]) and be retrievable piecemeal for composable documents.
What else?
[img[http://static.flickr.com/45/134550722_c94c455f65_m.jpg][http://www.flickr.com/photos/cdent/134550722/]]
! The API and the Interface
When the new S2 look and feel for the Socialtext web interface was released, it included quite a bit of AJAX to shuffle data around. The previous version used such things for the sideboxes, but that was about it. In S2, tag and attachment handling send and receive data with a big ol mess of Javascript.
Due to the vagrancies of time and the exigencies of the plot that Javascript does not use the interfaces provided by the [[REST API]]. We hope to change this. The most fabulous way would be by creating a set of UI fundamentals that could be reused or subclassed in other interfaces than the standard socialtext interface. For example: The current interface is designed to be relatively clean and approachable. As a result it doesn't present as much information about a page and its workspace as it could. A set of Javascript widgets could be used to compose an interface that might have a steeper learning curve but allowed for some serious gardening once you were warmed up.
Last Fall I started working on a Javascript widget class system for the entities represented in the REST API. It felt, at least to me, pretty extensible. With the help of [[Ingy]] I hooked in some [[Jemplate]] to separate out some of the presentation handling. Unfortunately due to the same vagrancies and exigencies mentioned above, it has since langished. Such things are luxuries.
We are, however, happily engaged in planning further improvements to the openness of the Socialtext code. The above mentioned code and assorted other ancillary goodies should make it out into the light of day for diddling by all soonish. So if I can't get to making happy little widgets, maybe somebody else can?
! Thumb Drives
In the future all my Socialtext wikis will come from [[Socialtext Unplugged]] so I'm thinking I need a thumb drive to carry them around. What's the latest and greatest in such things these days? I like small and rugged so I can sit on it without worry.
Such things are sort of superfluous though: I carry my macbook around with me and I don't often run into other computers where I would want to be doing my wikistuff. Back in the 90s I imagined ubiquitous networks bringing me a world where any computer could become my computer. Instead of going into a cafe and opening up my mac, I'd go into a cafe, sans stuff, sit down somewhere and my [[OnIt]] would negotiate a session with the local computing infrastructure, with my settings pulled from the cloud.
Where's that? Where's my jetpack and flying cars?
So maybe I don't need a thumb drive all that much, but having one would enhance the experience of developing unplugged features, so if you have a favorite or advice let me know. It seems likely that "just buy one, they're all the same" is a possible response. I saw one in the NYT that's a stick. Nice notion, but too big.
! Working from Home
I'm two years and four or five months into working from home. There's a big difference, I've found, between telecommuting in the "yeah, I could come into the office, but I don't because I'm modern like that" sense and the way many of us at Socialtext do it. For the majority of the people at Socialtext there is no option, on a hard day, of hopping in the car and going into the office for some fleshy motivational gathering. There's only the one office and it is hundreds or thousands of miles away. Some of us spend months out on the fences never seeing a coworker.
It sounds cool at first (naked working! every! single! day!) but if you focus too much on the primary work and not enough on the secondary support of your work then the little things start to add up. You have to put the snacks and coffee in the kitchen. When the office supplies run out, it's on you. Worse, the sublime pleasure of snarfing home office supplies from the work office is completely corrupted. When you need to bitch with your peers, it's not down the pub or over your favorite dish at the chinese place around the corner, it's mediated by gizmo, headphones, irc and keyboards.
Legally, things like IP agreements, workers comp, expense handling, etc haven't really caught up with permanent home working, especially people who do that while working for startups.
It's interesting business. I wouldn't change my current situation, it has [[its advantages|http://www.flickr.com/photos/cdent/175631893/]] but if/when the Socialtext thing plays out, I'll think hard about how to do this sort of thing again. This guy has some [[safety tips|http://davidbau.com/archives/2005/07/19/telecommuting_safety_tips.html]]. Making it easy to come in to port is probably most important for me.
In February Socialtext will be having one of its face to faces. For the developers this is often a week of compressed activity with little sleep. The planning is intense, the bitching is intense, the jokes are raunchier, meals are shared with vigor, the whiteboards overflow, crazy ideas and code explode at 5 in the morning.
It's probably the best part of the job. I don't know if it would be without the months of deprivation prior.
<!--{{{-->
<div macro='slideRevision'></div>
<div class='heading'>
<span class="titleBar">
<div class='title' macro='view title text'></div>
</span>
<span class='modifierIcon'
macro='view modifier SiteIcon label:no height:48 width:48 preserveAspectRatio:yes'>
</span>
<div class='toolbar'
macro='toolbar [[ToolbarCommands::RevisionToolbar]] icons:yes height:48 width:48 more:popup'>
</div>
<div class='tagClear'></div>
</div>
<div class='content'>
<div class='viewer' macro='view text wikified'></div>
</div>
<div class='tagInfo'>
<div class='tidTags' macro='tags'></div>
<div class='tagging' macro='tagging'></div>
</div>
<!--}}}-->
[img[http://static.flickr.com/1307/1286798685_211ae19eee_m.jpg][http://www.flickr.com/photos/cdent/1286798685/]]
! People in Social Software Systems
The trouble with dealing with People in social software systems is that people don't go in databases, they don't fit.
You can put limited representations of people in the database (Chris, male, 38) but there is very little meaning associated with the presence of that representation.
People experience other people by bearing witness to their actions. Often this is done synchronously. I observe the server bringing me coffee. I can see what someone is doing right now and have awareness or knowledge of them and their actions.
For the most part awareness of others is an asynchronous act. We know of people by the artifacts they have left behind. You, the reader, have some sense of me, the writer, from reading this. It is unlikely, however, that you saw me write it. If you are in this cafe, please introduce yourself.
In your head you may be forming a picture of me: What I like. What I know. It's not me, it's a model of me. It's completely different from my model of me. I hope.
In this wiki, you can't tell who wrote this without some assistance from the system. Various bits of UI furniture enhance the core element (the page) by providing context through the presentation of representations of other information present in the system. What this furniture is, what those representations are and what context you may experience depends on what interface to the system you are using.
More importantly, whether that context is relevant to //you// is entirely dependent on your goals. If you are stalking me it is useful to know I wrote this. If you are curious what is going on at Socialtext, not as much.
If you are curious about Socialtext and you find that what I write, in particular, provides a unique illumination of the Socialtext world, you may wish to track my editorial influence throughout the Socialtext ouvre.
> I, given my personal biasing of the text over the author (content is king!), don't really think tracking a person is the best strategy, but I can see how you might want to be able to do it.
But go a little further: when you are getting to know someone, you go over to their house for the first time, you have a look around. Few people can resist the allure of browsing the bookshelves, scanning the CDs and ~DVDs and looking at the choice of soap in the bathroom.
Much like the information people produce informs on the person, so does the information they consume.
These information productions and consumptions tell us what a person may know, what they might be interested in, and where they may have or lack expertise. *It puts a bit of People in the system.*
Knowing such things can help us decide if the person might be a candidate to be a collaborative peer. It takes far more than finding someone to really collaborate (which is far far more than just working together) but that finding is a significant first hurdle.
Next to your favorite book on your new friend's bookshelf may be your next favorite book that until now you've never heard of.
! So?
In these modern times a huge portion of our time is spent piddling around with electronic/digital/online/whatever information. I interact with Socialtext content all day long: reading, editing, commenting, checking my feeds for fresh content.
In these modern times all these actions can be recorded.
That recording creates an information artifact, akin to but different from this page. An artifact that is useful for finding our collaborative junctions. Or useful for Bossy Big Brother to catch us slacking. Or useful for making us worry about privacy (c.f. [[facebook|http://facebook.com]]).
I might argue, and I do, that I use and abuse so much information that tracking those behaviors is mere noise. If there is something that I care about enough for you to care that I care about it, my attention to it will be obvious. By the changes I make to it and the artifacts I create surrounding it.
A counter argument is that some people, and some forms of automation, can identify and match patterns in the flow of peoples' information behaviors. This processing can lead to interesting discoveries and new associations.
So setting aside for the moment the (considerable) Big Brother concerns, how would you track these things?
Each of the actions I've described above take the form //Noun A verbed Noun B//:
* Chris created Page foo
* Marc read page foo
From this we can infer that Marc read something that Chris wrote. We can also info that Chris wrote something that was read by Marc.
(am I using "infer" correctly here?)
Whoa, I can hear RDF calling out, "me! me! oh me!" Or maybe some kind of tuplestore. Something that allows, encourages and is built for cascading inference.
Whatever the implementation, these descriptions of actions ride at a level separate from the representations in a core datastore
There is a Page in the datastore.
There is a User identified as Chris in the datastore.
There is no Person in the datastore because (as we've given) they don't fit.
There is no action of Edit in the datastore. Edit is something else.
When you see that something else in a description that's a good sign you have a separate service. This service is aware of what entities exist in the system and what actions are available to be performed. Two entities can be connected by an action.
If such a thing exists, we have the ability to make a trail that identifies the information consumption and production by actors in the //social// system. At the front of that trail is what just happened or is happening //right now//.
From any action in time we can calculate by inference how we got here and where things went from here.
Cool? Yes. Dangerous? Also yes. Useful? Dunno, you tell me?
These are some of the postings from [[a blog|https://www.socialtext.net/open/index.cgi?action=blog_display&category=Chris+Dent+Blog]] I (@cdent) kept while working at [[Socialtext]]. I'm porting a selection of them here so I can mine them for common themes and what not. I'm importing them by hand and may be making edits as I go.
/***
|''Name''|TiddlySpacePublishingCommands|
|''Version''|0.8.5|
|''Status''|@@beta@@|
|''Description''|toolbar commands for drafting and publishing|
|''Author''|Jon Robson|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpacePublishingCommands.js|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlySpaceConfig TiddlySpaceFilters|
!Usage
Provides changeToPrivate, changeToPublic and saveDraft commands
Provides TiddlySpacePublisher macro.
{{{<<TiddlySpacePublisher type:private>>}}} make lots of private tiddlers public.
{{{<<TiddlySpacePublisher type:public>>}}} make lots of public tiddlers public.
!TODO
* add public argument?
!Code
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var originMacro = config.macros.tiddlerOrigin;
tiddlyspace.getTiddlerStatusType = function(tiddler) {
var isShadow = store.isShadowTiddler(tiddler.title);
var exists = store.tiddlerExists(tiddler.title);
if(isShadow && !exists) {
return "shadow";
} else if(!exists) {
return "missing";
} else {
var types = ["private", "public"];
var type = "external";
for(var i = 0; i < types.length; i++) {
var t = types[i];
type = config.filterHelpers.is[t](tiddler) ? t : type;
}
if(config.filterHelpers.is.unsynced(tiddler)) {
type = type == "private" ? "unsyncedPrivate" : "unsyncedPublic";
}
return type;
}
};
var cmd = config.commands.publishTiddler = {
text: "make public",
tooltip: "Change this private tiddler into a public tiddler",
errorMsg: "Error publishing %0: %1",
isEnabled: function(tiddler) {
return !readOnly && config.filterHelpers.is["private"](tiddler);
},
handler: function(ev, src, title) {
var tiddler = store.getTiddler(title);
if(tiddler) {
var newBag = cmd.toggleBag(tiddler.fields["server.bag"]);
this.moveTiddler(tiddler, {
title: tiddler.fields["publish.name"] || tiddler.title,
fields: { "server.bag": newBag }
});
}
},
toggleBag: function(bag, to) {
var newBag;
if(typeof bag != typeof "") {
var tiddler = bag;
bag = tiddler.fields["server.bag"];
}
if(bag.indexOf("_private") > -1) { // should make use of endsWith
to = to ? to : "public";
newBag = bag.replace("_private", "_" + to);
} else {
to = to ? to : "private";
newBag = bag.replace("_public", "_" + to);
}
return newBag;
},
copyTiddler: function(title, newTitle, newBag, callback) {
var original = store.getTiddler(title);
newTitle = newTitle ? newTitle : title;
var adaptor = original.getAdaptor();
var publish = function(original, callback) {
var tiddler = $.extend(new Tiddler(newTitle), original);
tiddler.fields = $.extend({}, original.fields, {
"server.bag": newBag,
"server.workspace": "bags/%0".format(newBag),
"server.page.revision": "false"
});
delete tiddler.fields["server.title"];
tiddler.title = newTitle;
adaptor.putTiddler(tiddler, null, null, callback);
};
publish(original, callback);
},
moveTiddler: function(tiddler, newTiddler, callback) {
var info = {
copyContext: {},
deleteContext: {}
};
var _dirty = store.isDirty();
var adaptor = tiddler.getAdaptor();
var newTitle = newTiddler.title;
var oldTitle = tiddler.title;
delete tiddler.fields["server.workspace"];
var oldBag = tiddler.fields["server.bag"];
var newBag = newTiddler.fields["server.bag"];
var newWorkspace = "bags/%0".format(newBag);
cmd.copyTiddler(oldTitle, newTitle, newBag, function(ctx) {
info.copyContext = ctx;
var context = {
tiddler: tiddler,
workspace: newWorkspace
};
store.addTiddler(ctx.tiddler);
tiddler.title = oldTitle; // for cases where a rename occurs
if(ctx.status) { // only do if a success
if(oldBag != newBag) {
adaptor.deleteTiddler(tiddler, context, {}, function(ctx) {
info.deleteContext = ctx;
var el;
if(tiddler) {
tiddler.fields["server.workspace"] = newWorkspace;
tiddler.fields["server.bag"] = newBag;
}
el = el ? el : story.refreshTiddler(oldTitle, null, true);
if(oldTitle != newTitle) {
store.deleteTiddler(oldTitle);
store.notify(oldTitle, true);
}
if(el) {
story.displayTiddler(el, newTitle);
}
if(oldTitle != newTitle) {
story.closeTiddler(oldTitle);
}
if(callback) {
callback(info);
}
store.setDirty(_dirty);
});
} else {
if(callback) {
callback(info);
}
}
refreshDisplay();
}
});
}
};
var changeToPrivate = config.commands.changeToPrivate = {
text: "make private",
tooltip: "turn this public tiddler into a private tiddler",
isEnabled: function(tiddler) {
return !readOnly && config.filterHelpers.is["public"](tiddler);
},
handler: function(event, src, title) {
var tiddler = store.getTiddler(title);
var newBag = cmd.toggleBag(tiddler, "private");
var newTiddler = { title: title, fields: { "server.bag": newBag }};
cmd.moveTiddler(tiddler, newTiddler);
}
};
config.commands.changeToPublic = cmd;
/* Save as draft command */
var saveDraftCmd = config.commands.saveDraft = {
text: "save draft",
tooltip: "Save as a private draft",
isEnabled: function(tiddler) {
return changeToPrivate.isEnabled(tiddler);
},
getDraftTitle: function(title) {
var draftTitle;
var draftNum = "";
while(!draftTitle) {
var suggestedTitle = "%0 [draft%1]".format(title, draftNum);
if(store.getTiddler(suggestedTitle)) {
draftNum = !draftNum ? 2 : draftNum + 1;
} else {
draftTitle = suggestedTitle;
}
}
return draftTitle;
},
createDraftTiddler: function(title, gatheredFields) {
var tiddler = store.getTiddler(title);
var draftTitle = saveDraftCmd.getDraftTitle(title);
var draftTiddler = new Tiddler(draftTitle);
if(tiddler) {
$.extend(true, draftTiddler, tiddler);
} else {
$.extend(draftTiddler.fields, config.defaultCustomFields);
}
for(var fieldName in gatheredFields) {
if(TiddlyWiki.isStandardField(fieldName)) {
draftTiddler[fieldName] = gatheredFields[fieldName];
} else {
draftTiddler.fields[fieldName] = gatheredFields[fieldName];
}
}
var privateBag = tiddlyspace.getCurrentBag("private");
var privateWorkspace = tiddlyspace.getCurrentWorkspace("private");
draftTiddler.title = draftTitle;
draftTiddler.fields["publish.name"] = title;
draftTiddler.fields["server.workspace"] = privateWorkspace;
draftTiddler.fields["server.bag"] = privateBag;
draftTiddler.fields["server.title"] = draftTitle;
draftTiddler.fields["server.page.revision"] = "false";
delete draftTiddler.fields["server.etag"];
return draftTiddler;
},
handler: function(ev, src, title) {
var tiddler = store.getTiddler(title); // original tiddler
var tidEl = story.getTiddler(title);
var uiFields = {};
story.gatherSaveFields(tidEl, uiFields);
var tid = saveDraftCmd.createDraftTiddler(title, uiFields);
tid = store.saveTiddler(tid.title, tid.title, tid.text, tid.modifier,
new Date(), tid.tags, tid.fields);
autoSaveChanges(null, [tid]);
story.closeTiddler(title);
story.displayTiddler(src, title);
story.displayTiddler(src, tid.title);
}
};
var macro = config.macros.TiddlySpacePublisher = {
locale: {
title: "Batch Publisher",
changeStatusLabel: "Make %0",
noTiddlersText: "No tiddlers to publish",
changeStatusPrompt: "Make all the selected tiddlers %0.",
description: "Change tiddlers from %0 to %1 in this space"
},
listViewTemplate: {
columns: [
{ name: "Selected", field: "Selected", rowName: "title", type: "Selector" },
{ name: "Tiddler", field: "tiddler", title: "Tiddler", type: "Tiddler" },
{ name: "Status", field: "status", title: "Status", type: "WikiText" }
],
rowClasses: []
},
changeStatus: function(tiddlers, status, callback) { // this is what is called when you click the publish button
var publicBag;
for(var i = 0; i < tiddlers.length; i++) {
var tiddler = tiddlers[i];
var newTiddler = {
title: tiddler.title,
fields: { "server.bag": cmd.toggleBag(tiddler, status) }
};
cmd.moveTiddler(tiddler, newTiddler, callback);
}
},
getMode: function(paramString) {
var params = paramString.parseParams("anon")[0];
var status = params.type ?
(["public", "private"].contains(params.type[0]) ? params.type[0] : "private") :
"private";
var newStatus = status == "public" ? "private" : "public";
return [status, newStatus];
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var wizard = new Wizard();
var locale = macro.locale;
var status = macro.getMode(paramString);
wizard.createWizard(place, locale.title);
wizard.addStep(macro.locale.description.format(status[0], status[1]),
'<input type="hidden" name="markList" />');
var markList = wizard.getElement("markList");
var listWrapper = $("<div />").addClass("batchPublisher").
attr("refresh", "macro").attr("macroName", macroName).
attr("params", paramString)[0];
markList.parentNode.insertBefore(listWrapper, markList);
$.data(listWrapper, "wizard", wizard);
macro.refresh(listWrapper);
},
getCheckedTiddlers: function(listWrapper, titlesOnly) {
var tiddlers = [];
$(".chkOptionInput[rowName]:checked", listWrapper).each(function(i, el) {
var title = $(el).attr("rowName");
if(titlesOnly) {
tiddlers.push(title);
} else {
tiddlers.push(store.getTiddler(title));
}
});
return tiddlers;
},
refresh: function(listWrapper) {
var checked = macro.getCheckedTiddlers(listWrapper, true);
var paramString = $(listWrapper).empty().attr("params");
var wizard = $.data(listWrapper, "wizard");
var locale = macro.locale;
var params = paramString.parseParams("anon")[0];
var publishCandidates = [];
var status = macro.getMode(paramString);
var pubType = status[0];
var newPubType = status[1];
var tiddlers = params.filter ? store.filterTiddlers(params.filter[0]) :
store.filterTiddlers("[is[%0]]".format(pubType));
var enabled = [];
for(var i = 0; i < tiddlers.length; i++) {
var tiddler = tiddlers[i];
var title = tiddler.title;
if(!tiddler.tags.contains("excludePublisher") && title !== "SystemSettings") {
publishCandidates.push({ title: title, tiddler: tiddler, status: pubType});
}
if(checked.contains(title)) {
enabled.push("[rowname=%0]".format(title));
}
}
if(publishCandidates.length === 0) {
createTiddlyElement(listWrapper, "em", null, null, locale.noTiddlersText);
} else {
var listView = ListView.create(listWrapper, publishCandidates, macro.listViewTemplate);
wizard.setValue("listView", listView);
var btnHandler = function(ev) {
var tiddlers = macro.getCheckedTiddlers(listWrapper);
var callback = function(status) {
$(".batchPublisher").each(function(i, el) {
macro.refresh(el);
});
};
macro.changeStatus(tiddlers, newPubType, callback);
};
wizard.setButtons([{
caption: locale.changeStatusLabel.format(newPubType),
tooltip: locale.changeStatusPrompt.format(newPubType),
onClick: btnHandler
}]);
$(enabled.join(",")).attr("checked", true); // retain what was checked before
}
}
};
})(jQuery);
//}}}
!Spaces
<<groupBy server.bag>>
!Private
<<list filter [is[private]]>>
!Public
<<list filter [is[public]]>>
!Drafts
<<list filter [is[draft]]>>
/***
|''Name''|TiddlySpaceBackstage|
|''Version''|0.8.0|
|''Description''|Provides a TiddlySpace version of the backstage and a homeLink macro|
|''Status''|@@beta@@|
|''Contributors''|Jon Lister, Jon Robson, Colm Britton|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceBackstage.js|
|''Requires''|TiddlySpaceConfig ImageMacroPlugin TiddlySpaceViewTypes|
!StyleSheet
.publicLightText {
color: #C0E5FC;
}
.privateLightText {
color: #E2C1D6;
}
.tiddler .error.annotation .button{
display: inline-block;
}
#backstageArea #backstageToolbar a.task_tiddlyspace {
margin: 0px auto auto -75px;
font-weight: bold;
width: 150px;
line-height:24px;
font-size: 1.2em;
padding: 0;
top: 0;
position: absolute;
left: 50%;
}
.task_tiddlyspace .image,
.task_tiddlyspace .svgIcon {
display: inline;
}
.task_tiddlyspace .svgIconText {
display: none;
}
.task_tiddlyspace .logoText {
position: absolute;
top: 0px;
margin-left: 5px;
}
#backstageArea {
z-index: 49;
color: white;
background-color: black;
background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #222),color-stop(0.5, #333),color-stop(1, #555));
background: -moz-linear-gradient(center bottom,#222 0%, #333 50%, #555 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff555555, endColorstr=#ff222222);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff555555, endColorstr=#ff222222)";
height: 25px;
padding: 0;
}
.backstageBackground {
fill: black;
}
#backstageButton {
overflow: hidden;
}
#backstageButton #backstageShow,
#backstageButton #backstageHide {
margin: 0px;
padding: 0px;
}
#backstageButton #backstageShow:hover,
#backstageButton #backstageHide:hover {
background: none;
color: none;
}
#backstageButton img,
#backstageButton svg {
width: 24px;
height: 24px;
}
#messageArea {
top: 50px;
}
#backstageToolbar {
position: relative;
}
#backstageArea a {
padding: 0px;
margin-left: 0px;
color: white;
background: none;
}
#backstageArea a:hover {
background-color: white;
}
#backstage .tabContents ol,
#backstage .tabContents ul {
padding: auto;
}
#backstageButton a {
margin: 0;
}
.backstagePanelBody .tabContents ul {
padding: 5px;
margin: 5px;
}
#backstage #backstagePanel {
margin-left: 5%;
padding: 0em;
margin-right: 5%;
text-align: center;
}
#backstageToolbar a {
position: relative;
}
#backstageArea a.backstageSelTab,
#backstageToolbar .backstageTask {
line-height: 25px;
color: #767676;
}
.backstageTask .externalImage,
.backstageTask .image {
display: inline;
}
.backstageTask .txtUserName,
.backstageTask .spaceName {
color: #fff;
}
.backstageSelTab .txtUserName,
.backstageSelTab .spaceName,
a:hover .txtUserName,
a:hover .spaceName {
color: #000;
}
.spaceSiteIcon {
margin-right: 10px;
}
.userSiteIcon {
margin-left: 10px;
}
#backstageToolbar .task_space {
position: absolute;
top: 0px;
left: 0%;
}
#backstageToolbar .task_user,
#backstageToolbar .task_login {
display: block;
position: absolute;
top: 0px;
right: 5%;
}
#backstageToolbar .task_login img {
position: relative;
display: inline;
}
#backstageToolbar .task_login img,
#backstageToolbar .task_user img {
float: right;
}
#backstageToolbar .task_space .svgIcon {
float: left;
position: relative;
z-index: 2;
}
#backstageToolbar a span {
z-index: 2;
}
#backstageToolbar .spaceSiteIcon {
float: left;
}
a.backstageTask {
display: block;
}
#backstageToolbar a span.txtUserName,
#backstageToolbar a .txtUserName span {
display: inline;
float: none;
}
#backstage .deleteButton {
margin-left: 0.3em;
font-weight: bold;
color: red;
font-size: 1.6em;
}
#backstage .deleteButton:hover {
background: none;
}
#backstageArea .siteIcon {
display: inline;
}
#backstagePanel .TiddlySpaceLogin {
display: inline;
}
.backstagePanelBody .tabContents .button {
display: inline-block;
margin-right: 10px;
}
.backstagePanelBody .tab {
margin: 0 0 0 0.6em;
padding: 0.4em 0.5em 1px 0.5em;
}
#backstage .tabContents {
padding: 1.5em;
text-align: left;
}
#backstage table {
margin: auto;
}
#backstage .wizard table {
border: 0px;
margin: 0;
}
#backstage .txtSpaceTab li {
border: 1px solid #ddd;
background: #eee;
list-style: none;
margin: 0.5em;
padding: 0.5em;
width: 80%;
}
#backstage .txtSpaceTab li.annotation {
border: 2px solid [[ColorPalette::SecondaryMid]];
}
#backstage div li.listLink {
border: 0px;
width: 78%;
font-size: 0.7em;
}
#backstage div li.listTitle {
font-weight: bold;
text-decoration: underline;
font-size: 1em;
background: #ccc;
width: 100%;
}
#backstage div.txtSpaceTab li .deleteButton {
float: right;
}
#backstage fieldset {
border: solid 1px [[ColorPalette::Background]];
}
#backstage .viewer table,#backstage table.twtable {
border: 0px;
}
#backstageToolbar img {
padding: 0;
}
#backstage .wizard,
#backstage .wizardFooter {
background: none;
}
.viewer td, .viewer tr, .twtable td, .twtable tr {
border: 1px solid #eee;
}
#backstage .inlineList ul li {
background-color: [[ColorPalette::Background]];
border: solid 1px [[ColorPalette::TertiaryMid]];
display: block;
float: left;
list-style: none;
margin-right: 1em;
padding: 0.5em;
}
.backstageClear, .inlineList form {
clear: both;
display: block;
margin-top: 3em;
}
.tiddlyspaceMenu {
text-align: center;
}
span.chunkyButton {
display: inline-block;
padding: 0;
margin: 0;
border: solid 2px #000;
background-color: #04b;
}
span.chunkyButton a.button, span.chunkyButton a:active.button {
white-space: nowrap;
font-weight: bold;
font-size: 1.8em;
color: #fff;
text-align: center;
padding: 0.5em 0.5em;
margin: 0;
border-style: none;
display: block;
}
span.chunkyButton:hover {
background-color: #014;
}
span.chunkyButton a.button:hover {
border-style: none;
background: none;
color: #fff;
}
a.baskstageTask.task_login,
a.baskstageTask.task_user {
_width: 200px;
_text-align: right;
}
#backstageArea #backstageToolbar .task_login img,
#backstageArea #backstageToolbar .task_user img {
_display: inline;
_float: none;
}
#backstage .unpluggedSpaceTab .wizard,
.unpluggedSpaceTab .wizard {
background: white;
border: 2px solid #CCC;
padding: 5px;
}
.syncKey .keyItem {
border: 1px solid black;
display: inline-block;
margin: 0.2em;
padding: 0.1em 0.1em 0.1em 0.1em;
}
.keyHeading {
font-size: 2em;
font-weight: bold;
margin: 0.4em 0em -0.2em;
}
.unpluggedSpaceTab .putToServer,
.unpluggedSpaceTab .notChanged {
display: none;
}
.tiddlyspaceMenu ul {
margin: 0;
padding: 0;
}
.tiddlyspaceMenu ul li {
list-style: none;
}
.unsyncedChanges .unsyncedList {
display: block;
}
.unsyncedList {
display: none;
}
#backstage iframe {
height: 600px;
width: 100%;
border: none;
}
!Code
***/
//{{{
(function($) {
var name = "StyleSheet" + tiddler.title;
config.shadowTiddlers[name] = "/*{{{*/\n%0\n/*}}}*/".
format(store.getTiddlerText(tiddler.title + "##StyleSheet")); // this accesses the StyleSheet section of the current tiddler (the plugin that contains it)
store.addNotification(name, refreshStyles);
if(!config.extensions.tiddlyweb.status.tiddlyspace_version) { // unplugged
config.extensions.tiddlyweb.status.tiddlyspace_version = "<unknown>";
config.extensions.tiddlyweb.status.server_host = {
url: config.extensions.tiddlyweb.host }; // TiddlySpaceLinkPlugin expects this
}
var disabled_tabs_for_nonmembers = ["PluginManager", "Backstage##FileImport",
"Backstage##BatchOps", "Backstage##SpaceMembers",
"TiddlySpaceTabs##Private", "TiddlySpaceTabs##Drafts"];
var tweb = config.extensions.tiddlyweb;
var tiddlyspace = config.extensions.tiddlyspace;
var currentSpace = tiddlyspace.currentSpace.name;
var imageMacro = config.macros.image;
if(config.options.chkBackstage === undefined) {
config.options.chkBackstage = true;
}
config.tasks.user = {
text: "user: ",
tooltip: "user control panel",
unpluggedText: "unplugged user",
content: "<html><iframe frameBorder='0' src='" + config.extensions.tiddlyweb.host + "/_account'></iframe></html>"
};
config.tasks.space = {
text: "space: ",
tooltip: "space control panel",
className: "right"
};
config.tasks.tiddlyspace = {
text: "",
tooltip: "",
content: "<<tiddler Backstage##Menu>>"
};
if(window.location.protocol == "file:") {
config.unplugged = true; // TODO: move into extensions.tiddly{web/space} namespace!?
config.tasks.space.content = "<<tiddler Backstage##SpaceUnplugged>>";
} else {
config.tasks.space.content = "<html><iframe frameBorder='0' src='/_space'></iframe></html>";
}
config.backstageTasks = ["tiddlyspace", "user", "space"];
config.messages.backstage.prompt = "";
// initialize state
var _show = backstage.show;
backstage.show = function() {
// selectively hide backstage tasks and tabs based on user status
var tasks = $("#backstageToolbar .backstageTask").show();
if(!config.unplugged) {
tweb.getUserInfo(function(user) {
if(user.anon) {
$(".task_user", tasks).hide();
tiddlyspace.disableTab(disabled_tabs_for_nonmembers);
} else {
$(".task_login", tasks).hide();
}
});
}
// display backstage
return _show.apply(this, arguments);
};
if(readOnly) {
tiddlyspace.disableTab(disabled_tabs_for_nonmembers);
}
var tasks = config.tasks;
var commonUrl = "/bags/common/tiddlers/%0";
// mock out renderAvatar if unavailable -- XXX: temporary hotfix, not a permanent solution!
tiddlyspace.renderAvatar = tiddlyspace.renderAvatar || function() {};
backstage.tiddlyspace = {
locale: {
member: "You are a member of this space.",
nonmember: "You are not a member of this space.",
loggedout: "You are currently logged out of TiddlySpace.",
unplugged: "You are unplugged."
},
checkSyncStatus: function(tiddler) {
var bs = backstage.tiddlyspace;
var t = store.filterTiddlers("[is[unsynced]]");
var unsyncedList = $("#backstage .tiddlyspaceMenu .unsyncedList");
if(t.length > 0 && !readOnly) {
bs.tweakMiddleButton("unsyncedIcon");
$("#backstage").addClass("unsyncedChanges");
} else {
bs.tweakMiddleButton();
$("#backstage").removeClass("unsyncedChanges");
}
refreshElements($("#backstage")[0]);
if(tiddler) {
var title = typeof(tiddler) === "string" ? tiddler : tiddler.title;
var el = story.getTiddler(title) || false;
if(el) {
refreshElements(el);
}
}
},
userButton: function(backstageArea, user) {
// override user button (logged in) to show username
var userBtn = $("[task=user]", backstageArea).empty();
if(config.unplugged && user.anon) {
$("<span />").text(tasks.user.unpluggedText).appendTo(userBtn);
} else if(!config.unplugged && user.anon) {
userBtn.remove();
} else {
$("<span />").text(tasks.user.text).appendTo(userBtn);
$("<span />").addClass("txtUserName").text(user.name).appendTo(userBtn);
var container = $("<span />").appendTo(userBtn)[0];
tiddlyspace.renderAvatar(container, user.name,
{ imageOptions: { imageClass:"userSiteIcon", height: 24, width: 24 },
labelOptions: { include: false } });
}
},
showButton: function() {
var showBtn = $("#backstageShow")[0];
var altText = $(showBtn).text();
$(showBtn).empty();
imageMacro.renderImage(showBtn, "backstage.svg",
{ altImage: commonUrl.format("backstage.png"), alt: altText});
},
hideButton: function() {
var hideBtn = $("#backstageHide")[0];
altText = $(hideBtn).text();
$(hideBtn).empty();
imageMacro.renderImage(hideBtn, "close.svg",
{ altImage: commonUrl.format("close.png"), alt: altText, width: 24, height: 24 });
},
middleButton: function(backstageArea, user) {
var bs = backstage.tiddlyspace;
var backstageToolbar = $("#backstageToolbar", backstageArea)[0];
if(config.unplugged) {
config.messages.memberStatus = bs.locale.unplugged;
} else if(!user.anon) {
config.messages.memberStatus = readOnly ? bs.locale.nonmember : bs.locale.member;
} else {
config.messages.memberStatus = bs.locale.loggedout;
}
// construct the tiddlyspace logo
var backstageLogo = $("#[task=tiddlyspace]").empty()[0];
$("<span />").addClass("iconContainer").appendTo(backstageLogo);
$('<span class="logoText"><span class="privateLightText">tiddly</span>' +
'<span class="publicLightText">space</span></span>').
appendTo(backstageLogo);
bs.tweakMiddleButton();
},
tweakMiddleButton: function(iconName) {
var backstageLogo = $("#[task=tiddlyspace] .iconContainer").empty()[0];
var backstageToolbar = $("#backstageToolbar");
var plugin = backstage.tiddlyspace;
if(!iconName) {
iconName = readOnly ? "publicIcon" : "privateAndPublicIcon";
}
config.macros.image.renderImage(backstageLogo, iconName, { width: 24, height: 24 });
},
spaceButton: function(backstageArea, user) {
// override space button to show SiteIcon
var btn = $("[task=space]", backstageArea).show();
if(user && user.anon && config.unplugged) {
btn.hide();
return;
}
btn.empty();
tiddlyspace.renderAvatar(btn[0], currentSpace,
{ imageOptions: { imageClass:"spaceSiteIcon", height: 24, width: 24 },
labelOptions: { include: false } });
$("<span />").text(tasks.space.text).appendTo(btn);
$("<span />").addClass("spaceName").text(currentSpace).appendTo(btn);
},
addClasses: function(backstageArea) {
var tasks = $(".backstageTask", backstageArea);
for(var i = 0; i < tasks.length; i++) {
var btn = $(tasks[i]);
var taskName = btn.attr("task");
btn.addClass("task_%0".format(taskName));
}
}
};
var _init = backstage.init;
backstage.init = function() {
_init.apply(this, arguments);
var init = function(user) {
var backstageArea = $("#backstageArea")[0];
var bs = backstage.tiddlyspace;
store.addNotification(null, bs.checkSyncStatus);
bs.userButton(backstageArea, user);
bs.showButton();
bs.hideButton();
bs.middleButton(backstageArea, user);
bs.spaceButton(backstageArea, user);
bs.addClasses(backstageArea); // for IE styling purposes
bs.checkSyncStatus();
};
tweb.getUserInfo(init);
};
var home = config.macros.homeLink = {
locale: {
linkText: "your home space"
},
handler: function(place) {
var container = $("<span />").appendTo(place)[0];
tweb.getUserInfo(function(user) {
if(!user.anon && user.name != currentSpace) {
createSpaceLink(container, user.name, null, home.locale.linkText);
}
});
}
};
config.macros.exportSpace = {
handler: function(place, macroName, params) {
var filename = params[0] ||
"/?download=%0.html".format(currentSpace);
$('<a class="button">download</a>'). // XXX: i18n
attr("href", filename).appendTo(place);
}
};
$.extend(config.messages, {
syncExplanation: "You are currently viewing an offline version of this TiddlySpace. From here you can sync your offline copy with the online version.",
syncListHeading: "Unsaved tiddlers listed below"});
config.extensions.ServerSideSavingPlugin.reportSuccess = function(msg, tiddler) {
backstage.tiddlyspace.checkSyncStatus(tiddler);
msg = config.extensions.ServerSideSavingPlugin.locale[msg];
var link = "/" + encodeURIComponent(tiddler.title);
displayMessage(msg.format([tiddler.title]), link);
};
})(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);
//}}}
/***
|''Description''|Sanitisation for dynamically pulling tiddlers into your space and displaying them|
!Notes
Works both inside and outside TiddlyWiki. Uses the HTML Sanitizer provided by the Google Caja project
(see http://code.google.com/p/google-caja/wiki/JsHtmlSanitizer for more on this), which is licensed under
an Apache License (see http://www.apache.org/licenses/LICENSE-2.0).
!Code
***/
//{{{
(function($) {
var cleanURL = function(url) {
var regexp = /^(?:http|https|mailto|ftp|irc|news):\/\//;
return (regexp.test(url)) ? url : null;
};
$.sanitize = function(html) {
return html_sanitize(html, cleanURL);
};
/*
* HTML Sanitizer, provided by Google Caja
*/
/* Copyright Google Inc.
* Licensed under the Apache Licence Version 2.0
* Autogenerated at Tue May 17 17:39:24 BST 2011
* @provides html4
*/var html4={};html4.atype={NONE:0,URI:1,URI_FRAGMENT:11,SCRIPT:2,STYLE:3,ID:4,IDREF:5,IDREFS:6,GLOBAL_NAME:7,LOCAL_NAME:8,CLASSES:9,FRAME_TARGET:10},html4.ATTRIBS={"*::class":9,"*::dir":0,"*::id":4,"*::lang":0,"*::onclick":2,"*::ondblclick":2,"*::onkeydown":2,"*::onkeypress":2,"*::onkeyup":2,"*::onload":2,"*::onmousedown":2,"*::onmousemove":2,"*::onmouseout":2,"*::onmouseover":2,"*::onmouseup":2,"*::style":3,"*::title":0,"a::accesskey":0,"a::coords":0,"a::href":1,"a::hreflang":0,"a::name":7,"a::onblur":2,"a::onfocus":2,"a::rel":0,"a::rev":0,"a::shape":0,"a::tabindex":0,"a::target":10,"a::type":0,"area::accesskey":0,"area::alt":0,"area::coords":0,"area::href":1,"area::nohref":0,"area::onblur":2,"area::onfocus":2,"area::shape":0,"area::tabindex":0,"area::target":10,"bdo::dir":0,"blockquote::cite":1,"br::clear":0,"button::accesskey":0,"button::disabled":0,"button::name":8,"button::onblur":2,"button::onfocus":2,"button::tabindex":0,"button::type":0,"button::value":0,"canvas::height":0,"canvas::width":0,"caption::align":0,"col::align":0,"col::char":0,"col::charoff":0,"col::span":0,"col::valign":0,"col::width":0,"colgroup::align":0,"colgroup::char":0,"colgroup::charoff":0,"colgroup::span":0,"colgroup::valign":0,"colgroup::width":0,"del::cite":1,"del::datetime":0,"dir::compact":0,"div::align":0,"dl::compact":0,"font::color":0,"font::face":0,"font::size":0,"form::accept":0,"form::action":1,"form::autocomplete":0,"form::enctype":0,"form::method":0,"form::name":7,"form::onreset":2,"form::onsubmit":2,"form::target":10,"h1::align":0,"h2::align":0,"h3::align":0,"h4::align":0,"h5::align":0,"h6::align":0,"hr::align":0,"hr::noshade":0,"hr::size":0,"hr::width":0,"iframe::align":0,"iframe::frameborder":0,"iframe::height":0,"iframe::marginheight":0,"iframe::marginwidth":0,"iframe::width":0,"img::align":0,"img::alt":0,"img::border":0,"img::height":0,"img::hspace":0,"img::ismap":0,"img::name":7,"img::src":1,"img::usemap":11,"img::vspace":0,"img::width":0,"input::accept":0,"input::accesskey":0,"input::align":0,"input::alt":0,"input::autocomplete":0,"input::checked":0,"input::disabled":0,"input::ismap":0,"input::maxlength":0,"input::name":8,"input::onblur":2,"input::onchange":2,"input::onfocus":2,"input::onselect":2,"input::readonly":0,"input::size":0,"input::src":1,"input::tabindex":0,"input::type":0,"input::usemap":11,"input::value":0,"ins::cite":1,"ins::datetime":0,"label::accesskey":0,"label::for":5,"label::onblur":2,"label::onfocus":2,"legend::accesskey":0,"legend::align":0,"li::type":0,"li::value":0,"map::name":7,"menu::compact":0,"ol::compact":0,"ol::start":0,"ol::type":0,"optgroup::disabled":0,"optgroup::label":0,"option::disabled":0,"option::label":0,"option::selected":0,"option::value":0,"p::align":0,"pre::width":0,"q::cite":1,"select::disabled":0,"select::multiple":0,"select::name":8,"select::onblur":2,"select::onchange":2,"select::onfocus":2,"select::size":0,"select::tabindex":0,"table::align":0,"table::bgcolor":0,"table::border":0,"table::cellpadding":0,"table::cellspacing":0,"table::frame":0,"table::rules":0,"table::summary":0,"table::width":0,"tbody::align":0,"tbody::char":0,"tbody::charoff":0,"tbody::valign":0,"td::abbr":0,"td::align":0,"td::axis":0,"td::bgcolor":0,"td::char":0,"td::charoff":0,"td::colspan":0,"td::headers":6,"td::height":0,"td::nowrap":0,"td::rowspan":0,"td::scope":0,"td::valign":0,"td::width":0,"textarea::accesskey":0,"textarea::cols":0,"textarea::disabled":0,"textarea::name":8,"textarea::onblur":2,"textarea::onchange":2,"textarea::onfocus":2,"textarea::onselect":2,"textarea::readonly":0,"textarea::rows":0,"textarea::tabindex":0,"tfoot::align":0,"tfoot::char":0,"tfoot::charoff":0,"tfoot::valign":0,"th::abbr":0,"th::align":0,"th::axis":0,"th::bgcolor":0,"th::char":0,"th::charoff":0,"th::colspan":0,"th::headers":6,"th::height":0,"th::nowrap":0,"th::rowspan":0,"th::scope":0,"th::valign":0,"th::width":0,"thead::align":0,"thead::char":0,"thead::charoff":0,"thead::valign":0,"tr::align":0,"tr::bgcolor":0,"tr::char":0,"tr::charoff":0,"tr::valign":0,"ul::compact":0,"ul::type":0},html4.eflags={OPTIONAL_ENDTAG:1,EMPTY:2,CDATA:4,RCDATA:8,UNSAFE:16,FOLDABLE:32,SCRIPT:64,STYLE:128},html4.ELEMENTS={a:0,abbr:0,acronym:0,address:0,applet:16,area:2,b:0,base:18,basefont:18,bdo:0,big:0,blockquote:0,body:49,br:2,button:0,canvas:0,caption:0,center:0,cite:0,code:0,col:2,colgroup:1,dd:1,del:0,dfn:0,dir:0,div:0,dl:0,dt:1,em:0,fieldset:0,font:0,form:0,frame:18,frameset:16,h1:0,h2:0,h3:0,h4:0,h5:0,h6:0,head:49,hr:2,html:49,i:0,iframe:4,img:2,input:2,ins:0,isindex:18,kbd:0,label:0,legend:0,li:1,link:18,map:0,menu:0,meta:18,nobr:0,noframes:20,noscript:20,object:16,ol:0,optgroup:0,option:1,p:1,param:18,pre:0,q:0,s:0,samp:0,script:84,select:0,small:0,span:0,strike:0,strong:0,style:148,sub:0,sup:0,table:0,tbody:1,td:1,textarea:8,tfoot:1,th:1,thead:1,title:24,tr:1,tt:0,u:0,ul:0,"var":0},html4.ueffects={NOT_LOADED:0,SAME_DOCUMENT:1,NEW_DOCUMENT:2},html4.URIEFFECTS={"a::href":2,"area::href":2,"blockquote::cite":0,"body::background":1,"del::cite":0,"form::action":2,"img::src":1,"input::src":1,"ins::cite":0,"q::cite":0},html4.ltypes={UNSANDBOXED:2,SANDBOXED:1,DATA:0},html4.LOADERTYPES={"a::href":2,"area::href":2,"blockquote::cite":2,"body::background":1,"del::cite":2,"form::action":2,"img::src":1,"input::src":1,"ins::cite":2,"q::cite":2};var html=function(a){function x(b,c,d){var e=[];w(function(b,e){for(var f=0;f<e.length;f+=2){var g=e[f],h=e[f+1],i=null,j;if((j=b+"::"+g,a.ATTRIBS.hasOwnProperty(j))||(j="*::"+g,a.ATTRIBS.hasOwnProperty(j)))i=a.ATTRIBS[j];if(i!==null)switch(i){case a.atype.NONE:break;case a.atype.SCRIPT:case a.atype.STYLE:h=null;break;case a.atype.ID:case a.atype.IDREF:case a.atype.IDREFS:case a.atype.GLOBAL_NAME:case a.atype.LOCAL_NAME:case a.atype.CLASSES:h=d?d(h):h;break;case a.atype.URI:h=c&&c(h);break;case a.atype.URI_FRAGMENT:h&&"#"===h.charAt(0)?(h=d?d(h):h,h&&(h="#"+h)):h=null;break;default:h=null}else h=null;e[f+1]=h}return e})(b,e);return e.join("")}function w(b){var c,d;return v({startDoc:function(a){c=[],d=!1},startTag:function(e,f,g){if(!d){if(!a.ELEMENTS.hasOwnProperty(e))return;var h=a.ELEMENTS[e];if(h&a.eflags.FOLDABLE)return;if(h&a.eflags.UNSAFE){d=!(h&a.eflags.EMPTY);return}f=b(e,f);if(f){h&a.eflags.EMPTY||c.push(e),g.push("<",e);for(var i=0,j=f.length;i<j;i+=2){var k=f[i],l=f[i+1];l!==null&&l!==void 0&&g.push(" ",k,'="',r(l),'"')}g.push(">")}}},endTag:function(b,e){if(d)d=!1;else{if(!a.ELEMENTS.hasOwnProperty(b))return;var f=a.ELEMENTS[b];if(!(f&(a.eflags.UNSAFE|a.eflags.EMPTY|a.eflags.FOLDABLE))){var g;if(f&a.eflags.OPTIONAL_ENDTAG)for(g=c.length;--g>=0;){var h=c[g];if(h===b)break;if(!(a.ELEMENTS[h]&a.eflags.OPTIONAL_ENDTAG))return}else for(g=c.length;--g>=0;)if(c[g]===b)break;if(g<0)return;for(var i=c.length;--i>g;){var h=c[i];a.ELEMENTS[h]&a.eflags.OPTIONAL_ENDTAG||e.push("</",h,">")}c.length=g,e.push("</",b,">")}}},pcdata:function(a,b){d||b.push(a)},rcdata:function(a,b){d||b.push(a)},cdata:function(a,b){d||b.push(a)},endDoc:function(a){for(var b=c.length;--b>=0;)a.push("</",c[b],">");c.length=0}})}function v(c){return function(d,e){d=String(d);var f=null,g=!1,h=[],j=void 0,l=void 0,m=void 0;c.startDoc&&c.startDoc(e);while(d){var n=d.match(g?t:u);d=d.substring(n[0].length);if(g){if(n[1]){var o=b(n[1]),p;if(n[2]){var q=n[3];switch(q.charCodeAt(0)){case 34:case 39:q=q.substring(1,q.length-1)}p=k(i(q))}else p=o;h.push(o,p)}else if(n[4]){l!==void 0&&(m?c.startTag&&c.startTag(j,h,e):c.endTag&&c.endTag(j,e));if(m&&l&(a.eflags.CDATA|a.eflags.RCDATA)){f===null?f=b(d):f=f.substring(f.length-d.length);var r=f.indexOf("</"+j);r<0&&(r=d.length),l&a.eflags.CDATA?c.cdata&&c.cdata(d.substring(0,r),e):c.rcdata&&c.rcdata(s(d.substring(0,r)),e),d=d.substring(r)}j=l=m=void 0,h.length=0,g=!1}}else if(n[1])c.pcdata&&c.pcdata(n[0],e);else if(n[3])m=!n[2],g=!0,j=b(n[3]),l=a.ELEMENTS.hasOwnProperty(j)?a.ELEMENTS[j]:void 0;else if(n[4])c.pcdata&&c.pcdata(n[4],e);else if(n[5]&&c.pcdata)switch(n[5]){case"<":c.pcdata("<",e);break;case">":c.pcdata(">",e);break;default:c.pcdata("&",e)}}c.endDoc&&c.endDoc(e)}}function s(a){return a.replace(m,"&$1").replace(n,"<").replace(o,">")}function r(a){return a.replace(l,"&").replace(n,"<").replace(o,">").replace(p,""").replace(q,"=")}function k(a){return a.replace(j,g)}function i(a){return a.replace(h,"")}function g(a,b){return f(b)}function f(a){a=b(a);if(c.hasOwnProperty(a))return c[a];var f=a.match(d);if(f)return String.fromCharCode(parseInt(f[1],10));if(!!(f=a.match(e)))return String.fromCharCode(parseInt(f[1],16));return""}var b;"script"==="SCRIPT".toLowerCase()?b=function(a){return a.toLowerCase()}:b=function(a){return a.replace(/[A-Z]/g,function(a){return String.fromCharCode(a.charCodeAt(0)|32)})};var c={lt:"<",gt:">",amp:"&",nbsp:"240",quot:'"',apos:"'"},d=/^#(\d+)$/,e=/^#x([0-9A-Fa-f]+)$/,h=/\0/g,j=/&(#\d+|#x[0-9A-Fa-f]+|\w+);/g,l=/&/g,m=/&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi,n=/</g,o=/>/g,p=/\"/g,q=/\=/g,t=new RegExp("^\\s*(?:(?:([a-z][a-z-]*)(\\s*=\\s*(\"[^\"]*\"|'[^']*'|(?=[a-z][a-z-]*\\s*=)|[^>\"'\\s]*))?)|(/?>)|[\\s\\S][^a-z\\s>]*)","i"),u=new RegExp("^(?:&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);|<!--[\\s\\S]*?-->|<!\\w[^>]*>|<\\?[^>*]*>|<(/)?([a-z][a-z0-9]*)|([^<&>]+)|([<&>]))","i");return{escapeAttrib:r,makeHtmlSanitizer:w,makeSaxParser:v,normalizeRCData:s,sanitize:x,unescapeEntities:k}}(html4),html_sanitize=html.sanitize
// stop here if we're not in TiddlyWiki
// XXX: is this the correct way of checking for TiddlyWiki?
if (!window.TiddlyWiki || !window.store || !store instanceof TiddlyWiki) {
return;
}
var tiddlyspace = config.extensions.tiddlyspace;
var _subWikify = Wikifier.prototype.subWikify;
var cleanedTitle = 'This section has been cleaned of any potentially harmful code';
var replaceFunctions = {
html: function(w) {
var sanitizedHTML, spanEl;
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
sanitizedHTML = $.sanitize(lookaheadMatch[1]);
spanEl = createTiddlyElement(w.output, 'span', null, 'sanitized');
spanEl.innerHTML = sanitizedHTML;
spanEl.setAttribute('title', cleanedTitle);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
},
customFormat: function(w) {
switch(w.matchText) {
case '@@':
var e = createTiddlyElement(w.output, 'span');
var styles = config.formatterHelpers.inlineCssHelper(w);
if (styles.length === 0) {
e.className = 'marked';
}
w.subWikifyTerm(e, /(@@)/mg);
break;
case '{{':
var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch) {
w.nextMatch = lookaheadRegExp.lastIndex;
e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
w.subWikifyTerm(e,/(\}\}\})/mg);
}
break;
}
}
};
Wikifier.prototype.subWikify = function(output, terminator) {
var tid = this.tiddler,
spaceName = tiddlyspace.currentSpace.name,
tidSpace, recipeName, stripped;
try {
recipeName = tid.fields['server.recipe'] ||
tid.fields['server.workspace'];
tidSpace = tiddlyspace.resolveSpaceName(recipeName);
if (tidSpace !== spaceName) {
// external tiddler, so replace dangerous formatters
stripped = stripHTML(tid, this.formatter);
}
} catch(e) {
// do nothing. There's no tiddler, so assume it's safe (?!?!?)
}
_subWikify.apply(this, arguments);
if (stripped) {
// change back to the original function
unstripHTML(stripped, this.formatter);
}
};
// replace potentially unsafe formatters with versions that strip bad HTML/CSS
var stripHTML = function(tid, formatter) {
var popped = {}, _handler;
for (var i = 0; i < formatter.formatters.length; i++) {
var f = formatter.formatters[i];
if (replaceFunctions[f.name]) {
_handler = f.handler;
popped[f.name] = _handler;
f.handler = replaceFunctions[f.name];
}
};
return popped;
};
// put the original formatters back where they belong
var unstripHTML = function(stripped, formatter) {
for (var i = 0; i < formatter.formatters.length; i++) {
var f = formatter.formatters[i];
if (stripped[f.name]) {
f.handler = stripped[f.name];
}
};
};
})(jQuery);
//}}}
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
<!--{{{-->
<link rel="shortcut icon" href="/recipes/cdent-st_public/tiddlers/favicon.ico" />
<link href="/bags/cdent-st_public/tiddlers.atom" rel="alternate"
type="application/atom+xml" title="cdent-st's public feed" />
<!--}}}-->
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
/***
|''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);
//}}}
/***
|''Name''|ImageMacroPlugin|
|''Version''|0.9.4|
|''Description''|Allows the rendering of svg images in a TiddlyWiki|
|''Author''|Osmosoft|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''Notes''|Currently only works in modern browsers (not IE)|
|''Requires''|BinaryTiddlersPlugin|
!Usage
{{{<<image SVG>>}}} will render the text of the tiddler with title SVG as an SVG image (but not in ie where it will fail silently)
!!Parameters
width/height: specify width/height parameters
link: make the image link to a given location
tiddlyLink: link to a tiddler
!Notes
Binary tiddlers in TiddlyWeb when passed through the wikifier will be shown as images.
eg. {{{<<view text wikified>>}}} on a binary tiddler will show the image.
{{{<<view fieldname image>>}}}
will render the value of the tiddler field 'fieldname' as an image. This field can contain a tid
{{{<<image SiteIcon>>}}}
will create an image tag where the tiddler has content type beginning image and not ending +xml
will attempt to create svg object in other scenarios
{{{<<image /photos/x.jpg>>}}}
will create an image tag with src /photos/x.jpg as long as there is not a tiddler called /photos/x.jpg in
which case it will render that tiddler as an image. Note for the case of svg files it will attempt to render as an svg if possible via the image
tag. It doesn't embed the svg in the dom for security reasons as svg code can contain javascript.
!Code
***/
//{{{
(function($) {
var macro = config.macros.image = {
shim: "/bags/common/tiddlers/shim",
ieVersion: config.browser.isIE ? parseInt(config.browser.ieVersion[1], 10) : false,
svgns: "http://www.w3.org/2000/svg",
xlinkns: "http://www.w3.org/1999/xlink",
svgAvailable: document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
_fixPrefix: 1,
_external_cache: {},
_image_tag_cache: {},
_image_dimensions: {},
locale: {
badImage: "This image cannot be displayed."
},
handler: function(place, macroName, params, wikifier, paramString, tiddler){
var imageSource = params[0];
// collect named arguments
var args = macro.getArguments(paramString, params);
this.renderImage(place, imageSource, args);
},
init: function() {
var startupImages = store.getTaggedTiddlers("systemImage");
var place = $("<div />").attr("id", "systemImageArea").appendTo("body").hide()[0];
for(var i = 0; i < startupImages.length; i++) {
var image = startupImages[i];
macro.renderImage(place, image.title, { idPrefix: "" });
}
var data = new Image();
data.onload = function() {
// note ie 8 only supports data uris up to 32k so cannot be relied on
macro.supportsDataUris = this.width != 1 || this.height != 1 ? false : true;
macro.supportsDataUris = macro.ieVersion && macro.ieVersion < 9 ? false : macro.supportsDataUris;
};
data.onerror = data.onload;
data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
},
refreshImage: function(src) {
var elements = macro._image_tag_cache[src] ? macro._image_tag_cache[src] : [];
if(macro._image_dimensions[src]) {
macro._image_dimensions[src] = false;
}
for(var i = 0; i < elements.length; i++) {
var el = $(elements[i]);
var newSrc = "%0?nocache=%1".format(src, Math.random());
el.attr("src", newSrc); // force reload
}
},
isBinaryImageType: function(contentType) {
return (contentType && contentType.indexOf("image") === 0 &&
contentType.indexOf("+xml") != contentType.length - 4) ? true : false;
},
isImageTiddler: function(tiddler) {
return macro.isSVGTiddler(tiddler) || macro.isBinaryImageTiddler(tiddler);
},
isSVGTiddler: function(tiddler) {
var type = tiddler ? tiddler.fields['server.content-type'] : false;
return type == "image/svg+xml";
},
isBinaryImageTiddler: function(tiddler) {
return macro.isBinaryImageType(tiddler.fields['server.content-type']);
},
renderImage: function(place, imageSource, options) {
var imageTiddler = store.getTiddler(imageSource);
var container;
var classes = ["image"];
if(options.link) {
classes = classes.concat(["imageLink", "externalLink"]);
container = $("<a />").attr("href", options.link).appendTo(place)[0];
} else if(options.tiddlyLink) {
classes.push("imageLink");
container = createTiddlyLink(place, options.tiddlyLink, false);
} else {
container = $("<span />").appendTo(place)[0];
}
$(container).addClass(classes.join(" "));
options = options ? options : {};
if(imageTiddler && macro.isBinaryImageTiddler(imageTiddler)) { // handle the case where we have an image url
return macro._renderBinaryImageTiddler(container, imageTiddler, options);
} else if(imageTiddler){ // handle the case where we have a tiddler
return macro._renderSVGTiddler(container, imageTiddler, options);
} else { // we have a string representing a url
return macro._renderBinaryImageUrl(container, imageSource, options);
}
},
_renderAlternateText: function(container, options) {
var img;
var src = options.src || "";
if(options.width && options.height) {
img = $("<img />").attr("src", src).addClass("svgImageText").attr("width", options.width).
attr("height", options.height).appendTo(container);
}
var alt = options.alt;
if(img && alt) {
img.attr("alt", alt).attr("title", alt);
} else if(alt) {
$(container).addClass("svgImageText").text(alt);
}
macro._image_tag_cache[src] = img;
},
_renderSVGTiddler: function(place, tiddler, options) {
if(!options) {
options = {};
}
merge(options, { tiddler: tiddler, fix: true});
if(macro.svgAvailable) {
this._importSVG(place, options); // display the svg
} else if(options.altImage) {
var image = options.altImage;
delete options.altImage;
this._renderBinaryImageUrl(place, image, options);
} else {
this._renderAlternateText(place, options); // instead of showing the image show the alternate text.
}
},
_renderBinaryImageTiddler: function(place, tiddler, options) {
var resourceURI;
var fields = tiddler.fields;
if(fields["server.type"] == "tiddlyweb") { // construct an accurate url for the resource
resourceURI = "%0/%1/tiddlers/%2".format(config.defaultCustomFields["server.host"],
fields["server.workspace"], encodeURI(fields["server.title"]));
} else { // guess the url for the resource
resourceURI = tiddler.title;
}
var ctype = fields["server.content-type"] || tiddler.type;
var text = tiddler.text;
if(macro.supportsDataUris && ctype && text.indexOf("<html") == -1) {
var uri = "data:%0;base64,%1".format(ctype, text);
options.src = resourceURI;
return macro._renderBinaryImageUrl(place, uri, options);
} else if(options.src) {
return macro._renderBinaryImageUrl(place, options.src, options);
} else {
return macro._renderBinaryImageUrl(place, resourceURI, options);
}
},
_renderImageTag: function(container, src, width, height, options) {
var img;
img = $("<img />").appendTo(container);
if(height) {
img.attr("height", height);
}
if(width) {
img.attr("width", width);
}
if(macro.ieVersion && macro.ieVersion < 7 && macro.shim && options.ie6png) {
$(img).css({width: userW, height: userH,
filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%0', sizingMethod='scale')".format(src)
}).attr("src", macro.shim);
} else {
img.attr("src", src);
}
if(!macro._image_tag_cache[options.srcUrl]) {
macro._image_tag_cache[options.srcUrl] = [];
}
img = $(img).addClass(options.imageClass)[0];
macro._image_tag_cache[options.srcUrl].push(img);
return img;
},
_getDimensions: function(realDimensions, reqDimensions, preserve) {
var w = realDimensions.width;
var h = realDimensions.height;
var reqh = reqDimensions.height;
var reqw = reqDimensions.width;
var finalw = w, finalh = h;
var ratiow = reqw / w, ratioh = reqh / h;
var scaledw = ratioh * w;
var scaledh = ratiow * h;
if(!reqw && reqh) {
finalw = scaledw;
finalh = reqh;
} else if(reqw && !reqh) {
finalw = reqw;
finalh = scaledh;
} else if(reqh && reqw) {
var preserveWidth = w > h ? true : false;
if(preserve) {
if(preserveWidth && scaledh < reqh) {
finalh = scaledh;
finalw = reqw;
} else {
finalh = reqh;
finalw = scaledw;
}
} else {
finalw = reqw;
finalh = reqh;
}
}
return { width: parseInt(finalw, 10), height: parseInt(finalh, 10) };
},
_renderBinaryImageUrl: function(container, src, options) {
var srcUrl = options.src ? options.src : src;
srcUrl = srcUrl.indexOf("/") === -1 ? "/%0".format(srcUrl) : srcUrl; // for IE.
var image_dimensions = macro._image_dimensions[srcUrl];
var image = new Image(); // due to weird scaling issues where you use just a width or just a height
var createImageTag = function(dimensions, error) {
if(error) {
var altImage = options.altImage;
if(altImage) {
delete options.altImage;
macro._renderBinaryImageUrl(container, altImage, options);
} else {
options.src = src;
macro._renderAlternateText(container, options);
}
} else {
var dim = macro._getDimensions(dimensions, {
width: options.width, height: options.height }, options.preserveAspectRatio);
options.srcUrl = srcUrl;
macro._renderImageTag(container, src, dim.width, dim.height, options);
}
};
if(!image_dimensions) {
image.onload = function() {
var dimensions = { width: image.width, height: image.height};
macro._image_dimensions[srcUrl] = dimensions;
createImageTag(dimensions);
};
image.onerror = function() {
createImageTag(null, true);
};
image.src = src;
} else {
createImageTag(image_dimensions);
}
},
_generateIdPrefix: function(){
return "twsvgfix_" + (this._fixPrefix++).toString() + "_";
},
_fixSVG: function(childNodes, idPrefix) {
var urlPattern = /url\(\#([^\)]*)\)*/ig;
var fixes = [
{ attr: "id", pattern: /^(.*)$/ig },
{ attr: "href", namespace: macro.xlinkns, pattern: /^#(.*)$/ig }
];
var url_fixes = ["filter", "fill", "mask", "stroke", "style"];
for(var i = 0; i < url_fixes.length; i++) {
fixes.push({ attr: url_fixes[i], pattern: urlPattern });
}
for(var t = 0; t < childNodes.length; t++) {
var node = childNodes[t];
for(var a = 0; a < fixes.length; a++) {
var fix = fixes[a];
var attr = fix.attr;
var ns = fix.namespace || "";
if(node.hasAttributeNS && node.hasAttributeNS(ns, attr)) {
var v = node.getAttributeNS(ns, attr);
fix.pattern.lastIndex = 0;
var match = fix.pattern.exec(v);
if(match) {
// Make sure replacement string doesn't contain any single dollar signs
var toReplace = match[1];
if(toReplace.indexOf(idPrefix) !== 0 && toReplace.indexOf("twglobal_") !== 0) {
var replacement = (idPrefix + toReplace).replace("$", "$$$$");
v = v.replace(match[1], replacement);
}
node.setAttributeNS(ns, attr,v);
}
}
}
var children = node.childNodes;
if(children.length > 0) {
this._fixSVG(children, idPrefix);
}
}
},
_importSVG: function(place, options){
options = options ? options : {};
var svgDoc, tiddlerText = options.tiddler.text;
if (window.DOMParser) {
svgDoc = new DOMParser().parseFromString(tiddlerText, "application/xml").documentElement;
var idPrefix = options.idPrefix || this._generateIdPrefix();
this._fixSVG([svgDoc], idPrefix);
var el = document.importNode(svgDoc, true);
var svgHolder = document.createElementNS(macro.svgns,"svg");
var width = options.width;
var height = options.height;
if(width || height) {
if(width && height) { // set view box of containing svg element based on the svg viewbox and width and height.
var viewBox = el.getAttribute("viewBox");
var topLeft = "0 0";
if(viewBox) {
topLeft = viewBox.replace(/([0-9]*) +([0-9]*) +([0-9]*) +([0-9]*) */gi,"$1 $2");
}
svgHolder.setAttributeNS(macro.svgns, "viewBox", "0 0 %0 %1".format(width, height));
} else {
if(!width) {
width = el.getAttribute("width");
}
if(!height) {
height = el.getAttribute("height");
}
}
svgHolder.setAttribute("width", width);
svgHolder.setAttribute("height", height);
el.setAttribute("width", "100%");
el.setAttribute("height", "100%");
svgHolder.setAttribute("class", "svgImage svgIcon %0".format(options.imageClass || ""));
svgHolder.appendChild(el);
place.appendChild(svgHolder);
}
else {
var existing = el.className ? el.className.baseVal : "";
el.setAttribute("class","svgImage %0".format(existing));
place.appendChild(el);
}
// if a tiddler attribute is set this is read as a link
$("[tiddler], [tiddlyLink]", place).attr("refresh", "link").click(function(ev) {
var tiddler = $(ev.target).attr("tiddlyLink");
if(tiddler) {
story.displayTiddler(ev.target, tiddler);
}
});
}
},
getArguments: function(paramString, params) {
var args = paramString.parseParams("name", null, true, false, true)[0];
var options = {};
for(var id in args) {
if(true) {
var p = args[id];
if(id == "def") {
options[id] = p;
} else {
options[id] = p[0];
}
}
}
var width = isNaN(params[1]) ? false : parseInt(params[1], 10);
var height = isNaN(params[2]) ? false : parseInt(params[2], 10);
options.width = macro.lookupArgument(options, "width", width);
options.height = macro.lookupArgument(options, "height", height);
options.preserveAspectRatio = args.preserveAspectRatio &&
args.preserveAspectRatio[0] == "yes" ? true : false;
options.tiddlyLink = macro.lookupArgument(options, "tiddlyLink", false);
options.link = macro.lookupArgument(options, "link", false);
return options;
},
lookupArgument: function(args, id, ifEmpty) {
return args[id] ? args[id] : ifEmpty;
}
};
// update views
var _oldwikifiedview = config.macros.view.views.wikified;
// update wikifier to check tiddler type before rendering
merge(config.macros.view.views, {
wikified: function(value, place, params, wikifier, paramString, tiddler) {
if(macro.isImageTiddler(tiddler) && params[0] == "text") {
var newplace = $("<div />").addClass("wikifiedImage").appendTo(place)[0];
macro.renderImage(newplace, tiddler.title, { alt: macro.locale.badImage });
} else {
_oldwikifiedview.apply(this, arguments);
}
},
image: function(value, place, params, wikifier, paramString, tiddler) {
// a field can point to another tiddler whereas text is the current tiddler.
var title = params[0] == "text" ? tiddler.title : value;
var args = macro.getArguments(paramString, params);
macro.renderImage(place, title, args);
}
});
config.shadowTiddlers.StyleSheetImageMacro = [".wikifiedImage svg, .wikifiedImage .image { width: 80%; }",
".svgImageText { background-color:[[ColorPalette::Error]]; color:#ddd; display: inline-block; }",
"span.svgImageText { display: inline-block; overflow: hidden; }"
].join("");
store.addNotification("StyleSheetImageMacro", refreshStyles);
})(jQuery);
//}}}
/*{{{*/
Background: #def7dd
Foreground: #082107
PrimaryPale: #e2f8e1
PrimaryLight: #87e482
PrimaryMid: #33c82b
PrimaryDark: #020b02
SecondaryPale: #f8e9e1
SecondaryLight: #e4a682
SecondaryMid: #c8642b
SecondaryDark: #0b0502
TertiaryPale: #e1ecf8
TertiaryLight: #82b0e4
TertiaryMid: #2b74c8
TertiaryDark: #02060b
Error: #f88
ColorPaletteParameters: HSL([117|85], [0.6437475255224854],[0.026460158405825496|0.9294952515047044])
/*}}}*/
Unless you're delighted with the default scheme you can make some quick changes by generating a new random color palette, hit this button to cycle through some alternatives.
<<RandomColorPaletteButton saturation_pale:0.67 saturation_light:0.53
saturation_mid:0.43 saturation_dark:0.06 pale:0.99 light:0.85 mid:0.5 dark:0.31>>
<html><hr><html>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="14pt"
height="14pt"
viewBox="918 510 14 14"
id="svg3070">
<metadata
id="metadata3089">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3072">
<radialGradient
cx="0"
cy="0"
r="1"
id="Gradient"
gradientUnits="userSpaceOnUse">
<stop
id="stop3075"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3077"
style="stop-color:#2b2b2b;stop-opacity:1"
offset="1" />
</radialGradient>
<radialGradient
id="Obj_Gradient"
xlink:href="#Gradient"
gradientTransform="matrix(11.473944,0,0,11.473944,922.3752,513.7837)" />
</defs>
<g
id="g3080"
style="fill:none;stroke:none">
<g
id="g3082">
<path
d="m 929.6952,512.9018 c -2.5384,-2.53843 -6.654,-2.53843 -9.1924,0 -2.5384,2.5384 -2.5384,6.654 0,9.19238 2.5384,2.53839 6.654,2.53839 9.1924,0 2.5384,-2.53838 2.5384,-6.65398 0,-9.19238 m -4.5962,2.8407 2.07733,-2.07734 1.75547,1.75549 -2.0773,2.07735 2.0773,2.07732 -1.75547,1.75548 -2.07733,-2.07732 -2.07733,2.07732 -1.75547,-1.75548 2.0773,-2.07732 -2.0773,-2.07735 1.75547,-1.75549 z"
id="path3084"
style="fill:url(#Obj_Gradient)" />
<path
d="m 927.61447,515.38354 a 4.51205,4.2590378 0 1 1 -9.0241,0 4.51205,4.2590378 0 1 1 9.0241,0 z"
transform="matrix(1.0218069,0,0,1.0462046,-18.063694,-21.648443)"
id="path2394"
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</g>
</svg>
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
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 40 40"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><defs><linearGradient id="lG3826" x1="7.0996" gradientUnits="userSpaceOnUse" y1="18.829" gradientTransform="matrix(1.5858347,0,0,1.8078238,1098.1851,351.13716)" x2="1.5461" y2="-0.95166"><stop stop-color="#000" offset="0"/><stop stop-color="#9c9b99" offset="1"/></linearGradient><linearGradient id="lG3828" y2="372.44" gradientUnits="userSpaceOnUse" y1="375.7" x2="1111.7" x1="1097.7"><stop style="stop-color:#ac9393;" offset="0"/><stop style="stop-color:#c8b7b7;" offset="1"/></linearGradient></defs><g transform="translate(-1080.9375,-357.3329)"><path style="stroke-width:0;stroke-miterlimit:4;fill:url(#lG3826);" d="m1080.9,357.32,39.996-0.0426-0.01,40.008c-15.507-25.519-15.36-25.95-39.988-39.965z"/><path style="stroke-dashoffset:0;stroke:#7aa3be;stroke-linecap:round;stroke-miterlimit:4;stroke-width:1.49999988;fill:#c1e6fd;" d="m1091.9,363.55c6.5716-6.4925,16.576-7.3925,23.147-0.90003,6.5717,6.4925,6.5717,17.019,0,23.511-4.4424-8.6113-12.288-15.713-23.147-22.611z"/><path style="stroke-dashoffset:0;stroke:#ce81b0;stroke-linecap:round;stroke-miterlimit:4;stroke-width:1.5;fill:#f4c4e2;" d="m1110.2,367.62c3.217,3.2168,3.217,8.4323,0,11.649-3.8194-4.2357-8.3307-8.1824-11.649-11.649,3.217-3.2168,8.4325-3.2168,11.649-0.00002z"/><path style="stroke-linejoin:bevel;stroke:#000000;stroke-linecap:round;stroke-dasharray:none;stroke-miterlimit:4;stroke-width:0.80000001;fill:url(#lG3828);" d="m1081,357.34c18.79,6.4752,32.53,16.56,39.894,39.892-11.19-17.028-14.878-19.19-27.352-14.96,6.2984-12.098,3.9371-13.19-12.542-24.932z"/></g></svg>
[img[http://static.flickr.com/31/64401018_f93d486d4b_m.jpg][http://www.flickr.com/photos/cdent/64401018/]]
! Things to Do
I took a moment to post [[Open Opportunities to Hack]].
! Wikis are Narrative
The other day [[BillSeitz]] said I was [[thinking about Structured Data|http://webseitz.fluxent.com/wiki/z2007-01-09-DentStructuredWiki]] in wikis and while I guess this is true I bristled a bit because one of the reasons I like wikis is that they start, for me, from a presumption that the critical aspect of knowledge is human narrative.
Things like [[magic tags]] add structure to content, but they preserve the core of the data object: A page of text that a person can come along and edit, as text, just like they would any other wiki page.
It's easy to imagine wikis becoming an application platform of some kind (c.f Jotspot) but I get a little nervous around such things. Wikis are great because, at base, they contain people's stories. That might be software patterns, adventures in Antarctica or the ramblings of a project team. Those are all stories.
When things like [[magic tags]], [[microformats]] or [[Wiki Objects]] are used, those things augment the narrative experience of the wiki, but do not replace it. If you don't need or want the editable narrative part, there really isn't much reason to use the wiki.
When we first did [[Purple Numbers]] for [[Purplewiki]] there was a contingent of people who complained that raising the status of the blocks of content within a page was damaging to the holistic value of ThePage. I think this remains a pretty valid complaint. A thing which has a name (for example, a page) is a character or participant in an emerging narration. A granularly addressable paragraph, that's something different. It sort of throws a small wrench. I'd like to think the benefits of addressability and reusability that Purple Numbers can provide augment and not lessen the narrative power, but I'm not entirely confident of that.
! Opening the Code
(I am not a lawyer, nor am I in charge of how source code is managed at Socialtext, so there's nothing here other than an idea. There is no promise that this will happen.)
As you might imagine, the process of taking an effectively closed source product open is a bit bewildering. A center of questions and concerns is: How do you manage code that for some reason needs to stay closed (lawyers, customer requirements, I can't really speculate about the reasons, but it is a definite concern)?
The current open release is a subset of a larger collection of code spread across a few different subversion repositories. Internally we work on those repositories, branches are merged to a trunk, and at some point the diff between internal trunk and open trunk is merged (this is not meant to be an exhaustive description of the process, I don't do it, so I'm probably missing something, I'm just trying to get the overview here).
This is fairly costly because thought is required to make sure the right things are happening. Both automatic and human filters need to be applied. In this model, being closed is the default, and action must be taken to be open.
This feels backwards from an energy management standpoint. I talked, separately, with Andy and Jon today and tossed out another idea which they helped refine.
If you reverse the positioning: Everything is open by default, the onus for keeping things closed is placed on those parts of the system which need to be closed. The people working on that project do the insurance work, as the people most invested.
So assume, hypothetically, that the entire code collection is opened and internal and selected external developers are committing. When something needs to be closed, but only then, the problem of making that work is taken care of.
Jon refines this idea by suggesting that the right way to do that would be to require any system which wants to have a closed source representation must first develop the code to make that subsystem, or the place where it would fit in, a pluggable aspect of the code. So for example, if there was some need to make a fully secure encrypted backup system for workspaces but this was going to need to be closed, the first step would be to create a system for pluggable backup tools and implement a straight-to-files version that was open (to prove the interface and provide a reference). Only then would work on the specialized version proceed. Because a pluggable interface now exists, the closed version could exist in its own repository.
Doing things this way would be a benefit for everyone: closed projects are easy to do, but they make it possible for robust and diverse development of multiple tools out in the open as well. Each new pluggable system is one more opportunity for an open user to make a choice.
Some observers might say, "but with this, everytime something becomes pluggable, that's a tell that something interesting is going on behind closed doors". Is it? Maybe it's a bluff?
[img[http://static.flickr.com/30/94870630_d6c29c7478_m.jpg][http://www.flickr.com/photos/cdent/94870630/]]
! Big Wikis or Little Wikis
A common debate at Socialtext goes something like this:
A: Let's use a different workspace for this project, it keeps things clear and navigable.
B: Put it in this existing workspace, that will enhance visibility and exposure.
This tends to result in some small number of tightly focussed but highly information dense workspaces alongside one or a few very large workspaces where a pulse exists but staying on top of stuff requires work and post hoc discovery is quite challenging. Stuff in the smaller workspaces is, in fact, not that visible to those who are mostly paying attention in the big workspace.
BillSeitz says:
* VisibleBacklinks [[can|http://webseitz.fluxent.com/wiki/VisibleBacklinks]], with a variety of RoadMap pages, improve post-hoc-discovery.
* SisterSites [[can|http://webseitz.fluxent.com/wiki/SisterSites]] improve visibility/reusability (of pages) across [[multiple spaces|http://webseitz.fluxent.com/wiki/MultipleThinkingSpaces]].
My technique for keeping up is to subscribe to any RSS or Atom feed I can find, but this doesn't work for some, and after a while it doesn't scale.
Is it better to just use one single gigantic workspace?
I tend to want to compartmentalize during composition but lump it all together while reading. My use of syndication supports this pretty well.
To put this in real terms: The current debate centers over where to locate the public activities of the Socialtext developers (not things like blogs like this one, but project plans and such). The three options under discussion are:
* Here in {{{/stoss}}}
* In an {{{/stdev}}} workspace that's readable by everyone, but only comments are allowed from the general public
* In project specific workspaces, such as {{{/stunplugged}}} for [[Socialtext Unplugged]], configured as {{{/stdev}}}
! Collocation
It would be cool if one could host one workspace or pages with a tag within another workspace. The {{{/stunplugged}}} workspace could nest within {{{/stdev}}} which itself nests within {{{/stoss}}}. For editing/composition and discovery one would use {{{/stunplugged}}} but for casual keeping up, there's {{{/stoss}}}. I'm not quite sure how this would work. A first step might be to break the containership relationship currently maintained between workspaces and pages. A page could be a first class top level entity with membership in a workspace by association not containment.
BillSeitz says: have you looked at MoinMoin's [[approach|http://webseitz.fluxent.com/wiki/SubPages]]?
> That's a good idea. I recall we explicitly took that out of Usemod when we used it as a base for Purplewiki. I think essentially to remove complexity. Socialtext is a different beast so might benefit. It's interesting to think about how the page/subpage relationship would be managed in wikitext, given the all freelinks all the time world of ST.
! Open Source Community as NIC
I think [[Eugene Kim]] has mentioned this notion before but the crayons put it up on the wall last night, so I pass it on.
One of the concepts that distills out of Doug Engelbart's huge oeuvre is the [[Networked Improvement Community]]. A group is formed from disparate but somewhat similar organizations to improve some activity those organizations do, or improve their ability to improve (not going to go into the distinctions between B and C activities here).
The canonical open source story is that some person or group suffers an itch: I love PAM, we've got all our severs using it with a precision tune, but wouldn't it be cool if we could use it with Apache (this story comes from the late nineties when I was a sysadmin)? Scratch, scratch, out comes an authentication module.
It goes out on the network, and a group forms around it, of people with a similar itch. It gets better. That's a community, being networked through the use of some tools, collaborating for the sake of improving their effectiveness.
It used to be that using a Wiki to help orchestrate the noodlings of an open source community was a sort of novel idea. These days if your project doesn't have a wiki or some hybrid thing that's like a wiki somewhere in its information space, it's new, small, dead, too busy to care, or just not cool. It may even be that if you're really cool you've gone all hip and decided not to have a wiki because that's like so last week's news.
Wiki's are pretty good tools, especially since they can be so flexible, but there's a great deal of room for more improvement by the community that uses them. My itch for the last few years has been [[granular reusability]]. You?
BillSeitz asks: do you mean Transclusion [[of|http://webseitz.fluxent.com/wiki/TransclusIon]] nodes?
> Yes, but not just that. If you have addressable nodes, they you have a world of opportunities. You can point to them, suck one in to another document, compose entire documents from subtrees of a nodal structure, distribute nodes across the network all in the pursuit of making ideas, not just whole documents, reusable in an open fashion.
! Incipient Frontlinks
I've just implemented a feature that I ought to have done a long time ago.
While I've been working on my [[Chris Dent in Antarctica]] trip log thingie I find that I keep creating a bunch of links to pages that don't exist yet and I never go back and make the pages. [[LinkAsYouThink|http://www.eekim.com/cgi-bin/wiki.pl?LinkAsYouThink]] is what makes wikis great, but if you //never// make the pages, it's sort of sad.
In Socialtext lingo, these pages are called incipient.
The feature makes it so you can, over the [[REST API]], retrieve a list of incipient pages that are linked from a page. Getting the pages that do exist was already in code (the inverse of backlinks, thus frontlinks).
Meanwhile, as Luke describes in [[Editing the wiki with Vim (or $EDITOR)]], we're cooking tools that allow easy editing of the wikis with an editor. Here's what I want to do:
* use {{{wikedit}}} to edit pages
* after editing, have it retrieve the list of incipient frontlinks
* offer me the opportunity to create and edit any of those pages
Also, it would be handy to create a separate tool that sniffs around a wiki and lets you know about pages that are fat with incipient links, and makes it easy to create them.
Similar things could be added to the primary web interface, when saving pages and when browsing.
This is my favorite picture from Antarctica thus far (I'm processing them in order). This one is Deception Island. The page describing that day is probably still incipient.
[img[http://farm1.static.flickr.com/128/351209921_47a261b602_m.jpg][http://www.flickr.com/photos/cdent/351209921/]]
[img[http://static.flickr.com/67/178572488_71e140b5fb_m.jpg][http://www.flickr.com/photos/cdent/178572488/]]
! HTTP
One project in the queue for the [[REST API]] is to make it better with the handling of HTTP response and request headers. It's already pretty good with those headers that control or describe the representation of a resource (things like `Accept` and `Content-Type`). It's not yet very good with those headers associated with controlling caching in browsers and other caches.
[[Mark Nottingham]] has a good [[Caching Tutorial for Web Authors and Webmasters|http://www.mnot.net/cache_docs/]] which sometimes feels a bit dated but a) isn't and b) is still very useful even if it is.
Much of Socialtext is accessed over SSL and cookies are used as one of the methods for authentication so much of the content cannot be cached by intermediary caches that do proxying or web acceleration. However, proper HTTP cache header handling can improve client<->server interaction where there is cache on the browser or client code is written with an awareness of HTTP headers. The modern case where the headers can be useful is with AJAX tools in so-called rich browser-based web apps. Consider the following example:
A page that displays a sidebox containing the recent changes of a workspace can use a `setTimeout()` or something similar to regularly update its content. If both the client javascript code and the server code are properly header aware, in those instances where the `Last-Modified` time of the recent changes (the last modified time of the most recently changed page) is not greater than the time sent in the If-Modified-Since header, the server can respond with a `304` and the client can continue to use the content it has. Only if the server responds with a 200 does the client need to update its data and refresh the screen. Similarly, if the server is able to determine the freshness of recent changes without regenerating its list of pages, some time can be saved.
(A continously open HTTP connection is another way to do this, but for the sake of example the above works and besides, the [[REST API]] doesn't support any form of push.)
The cache headers are also useful for editing, see [[Socialtext Unplugged, Reloaded]] for some discussion.
For resources that have fairly static representation (wikitext versions of pages) the REST API should probably be sending, in addition to accurate `Last-Modified` headers:
{{{
Cache-Control: public, no-cache
}}}
or
{{{
Cache-Control: public, no-cache, must-revalidate
}}}
If I'm understanding the tutorial correctly, this should cause properly behaving caches to be willing to cache representations that require authentication but still force the cache to revalidate the stored content. This means the cache will pass authentication credentials on to the server, validate them and validate the //freshness// of the representation stored on the cache. If the cache has fresh stuff, the server can avoid some processing.
This is no good for certain representations. For example, if the request is for a `text/html` representation of a wiki page, we can't tell a cache to use a stored representation for at least two reasons:
# Different users get different renderings of content
# HTML content from wikitext that includes dynamic wafl such as {{{fetchatom}}} can be different per request
If a request is for {{{text/x.socialtext-wiki}}} that content is static between edits, so caching is acceptable.
As has often been the case with exploratory research and the REST API, there are probably hidden gems of improvement to found by exploring how best to control and utilize HTTP headers.
[img[http://static.flickr.com/109/265216601_bb24747200_m.jpg][http://www.flickr.com/photos/cdent/265216601/]]
! My Fly is Open
Today I'm starting the process of moving [[Socialtext Unplugged]] development out here to `/stoss`. A series of pages will be built from [[Socialtext Unplugged Project Plan]]. A similar thing will be happening with [[REST API Project Plan]]. One reason for bringing this stuff out into the open is to make sure we get as much input as possible from interested parties so if you have things to say about this stuff, do.
Meanwhile, late last week, Zac and Andy started working on the plans to to stop the discontinuity between the [[Socialtext Open]] development on Sourceforge and Socialtext developer's work that happens on internal subversion repositories. The vast majority of those repositories are going to be opened up.
This makes me very happy: I won't feel like an ass when I say, "I committed this cool thing but you can't see it yet." I'll just point to it in the repository and you can go looking for yourself.
! Community in the Enterprise
Met [[Lee LeFever]] and [[Jay Fienberg]] for lunch today. We covered a lot of ground, but as is tradition for our irregular lunches we talked about the state of collaboration and community in the business space. All three of us agreed that it is harder for a community within a limited (but large) ecosystem (a business unit of a large corporation) to benefit from social software than it is for a community that transcends boundaries (such as the internet at large, cross divisional organizations, inter-corporation communities of practice, extranets, etc). We theorize that this is because the incentives to contribute and improve content are much different in a more closed system.
It seems, but I have only limited experience to support this, that community and content improve when moving in two directions:
# If a community is large and growing, the degree to which its participants are from diverse organizations or experiences improves quality
# If a community is small and mostly closed, quality is enhanced by the existence or creation of a shared goal.
We've seen the latter within Socialtext company workspaces: Small project teams that create project wikis tend to create workspaces with a higher density of long lived and frequently referenced knowledge artifacts.
This is an issue that any company attempting to work in or near the poorly named knowledge management sphere is going to have to deal with: Even if you have everyone writing in blogs every day, how do you ensure that all those stories are distilled for information that is useful tomorrow, next month, next year and five years down the road?
Some have argued that the success of Google proves that search conquers this problem. I don't buy it. Google works because Google calculates at scales greater than any (?) company. Even if there were perfect search, there's still the potentially onerous burden of separating out the parts that matter from the crap in a bunch of blog postings, forums, email messages, etc.
This is where the wiki concept of gardening can be helpful. Gardening tunes up, cleans up, clarifies, extracts, contains, constrains and essentially reifies the good stuff that is present in content. It's hard though. Someone has to do it. Not something. It may need to be part of people's jobs. It might not just emerge in the closed labor world as it does in the more open social world.
The next step in making wikis excellent is to come up with two things:
# Ways to augment the process of gardening with tools that help the discovery, extraction and naming that creates artifacts.
# Ways to extend the purview of Wikis to include documents and content beyond the scope of the wiki page
----
[[Tony Bowden]] writes an interesting response to the above called [[Wiki Gardening|http://nothing.tmtm.com/archives/2593]] where he identifies that one of the reasons search won't work is because sometimes you don't know what you're looking for and search works best when you do. All information systems need to support some form of multi-dimensional browsing. In wikis one way you improve browsing is through gardening. Lots of good bits in Tony's posting.
[[BillSeitz]] wonders whether this is an argument for LittleWikis over BigWikis because context is better preserved for readers after-the-fact. On the other hand, being off in a backwater means a LittleWiki that isn't changing anymore won't get updated, so might misleadingly document context. Argghhh.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="506 234 68 36" width="30" height="30"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2010-09-16 14:51Z</dc:date><!-- Produced by OmniGraffle Professional 5.2.3 --></metadata><defs></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><rect width="1118" height="783"/><g><path d="M 538.68195 244.31807 C 540.43927 246.07547 540.43927 248.9247 538.68195 250.68204 C 536.92456 252.4394 534.07532 252.4394 532.318 250.68204 C 530.5606 248.9247 530.5606 246.07547 532.318 244.31807 C 534.07532 242.56075 536.92456 242.56075 538.68195 244.31807 M 511.12607 257.99356 C 511.26108 258.13019 511.39728 258.26608 511.53473 258.40121 C 527.2556 273.86606 552.74414 273.86606 568.46515 258.40121 C 568.60248 258.26617 568.73853 258.13037 568.87354 257.9938 C 568.8736 257.99374 568.8736 257.99371 568.8736 257.99362 C 568.87366 257.99371 568.87366 257.9938 568.87372 257.9939 C 570.72504 256.12051 572.35046 254.11153 573.74994 252 C 573.74994 251.99997 573.74994 251.99994 573.74994 251.99992 C 572.35046 249.8884 570.72504 247.87938 568.87372 246.00606 C 568.87366 246.00613 568.87366 246.00621 568.8736 246.00627 C 568.73865 245.86966 568.60254 245.73383 568.46515 245.5987 C 552.74414 230.13387 527.2556 230.13387 511.53473 245.5987 C 511.39728 245.73383 511.26108 245.86974 511.12613 246.00635 C 511.126 246.00624 511.126 246.00616 511.12595 246.00606 C 509.2748 247.87938 507.64954 249.88837 506.24994 251.9998 L 506.24994 251.99983 C 506.24994 251.9999 506.25 251.99992 506.25 251.99997 C 506.25 252 506.24994 252.00005 506.24994 252.00009 L 506.24994 252.00012 C 507.64954 254.11157 509.2748 256.12051 511.12595 257.9939 C 511.126 257.99377 511.126 257.99365 511.12607 257.99359 Z M 515.44916 252 C 515.8548 251.55469 516.27502 251.11778 516.71014 250.68985 C 522.16632 245.32257 529.06055 242.23206 536.17273 241.41824 C 534.6662 241.96199 533.2525 242.83762 532.04498 244.04512 C 527.65155 248.43852 527.65155 255.56163 532.04498 259.95502 C 533.2522 261.16226 534.6656 262.03778 536.17175 262.58154 C 529.05988 261.76761 522.16608 258.6771 516.71014 253.31009 C 516.2751 252.88219 515.85486 252.44528 515.44922 252 Z M 564.55054 251.99995 C 564.14502 252.44525 563.7248 252.88217 563.28973 253.31009 C 557.83368 258.67712 550.93988 261.76764 543.828 262.58157 C 545.33423 262.03781 546.74756 261.1623 547.9549 259.95502 C 552.34833 255.56163 552.34833 248.43852 547.9549 244.04512 C 546.74744 242.83765 545.33374 241.96202 543.82715 241.41824 C 550.9394 242.23206 557.83356 245.3226 563.28973 250.68985 C 563.7248 251.11775 564.14502 251.55467 564.55054 251.99995 Z M 568.8736 257.99362 C 570.7249 256.12033 572.35028 254.11139 573.74988 252.00002" fill="black" class="glyph"/></g></g></svg>
[img[http://static.flickr.com/46/175631543_db98db71cb_m.jpg][http://www.flickr.com/photos/cdent/175631543/]]
! Programmers Like to Whine
The recently much referenced [[Programmers Don't Like to Code|http://rentzsch.com/notes/programmersDontLikeToCode]] from [[Jonathan Rentzsch]] may explain why developers, as a class, are often perceived to be a bunch of whiny know it alls. Jonathan, in my estimation, gets it right: few programmers like to code (some do) but most like to solve problems. They like to solve problems in some combination of quickly, creatively, with learning, without boredom.
A prerequisite skill for solving problems is being able to see them. You can't solve a problem without knowing it is there. More importantly, you can't solve a problem //well// and avoid creating //more// problems without first being able to model or perceive potential problems in the system in which the current solution is being developed.
After a while, seeing problems, roadblocks, conflicts, fallacies, misrepresentations, ineffective interfaces, etc becomes second nature. It is likely that some developers see problems better than they solve them, and some developers design or describe solutions better than they code them, and some developers code solutions better than they design solutions or discover problems.
Most of this is probably true for people who aren't developers as well but are also involved in problem solving activities.
@@Please do not modify this tiddler; it was created automatically upon space creation.@@
/***
|''Name''|TiddlySpaceFollowingPlugin|
|''Version''|0.7.1|
|''Description''|Provides a following macro|
|''Author''|Jon Robson|
|''Requires''|TiddlySpaceConfig TiddlySpaceTiddlerIconsPlugin ErrorHandler|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
!Usage
Tag a tiddler with "follow" to express a list of followers.
Using the {{{<<followTiddlers X>>}}}
will reveal the number of tiddlers with name X in the set of spaces the *current* user viewing your space follows.
{{{<<following jon>>}}} will list all the users following Jon.
{{{<<followers jon>>}}} will list all the followers of jon.
{{{<linkedTiddlers>>}}} will list all tiddlers across TiddlySpace linked to the current tiddler
{{{<linkedTiddlers follow:yes>>}}} will list all tiddlers across TiddlySpace that come from your list of followers
adds spaceLink view type {{{<<view server.bag spaceLink>>}}} creates a link to the space described in server.bag
{{{<<view server.bag spaceLink title>>}}} makes a link to the tiddler with title expressed in the field title in space server.bag
If no name is given eg. {{{<<following>>}}} or {{{<<follow>>}}} it will default the current user.
!StyleSheet
.followTiddlersList li {
list-style:none;
}
.followButton {
width: 2em;
}
.followTiddlersList li .siteIcon {
height:48px;
width: 48px;
}
#sidebarTabs .followers li a,
.followers .siteIcon,
.followers .siteIcon div {
display: inline;
}
.followTiddlersList li .externalImage, .followTiddlersList li .image {
display: inline;
}
.scanResults li {
list-style: none;
}
!Code
***/
//{{{
(function($) {
var LIMIT_FOLLOWING = 100;
var tweb = config.extensions.tiddlyweb;
var tiddlyspace = config.extensions.tiddlyspace;
var currentSpace = tiddlyspace.currentSpace.name;
var shadows = config.shadowTiddlers;
config.annotations.ScanTemplate = "This tiddler is the default template used in the display of tiddlers founding using the tsScan macro. To access attributes use the view macro e.g. {{{<<view title text>>}}}";
shadows.ScanTemplate = "<<view modifier SiteIcon width:24 height:24 spaceLink:yes label:no>> <<view title link>>";
shadows.FollowersTemplate = "<<view server.bag SiteIcon width:24 height:24 spaceLink:yes label:no>> <<view server.bag spaceLink>>";
shadows.FollowingTemplate = "<<view title SiteIcon width:24 height:24 spaceLink:yes label:no>> <<view title spaceLink>>";
shadows.FollowTiddlersBlackList = "";
shadows.FollowTiddlersHeading = "There are tiddlers in spaces you follow using the follow tag which use the title <<view title text>>";
shadows.FollowTiddlersTemplate = ["* <<view server.space SiteIcon width:24 height:24 spaceLink:yes label:no>> ",
"<<view server.space spaceLink title external:no>> modified by <<view modifier spaceLink>> ",
"in the <<view server.space spaceLink>> space (<<view modified date>> @ <<view modified date 0hh:0mm>>).\n"].join("");
var name = "StyleSheetFollowing";
shadows[name] = "/*{{{*/\n%0\n/*}}}*/".
format(store.getTiddlerText(tiddler.title + "##StyleSheet"));
store.addNotification(name, refreshStyles);
// provide support for sucking in tiddlers from the server
tiddlyspace.displayServerTiddler = function(src, title, workspace, callback) {
var adaptor = store.getTiddlers()[0].getAdaptor();
var localTitle = tiddlyspace.getLocalTitle(title, workspace);
var tiddler = new Tiddler(localTitle);
tiddler.text = "Please wait while this tiddler is retrieved...";
tiddler.fields.doNotSave = "true";
store.addTiddler(tiddler);
src = story.displayTiddler(src || null, tiddler.title);
tweb.getStatus(function(status) {
var context = {
host: tweb.host, // TODO: inherit from source tiddler?
workspace: workspace,
headers: { "X-ControlView": "false" }
};
var getCallback = function(context, userParams) {
var tiddler = context.tiddler;
tiddler.title = localTitle;
store.addTiddler(tiddler);
story.refreshTiddler(localTitle, null, true); // overriding existing allows updating
if(callback) {
callback(src, tiddler);
}
};
adaptor.getTiddler(title, context, null, getCallback);
});
};
tiddlyspace.scroller = {
runHandler: function(title, top, bottom, height) {
var i;
var handlers = tiddlyspace.scroller.handlers;
var tidEl = story.getTiddler(title);
if(tidEl) {
var topEl = $(tidEl).offset().top + 20;
if(top === false || (topEl > top && topEl < bottom)) {
var h = handlers[title];
for(i = 0; i < h.length; i++) {
h[i]();
}
tiddlyspace.scroller.clearHandlers(title);
}
} else {
tiddlyspace.scroller.clearHandlers(title);
}
},
clearHandlers: function(title) {
tiddlyspace.scroller.handlers[title] = [];
},
registerIsVisibleEvent: function(title, handler) {
tiddlyspace.scroller.handlers[title] = tiddlyspace.scroller.handlers[title] || [];
tiddlyspace.scroller.handlers[title].push(handler);
},
init: function() {
this.handlers = {};
this.interval = window.setInterval(function() {
var top = $(window).scrollTop();
var height = $(window).height();
var bottom = top + height;
var title;
for(title in tiddlyspace.scroller.handlers) {
if(title) {
tiddlyspace.scroller.runHandler(title, top, bottom, height);
}
}
}, 2000); // every 2 seconds check scroll position
}
};
tiddlyspace.scroller.init();
var followMacro = config.macros.followTiddlers = {
locale: {
followListHeader: "Here are tiddlers from spaces you follow using the follow tag which use this title.",
noTiddlersFromFollowers: "None of the spaces you follow contain a tiddler with this name.",
errorMessage: "There was a problem retrieving tiddlers from the server. Please try again later."
},
init: function() {
followMacro.lookup = {};
},
followTag: "follow",
getHosts: function(callback) {
tweb.getStatus(function(status) {
callback(tweb.host, tiddlyspace.getHost(status.server_host, "%0"));
});
},
getBlacklist: function() {
return store.getTiddlerText("FollowTiddlersBlackList").split("\n");
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var args = paramString.parseParams("anon")[0];
var containingTiddler = story.findContainingTiddler(place).getAttribute('tiddler');
var title = (args.anon && args.anon[0]) || tiddler.fields["server.title"] || tiddler.title;
var tid = store.getTiddler(title);
var user = params[1] || false;
if(tid) {
followMacro.makeButton(place, {
url: "/search?q=title:%22"
+ encodeURIComponent(title) + "%22",
containingTiddler: containingTiddler,
blacklisted: followMacro.getBlacklist(), title: title, user: user,
consultFollowRelationship: (args.follow &&
args.follow[0] === 'false') ? false : true });
}
},
makeButton: function(place, options) { // this is essentially the same code in TiddlySpaceFollowingPlugin
var title = options.title;
var blacklisted = options.blacklisted;
var tiddler = store.getTiddler(title);
var btn = $('<div class="followButton" />').addClass("notLoaded").appendTo(place)[0];
if(blacklisted.contains(title)) {
$(btn).remove();
return;
} else {
var user = options.user;
window.setTimeout(function() { // prevent multiple calls due to refresh
tiddlyspace.scroller.registerIsVisibleEvent(options.containingTiddler, function() {
var mkButton = function(followers, ignore) {
if(!followers && !ignore) {
$(btn).remove();
} else {
$("<a />").appendTo(btn);
var scanOptions = { url: options.url,
spaceField: options.spaceField || "bag", template: null, sort: "-modified",
callback: function(tiddlers) {
$(btn).removeClass("notLoaded");
followMacro.constructInterface(btn, tiddlers);
}
};
if(!ignore) {
scanOptions.showBags = followMacro._getFollowerBags(followers);
}
scanOptions.hideBags = [tiddler.fields["server.bag"]];
scanMacro.scan(null, scanOptions, user);
}
};
if(options.consultFollowRelationship) {
followMacro.getFollowers(mkButton);
} else {
mkButton([], true);
}
});
}, 1000);
}
},
constructInterface: function(container, tiddlers) {
var txt = tiddlers.length;
var className = txt > 0 ? "hasReplies" : "noReplies";
var el = $(story.findContainingTiddler(container));
$(container).empty().addClass(className);
var btn = $("<a />").addClass("followedTiddlers").text(txt).
click(function(ev) {
followMacro.followingOnClick(ev);
}).appendTo('<div class="followedTiddlers" />').appendTo(container)[0];
$.data(btn, "tiddlers", tiddlers);
},
followingOnClick: function(ev) {
var target = ev.target;
var locale = followMacro.locale;
var el = $('<div class="followTiddlersList" />')[0];
var popup = Popup.create(target,"div");
$(popup).addClass("taggedTiddlerList followList").click(function(ev) { // make it so only clicking on the document outside the popup removes the popup
if(ev.target.parentNode != document) {
ev.stopPropagation();
}
}).append(el);
var tiddlers = $.data(target, "tiddlers") || [];
scanMacro.template(el, tiddlers.slice(0,1), "FollowTiddlersHeading");
scanMacro.template(el, tiddlers, "FollowTiddlersTemplate");
if(tiddlers.length === 0) {
$("<li />").text(locale.noTiddlersFromFollowers).appendTo(el);
}
Popup.show();
ev.stopPropagation();
return popup;
},
_getFollowerBags: function(followers) { // XXX: private or not?
return $.map(followers, function(name, i) {
return name != currentSpace ? "%0_public".format(name) : null;
});
},
getFollowers: function(callback, username) {
// returns a list of spaces being followed by the existing space
var followersCallback = function(user) {
if(!user.anon) {
scanMacro.scan(null, {
url: "/search?q=bag:%0_public tag:%1 _limit:%2".format(user.name, followMacro.followTag, LIMIT_FOLLOWING),
spaceField: "title", template: null, cache: true,
callback: function(tiddlers) {
var followers = [];
for(var i = 0; i < tiddlers.length; i++) {
followers.push(tiddlyspace.resolveSpaceName(tiddlers[i].title));
}
callback(followers);
}
});
} else {
callback(false);
}
};
return !username ? tweb.getUserInfo(followersCallback) : followersCallback({ name: username });
}
};
var scanMacro = config.macros.tsScan = {
init: function () {
this.scanned = {};
},
_tiddlerfy: function(jsontiddlers, options) {
var tiddlers = [];
var spaceField = options.spaceField || "bag"; // TODO: phase out use view types instead
$.each(jsontiddlers, function(i, t) {
var use = false;
if(!options.showBags || (options.showBags && options.showBags.contains(t.bag))) {
use = true;
}
if(options.hideBags && options.hideBags.contains(t.bag)) {
use = false;
}
if(use) {
var spaceName = t[spaceField];
var tiddler = config.adaptors.tiddlyweb.toTiddler(t, tweb.host);
tiddler.fields["server.space"] = tiddlyspace.resolveSpaceName(spaceName);
tiddlers.push(tiddler);
}
});
return tiddlers;
},
_scanCallback: function(place, jsontiddlers, options) {
var locale = followersMacro.locale;
var tiddlers = scanMacro._tiddlerfy(jsontiddlers, options);
if(options.sort) {
tiddlers = store.sortTiddlers(tiddlers, options.sort);
}
if(options.filter) {
var _store = new TiddlyWiki();
config.lastStore = _store;
for(var i = 0; i < tiddlers.length; i++) {
var clone = tiddlers[i];
clone.title = tiddlyspace.getLocalTitle(clone.title, clone.fields['server.workspace']);
_store.addTiddler(clone);
}
tiddlers = _store.filterTiddlers(options.filter);
}
if(place) {
$(place).empty();
var list = $("<ul />").appendTo(place)[0];
scanMacro.template(list, tiddlers, options.template);
if(tiddlers.length === 0) {
$("<li />").text(options.emptyMessage || locale.noone).appendTo(list);
$(list).addClass("emptyList");
}
}
if(options.callback) {
options.callback(tiddlers);
}
},
constructSearchUrl: function(host, options) {
if(options.url) {
return options.url;
}
var inputs = options.searchValues;
var tag = options.tag;
var searchField = options.searchField || "title";
var searchQuery = [];
for(var i = 0; i < inputs.length; i++) {
searchQuery.push('%0:"%1"'.format(searchField, inputs[i]));
}
var query = searchQuery.join(" OR ");
query = tag ? "(%0) AND tag:%1".format(query, tag) : query;
query = options.query ? "%0;%1;".format(query, options.query) : query;
query = options.fat ? "%0&fat=y".format(query) : query;
return '%0/search?q=%1'.format(host, query);
},
scan: function(place, options) { // TODO: make use of list macro with url filter
var locale = followersMacro.locale;
options.template = options.template ? options.template : "ScanTemplate";
followMacro.getHosts(function(host, tsHost) {
$(place).text(followersMacro.locale.pleaseWait);
options = options ? options: {};
var url = scanMacro.constructSearchUrl(host, options);
if(options.cache && scanMacro.scanned[url]) {
var tiddlers = scanMacro.scanned[url].tiddlers;
var run = function(tiddlers) {
scanMacro._scanCallback(place, tiddlers, options);
};
if(tiddlers) {
run(tiddlers);
} else {
scanMacro.scanned[url].callbacks.push(run);
}
} else {
var callback = function(tiddlers) {
scanMacro._scanCallback(place, tiddlers, options);
};
if(scanMacro.scanned[url] && scanMacro.scanned[url].callbacks) {
scanMacro.scanned[url].callbacks.push(callback)
} else {
scanMacro.scanned[url] = {
callbacks: [callback]
};
}
ajaxReq({
url: url,
dataType: "json",
success: function(tiddlers) {
scanMacro.scanned[url].tiddlers = tiddlers;
var callbacks = scanMacro.scanned[url].callbacks;
while(callbacks.length > 0) {
callbacks.pop()(tiddlers);
}
},
error: function(xhr) {
$(place).empty();
$("<span />").addClass("annotation error").text(locale.error.format(xhr.status)).appendTo(place);
}
});
}
});
},
template: function(place, tiddlers, template) { // TODO: make use of list macro.
for(var i = 0; i < tiddlers.length; i++) {
var tiddler = tiddlers[i];
var item = $('<li class="spaceName" />').appendTo(place)[0];
var spaceName = tiddler.fields["server.space"] || "";
var templateText = store.getTiddlerText(template).replace(/\$1/mg, spaceName);
wikify(templateText, item, null, tiddler);
}
},
getOptions: function(paramString, tiddler) {
var args = paramString.parseParams("name", null, true, false, true)[0];
var options = { query: false, sort: false, tag: false, template: false, showBags: args.show || false,
hideBags: args.hide || false, filter: false, spaceField: "bag", searchField: "title", fat: false,
emptyMessage: false };
for(var name in args) {
if(name != "name") {
if(name == "fat") {
options[name] = true;
} else {
options[name] = args[name][0];
}
}
}
// if user has set searchField to modifier, then use the modifiers value if available otherwise use searchValues.
var searchField = options.searchField;
var searchValues = args[searchField] ? args[searchField] : args.searchValues;
// if neither of those were used use the first parameter
var defaultValues = tiddler ? [ tiddler.title ] : [];
options.searchValues = searchValues ? searchValues : ( args.name ? [args.name[0]] : defaultValues);
return options;
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var container = $("<div />").addClass("scanResults resultsArea").appendTo(place)[0];
var options = scanMacro.getOptions(paramString, tiddler);
scanMacro.scan(container, options);
}
};
var followersMacro = config.macros.followers = {
locale: {
loggedOut: "Please login to see the list of followers",
noSupport: "We were unable to retrieve followers as your browser does not support following.",
pleaseWait: "Please wait while we look this up...",
error: "Error %0 occurred whilst retrieving data from server",
noone: "None."
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var locale = followersMacro.locale;
var args = paramString.parseParams("name", null, true, false, true)[0];
var username = args.name ? args.name[0] : false;
var container = $('<div class="followers" />').text(locale.pleaseWait).
appendTo(place)[0];
var followersCallback = function(user) {
if(user.anon) {
$("<span />").text(locale.loggedOut).appendTo(container);
} else {
var options = scanMacro.getOptions(paramString);
$.extend(options, {
url: "/search?q=title:@%0 OR title:%0 tag:%1 _limit:%2".
format(user.name, followMacro.followTag, LIMIT_FOLLOWING),
spaceField: "bag",
template: options.template ? options.template : "FollowersTemplate"
});
scanMacro.scan(container, options);
}
};
return !username ? followersCallback({ name: currentSpace }) : followersCallback({ name: username });
}
};
var followingMacro = config.macros.following = {
locale: {
pleaseWait: followersMacro.locale.pleaseWait,
loggedOut: "Please login to see who you are following",
noSupport: followersMacro.locale.noSupport,
error: followersMacro.locale.error,
noone: followersMacro.locale.noone
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var locale = followingMacro.locale;
var args = paramString.parseParams("name", null, true, false, true)[0];
var fat = args.fat ? true : false;
var username = args.name ? args.name[0] : false;
var container = $('<div class="following" />').text(locale.pleaseWait).
appendTo(place)[0];
var followingCallback = function(user) {
if(user.anon) {
$("<span />").text(locale.loggedOut).appendTo(container);
} else {
var options = scanMacro.getOptions(paramString);
$.extend(options, {
url: "/search?q=bag:%0_public tag:%1 _limit:%2".format(user.name, followMacro.followTag, LIMIT_FOLLOWING),
spaceField: "title",
template: options.template ? options.template : "FollowingTemplate"
});
scanMacro.scan(container, options);
}
};
return !username ? followingCallback({ name: currentSpace }) : followingCallback({ name: username });
}
};
var linkedMacro = config.macros.linkedTiddlers = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var args = paramString.parseParams("anon")[0];
var title = params[0] || tiddler.fields["server.title"] || tiddler.title;
var tid = store.getTiddler(title);
var containingTiddler = story.findContainingTiddler(place).getAttribute('tiddler');
if(tid) {
followMacro.makeButton(place, {
spaceField: "recipe",
url: "/bags/%0/tiddlers/%1/backlinks".format(tid.fields['server.bag'],
encodeURIComponent(tid.title)),
blacklisted: followMacro.getBlacklist(),
title: title,
containingTiddler: containingTiddler,
user: params[1] || false,
consultFollowRelationship: args.follow ? true : false });
}
}
}
if(config.options.chkFollowTiddlersIsLinkedTiddlers) {
merge(config.macros.followTiddlers, config.macros.linkedTiddlers);
config.shadowTiddlers.FollowTiddlersHeading = "These are the other tiddlers that link to this tiddler.";
}
})(jQuery);
//}}}
/*{{{*/
body {
font-size: 1em;
font-family: helvetica, arial, sans-serif;
background-color: #fff;
color: [[ColorPalette::Foreground]];
}
body ul { margin: 0; }
#popup {
background-color: [[ColorPalette::TertiaryPale]];
}
#popup.confirmationPopup, .followList {
font-size: 0.8em;
padding: 1em;
border: solid 1px [[ColorPalette::SecondaryMid]];
background-color: [[ColorPalette::SecondaryPale]];
}
.followList .listTitle {
text-decoration: underline;
}
#popup .followTiddlersList a {
display: inline;
padding: 0;
}
#popup li a {
color: [[ColorPalette::PrimaryMid]];
font-weight: bold;
}
#popup li a:hover {
color: [[ColorPalette::PrimaryPale]];
background: [[ColorPalette::PrimaryMid]];
}
#popup li.listTitle {
border-bottom: 1px solid #000;
font-weight: bold;
margin-bottom: 10px;
}
#popup.followList {
margin-left: 50px;
margin-top: -30px;
}
.followTiddlersList .label {
display: block;
left: 10px;
top: 0px;
line-height: 16px;
position: relative;
}
#popup .followTiddlersList .siteIcon{
height: auto;
}
#popup .followTiddlersList li{
clear: both;
display: block;
height: 48px;
margin-bottom: 8px;
position: relative;
}
#popup .followTiddlersList a{
display: inline;
}
#displayArea {
margin: 0;
top: 0px;
left: 0px;
width: 100%;
position: relative;
}
.revisionCloak {
position: absolute;
position: fixed !important;
height: 100%;
width: 100%;
top: 0;
left: 0;
border: 0;
margin: 0;
padding: 0;
opacity: 0.5;
filter: alpha(opacity=50);
background-color: #000;
}
/* *** Header *** */
.header {
position: relative;
background-color: [[ColorPalette::PrimaryMid]];
_width: 100%; /* ie 6 demands */
}
.headerForeground {
background-color: [[ColorPalette::PrimaryMid]];
float: left;
margin: 24px 16px 0px 72px;
padding: 0;
position: relative;
top: 0;
_width: 70%; /*ie6: needed for the background to actually be transparent*/
_background-color: transparent; /*ie6: needed to show the search box*/
}
.clearFloat {
clear: both;
}
#contentWrapper {
position: relative;
padding-top: 1px;
top: -1px;
}
#tiddlerDisplay {
_position: relative; /* ie 6*/
}
.siteTitle {
clear: both;
display: block;
font-size: 32px;
font-weight: bold;
line-height: 32px;
}
.siteSubtitle {
display: block;
font-size: 14px;
height: 16px;
margin-bottom: 8px;
}
#sidebarSearch {
padding: 0;
position: absolute;
right: 80px;
top: 8px;
width: 176px;
}
#sidebarSearch .txtOptionInput {
width: 100%;
margin-top: 5px;
_color: #bbb; /* ie6 danger */
}
#sidebarSearch .txtOptionInput:focus {
color: #000;
}
#sidebarSearch .searchButton {
display: none;
}
/* *** Menu Bar *** */
#mainMenu {
position: static;
text-align: left;
margin-left: 72px;
float: left;
width: auto;
padding: 0;
font-size: 1em;
line-height: normal;
}
#mainMenu a {
color: #fff;
padding: 8px;
font-size: 0.9em;
margin-right: 16px;
}
#mainMenu a:hover {
background-color: [[ColorPalette::PrimaryMid]];
color: [[ColorPalette::Background]]
}
#sidebarOptions {
margin-right: 72px;
float: right;
font-size: 1.1em;
line-height: 1.6em;
min-height: 1em;
padding-top: 0;
}
#sidebarOptions a {
margin-right: 8px;
}
.confirmationPopup .button,
#sidebarOptions .button {
cursor: pointer;
line-height: 1.4em;
text-align: center;
margin-right: 8px;
margin-left:-2px;
}
.confirmationPopup .button {
font-size: 0.9em;
padding: 2px;
}
#sidebarOptions .button {
font-size: 0.7em;
float: left;
width: 80px;
padding: 0px;
color: #fff;
}
.confirmationPopup a.button,
#sidebarOptions a {
border: none;
margin: 0 0.2em;
padding: 0.6em 0.25em;
display: inline;
color: #666;
}
.confirmationPopup a.button:hover,
#sidebarOptions a:hover {
color: #000;
}
.confirmationPopup a.button:active,
#sidebarOptions a:active {
border: solid 1px [[ColorPalette::PrimaryMid]];
background-color: #fff;
background: -webkit-gradient( linear, left bottom, left top, color-stop(0.1,rgb(200,200,200)), color-stop(1, rgb(100,100,100)));
background: -moz-linear-gradient(center bottom , rgb(200,200,200) 10%,rgb(100,100,100) 100%) repeat scroll 0 0 transparent;
}
/* *** Sidebar *** */
#sidebar .wizard table {
margin: 0px;
}
.tabContents .listTitle:first-child {
margin-top: 0px;
}
#menuBar {
background: [[ColorPalette::PrimaryLight]];
left: 0;
right: 0;
position: relative;
margin: 0;
padding: 0.5em 0 0.5em 0;
min-height: 1em;
overflow: hidden;
_width: 100%; /* for ie 6 */
}
#sidebarOptions a.button:hover {
color: [[ColorPalette::PrimaryPale]];
background: [[ColorPalette::PrimaryMid]];
}
#tiddlerDisplay, #searchResults {
margin: 16px 448px 0 72px;
}
#sidebarTabs {
position: absolute;
right: 72px;
width: 352px;
top: 0;
}
#sidebarTabs .tabsetWrapper .tabset {
width: 87px;
border-top: 1px solid [[ColorPalette::PrimaryPale]];
border-left: 1px solid [[ColorPalette::PrimaryPale]];
border-bottom: 1px solid [[ColorPalette::PrimaryPale]];
height: auto;
float: left;
word-wrap: break-word;
top: 0;
padding: 0;
}
#sidebarTabs .tabsetWrapper .tabContents {
background-color: [[ColorPalette::PrimaryPale]];
border: 3px solid [[ColorPalette::PrimaryMid]];
width: 242px;
_width: 238px;
left: -3px;
_left: -5px;
position: relative;
min-height: 34em;
padding: 8px;
font-size: 0.8em;
}
/* ---- Side style --- */
#sidebarTabs .tabsetWrapper .tabset .tab {
font-size: 0.9em;
padding: 0.7em 8px 0.5em;
color: #fff;
background: [[ColorPalette::PrimaryLight]];
border: none;
line-height: 16px;
position: relative;
display: block;
margin: 0;
}
#sidebarTabs .tabsetWrapper .tabset .tabSelected {
color: [[ColorPalette::PrimaryMid]];
background: [[ColorPalette::PrimaryPale]];
border-top: 3px solid [[ColorPalette::PrimaryMid]];
border-bottom: 3px solid [[ColorPalette::PrimaryMid]];
border-left: 3px solid [[ColorPalette::PrimaryMid]];
z-index: 10;
margin-top: -1px;
font-weight: bold;
}
#sidebarTabs .tabContents li {
border: none;
margin-left: 0;
word-wrap: break-word;
}
.tabContents .timeline {
background: [[ColorPalette::PrimaryPale]];
margin-bottom: 8px;
}
#sidebarTabs .timeline li.listTitle {
color: #132E43;
margin-left: 8px 0;
padding: 0.3em 0.11em;
font-size: 1em;
border-bottom: none;
}
#sidebarTabs .tabContents li a {
display: block;
text-align: left;
margin: 0 0 1px 0;
padding: 0.3em 1em;
background: [[ColorPalette::PrimaryPale]];
}
#sidebarTabs .tabsetWrapper .tabset a:hover,
#sidebarTabs .tabContents li a:hover {
color: [[ColorPalette::PrimaryPale]];
background: [[ColorPalette::PrimaryMid]];
}
/* Activity Stream */
#sidebarTabs .tabContents .activityStream .feedItem a {
display: inline-block;
padding: 0;
background: none;
}
/* ---- Tagging box --- */
.tagInfo {
border: 1px solid #cccccc;
padding: 10px 15px;
-moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 2px rgba(0,0,0,0.2);
color: [[ColorPalette::TertiaryMid]];
background: -moz-linear-gradient(100% 100% 90deg, #f4f4f4, #e5e5e5);
background: -webkit-gradient(linear, left top, right top, from(#e5e5e5), to(#f4f4f4));
margin-top: 1em;
font-size: 13px;
margin: 0 0 0 56px;
}
.tagInfo ul {
list-style: none;
padding-left: 2.2em;
}
.tagInfo ul li {
display: inline;
}
.tagInfo ul li.listTitle,
.tagInfo .tagging ul li.listTitle {
color: [[ColorPalette::PrimaryMid]];
font-size: 13px;
}
.tagInfo ul li a {
border: none;
}
.tagInfo .tagging ul li {
float: none;
display: inline-block;
}
.tagInfo .tagging {
padding: 0;
}
.viewRevision .toolbar {
right: 48px;
top: 8px;
}
.viewRevision .modifierIcon img,
.viewRevision .modifierIcon svg {
margin-right: 8px;
}
.viewRevision .toolbar svg {
width: 32px;
height: 32px;
}
/* --- IE hacks from lattice --- */
/* ie hacks */
* html #menuBar {
margin-bottom: 8px;
}
.toolbar .svgIconText {
*display: inline;
}
div.tiddler .toolbar a {
cursor: pointer;
float: left\9;
display: inline\9;
}
* html .toolbar {
right: 8px;
}
* html .followButton a {
margin-top: 0px;
margin-right: 8px;
}
* html #tiddlerDisplay {
margin-top: 0px;
}
/* for printing purposes */
@media print {
#mainMenu,
#sidebar,
#messageArea,
.toolbar,
.followPlaceHolder,
#backstageButton,
#backstageArea,
#sidebarTabs,
#sidebarSearch .txtOptionInput,
#sidebarOptions {
display: none !important;
}
#displayArea {
margin: 1em 1em 0em;
}
noscript {
display:none; /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
}
#tiddlerDisplay {
margin: 16px 16px;
}
}
@media all and (max-width: 960px){
#tiddlerDisplay,
#searchResults {
margin: 16px 366px 0 16px;
}
#mainMenu {
margin-left: 16px;
}
.headerForeground {
margin-left: 16px;
}
#sidebarSearch {
right: 16px;
}
#sidebarOptions {
margin-right: 16px;
}
#sidebarTabs {
right: 16px;
width: 326px;
}
#sidebarTabs .tabsetWrapper .tabset {
font-size: 0.9em;
width: 77px;
}
#sidebarTabs .tabsetWrapper .tabContents {
width: 226px;
_width: 222px;
}
#sidebarTabs .tabContents li a {
font-size: 0.9em;
}
}
/*}}}*/
[[StyleSheetTiddler]]
[img[http://static.flickr.com/57/155240361_d306992aae_m.jpg][http://www.flickr.com/photos/cdent/155240361/]]
While conversing with Jesse (of RT fame) today about using RT more effectively at Socialtext, I was reminded of what may have been the most valuable learning from my trip to graduate school: The distinction between [[Automation]] and [[Augmentation]].[1]
An automated task is one that has replaced the work of a human with some technology (often some kind of computational device). The //only// tasks which can be automated are those that "science, engineering, and statistics have captured in their theories" [2]. That is, it is only possible to automate those things which are well understood, well described, and proceed in a discernable and predictable fashion.
In automated tasks, the human user or initiator of the process can leave things alone once the process is started. There are some tasks which require human oversight and interaction throughout the process but can be assisted or //augmented// by the presence of technology. Often these are associated with the filtering, interpretation or re-presentation of information.
An argument can be made that effective software and process design is predicated on correct task analysis and decomposition and clear understanding of the location of the membrane between automatable and not automatable tasks.
When clippy came along in Office, it (he?) was such a disaster because the people who designed and built him had misunderstood what was truly automatable. They came out too far to the human side of the interaction, into the land of the unpredictable.
At Socialtext there has been some fear of RT because it can be misued to over automate and thus constrain the issue tracking process. We want to use RT to //augment//, not automate, our activity as issue solves. We can //also// use RT to automate things we can describe and intentionally limit such as reporting, notification, and other tasks that are predictable.
Process and automation are not bad. Bad processes and incorrect automation are bad.
----
[1] Instead of learning it may have been a confirmation. In my previous career as a sysadmin the group I was in was committed to [[putting ourselves out of a job|http://www.kiva.net/~systhug/command/]]: Anything tedious can be automated. We learned quickly what could or could not be automated and what could happen if we overautomated. Granularity is good.
[2] Landauer, T. (1995). //The trouble with computers//. Cambridge, MA: MIT Press.
(function() {
var getCSRFToken = function(window) {
// XXX: should not use RegEx - cf.
// http://www.quirksmode.org/js/cookies.html
// https://github.com/TiddlySpace/tiddlyspace/commit/5f4adbe009ed4bda3ce39058a3fb07de1420358d
var regex = /^(?:.*; )?csrf_token=([^(;|$)]*)(?:;|$)/;
var match = regex.exec(document.cookie);
var csrf_token = null;
if (match && (match.length === 2)) {
csrf_token = match[1];
}
return csrf_token;
};
if (typeof config !== 'undefined' && config.extensions &&
config.extensions.tiddlyspace &&
config.extensions.tiddlyspace.getCSRFToken === null) {
config.extensions.tiddlyspace.getCSRFToken = getCSRFToken;
} else {
window.getCSRFToken = getCSRFToken;
}
})(window);
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="434 218 68 68"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 478.39694 232.53705 L 478.39694 232.53705
C 477.1145 231.85132 475.77875 231.30147 474.41058 230.88734 L 474.41058 218.24994 L 461.58942 218.24994
L 461.58942 230.88734 C 460.22125 231.30147 458.8855 231.85132 457.60306 232.53705 L 448.66824 223.60214
L 439.6022 232.66814 L 448.53717 241.60304 C 447.8515 242.8854 447.30157 244.22116 446.88745 245.58936
L 434.25 245.58936 L 434.25 258.41052 L 446.88745 258.41052
C 447.30157 259.77869 447.8515 261.11447 448.53717 262.39688 L 439.6022 271.33173 L 448.66824 280.3978
L 457.60306 271.46283 C 458.8855 272.14862 460.22125 272.69846 461.58942 273.11252 L 461.58942 285.74988
L 474.41058 285.74988 L 474.41058 273.11252 C 475.77875 272.69846 477.1145 272.14862 478.39694 271.46283
L 487.33176 280.3978 L 496.39767 271.33173 L 487.46286 262.39688
C 488.14853 261.11447 488.69836 259.77869 489.11255 258.41052 L 501.74988 258.41052 L 501.74988 245.58936
L 489.11255 245.58936 C 488.69836 244.22116 488.14853 242.8854 487.46286 241.60304 L 496.39767 232.66814
L 487.33176 223.60214 Z M 475.3328 244.66714 C 479.3825 248.71698 479.3825 255.2829 475.3328 259.33273
C 471.28296 263.3826 464.71704 263.3826 460.66724 259.33273
C 456.61737 255.2829 456.61737 248.71698 460.66724 244.66714
C 464.71704 240.61734 471.28296 240.61734 475.3328 244.66714" fill="#111"
class="glyph"/>
</g>
</g>
</svg>
[img[http://static.flickr.com/28/94870125_52357b7304_m.jpg][http://www.flickr.com/photos/cdent/94870125/]]
! Composition
I'm not sure why, but I just remembered that when I originally learned about OOP all the texts talked about inheritance as the bees knees. Class hierarchies were described as the big win. These days the texts talk brightly about composition.
This is what we'd hope for and it's not the [[technical ramifications|http://brighton.ncsa.uiuc.edu/prajlich/T/node14.html]] I'm talking about here, but rather the change in the literature that I've seen over the course of the last, uh, ten to twenty years. After reading about inheritance a bunch of developers went out in the world and created deep and complex trees of objects. A few years later the poor saps left behind to maintain those trees got tired of looking around for methods and being dependent up the tree and worked out some new ways to go. People learned stuff and made some new books.
When I first started at Socialtext the code was all about inheritance, for two reasons:
# The Spiffy, Spoon, Kwiki trio was the base for the socialtext code
# Any duplication had been pushed up into super classes. Duplication was considered the greatest sin.
For people new to the code, it was rough going. Ctags could help with finding methods, but if you ever wanted to change something, it was fraught with trouble (we also didn't have good tests then, both situations have improved greatly).
A little more than a year ago we flattened the class hierarchy and ripped out the Kwiki bits, limiting much of the inheritance we had. Since then there has been increasing appreciation of the need for composition but not yet that much increased implementation. It can be hard to break the sometimes useful patterns and idioms that are built into a codebase, even when you desire to do things better or differently. Especially under the usual pressures of product development.
Many times I've thought, "let's start over from scratch". This would make sense if we were developing a new product, but makes less sense when there is so much code, so much infrastructure and so many customers.
So as time allows, we tweak here and tweak there, bringing clarity to our chaos.
When you look at the code, which part is most murky?
[img[http://static.flickr.com/51/155246183_44f86dfd34_m.jpg][http://www.flickr.com/photos/cdent/155246183/]]
In the continuing drama of how to survive distributed companies:
I've been in Bloomington, Indiana for the past week. For some of that time I hung with [[mml]]. We worked together on improvements to socialtext's searching capabilities. Some obvious observations from the whole week:
* It's far easier to be smart, have ideas and identify problems in person. In large part this is because of body language, but that's not the whole deal. Another part is that it is fun to be smart with your chums and chumminess is far more real in person. An aspect of chumminess is friendly competition: "Oh you think you're smart? Try this one."
* However, if there's non-planning stuff to do but the people who are together are not working on the same stuff, it is harder to get things done. The other person is a big distraction because they are like a giant shiny rare diamond that you see only a few times a year.
* Using computers makes you stupid. Get away. Think more. Spike after a good rest.
* If you're feeling the bile of urgency rising in your craw, do a reality check. It's probably not really all that urgent.
* Being smart and clear headed //now// because you want to be or can be is much more effective than being smart later because you have to be.
These are obvious, but so often obvious stuff falls between in the cracks created by false urgency, fuzzy headedness and just being dumb.
<<closeAll>><<permaview>><<newTiddler>>
[img[http://static.flickr.com/36/115438815_1e87d11d80_m.jpg][http://www.flickr.com/photos/cdent/115438815/]]
! Back from Yosemite
I'm back from Yosemite. Was lovely. Took [[some pictures|http://www.flickr.com/photos/cdent/collections/72157600321290043/]].
[img[http://farm2.static.flickr.com/1308/534226186_b7b1c7df99.jpg][http://www.flickr.com/photos/cdent/534226186/]]
Now I'm back in Palo Alto for a day and a half. There will be some discussion of themes for the second half of the year and plans for the rest of this quarter and next. I need to figure out what's happening with the latest bug fixes for [[Socialtext Unplugged]] and get [[ra-dispatch]] further down the release chute.
There's such a stark difference between [[sitting a few thousand feet above a beautiful valley|http://www.flickr.com/photos/cdent/534303503/in/set-72157600321250751/]] and doing work. One could argue that doing the work makes the sitting possible, but it's never really clear that the trade maps. I once thought that working on wikis was a vocation but now I'm not sure. Doing wikis for enterprises to the exclusion of doing wikis for //people// is a bit stultifying. So henceforth I hope to put some effort and attention into [[Amo]], [[WikiOhana]] and the world //out there//. Out there is where the good ideas happen, so we have to go out there to get the good stuff.
! Dependency Management
I'm noodling around this notion in my head that the job of managers is to resolve dependencies in the workforce tree. That is that's job one and nothing else should be done until that is handled.
More noodling required.
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
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="72 648 70 70"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 77.59005 669.34003 C 71.532745 681.90424 73.714462 697.4441 84.135193 707.86475
C 97.315445 721.0451 118.684715 721.0451 131.8649 707.86475
C 145.04515 694.68457 145.04515 673.31537 131.8649 660.13513
C 121.4441 649.7141 105.90419 647.53253 93.339905 653.5899 L 102.047455 662.2976
C 109.58637 660.2373 117.987976 662.16803 123.90997 668.08997
C 132.69673 676.8767 132.69673 691.12317 123.90997 699.90985
C 115.12313 708.6966 100.87699 708.6966 92.09012 699.90985
C 86.168266 693.98804 84.23744 685.58643 86.297653 678.04755 Z M 72 648 L 72 668.25 L 78.75 661.49957
L 99.00019 681.7502 L 105.750175 675.00006 L 85.50013 654.75012 L 92.249985 648 Z" fill="black"
class="glyph"/>
</g>
</g>
</svg>
[img[http://static.flickr.com/48/134521651_ce85e509bd_m.jpg][http://www.flickr.com/photos/cdent/134521651/]]
! Observations
* RTF and tables. Not my favorite.
* [[mml][ showed me [[Brainfuck]] today and my head exploded.
* The things showing up as [[REST API]] clients make me feel good.
! Python
At the Socialtext f2f a while back we opened the can of "maybe using a different language" for some things. We've already been doing miscellaneous development of [[REST API]] and [[SOAP API]] clients in a variety of languages (C++, Java, Python, Ruby, C#, Haskell) but not made much in the way of plans for large development with something other than Perl. We tossed around the idea a bit, and we're now in some kind of "yeah, something else is okay if it seems right" state.
Which is great. Whatever you can say about the awesomeness of Perl and CPAN (they are, together, very awesome and I won't be giving up Perl) I've yet to have what I consider an extremely pleasant experience doing multi-party large-system development in Perl. There's just too many ways to do it and that leads to a swamp of hard to maintain code. There's also duplicate dependencies satisfied by multiple modules and a lack of attention to standardization, patterns and interaces.
(Yes, I know you can have all those things in Perl, if you try, but it is very easy not to.)
So I went off to look at Python. Squeeee! Damn, the artifacts of that culture sure look shiny. [[import this]] makes me feel all warm and fuzzy.
I hope to get some resty things going as time allows (there hasn't been much lately).
! Collaborating on Collaboration
One of the pleasures of participating in a growing company is watching how people slot into the vision as it is refined, clarified, illuminated and whatever. When I started at Socialtext I was coming from [[Blue Oxen Associates]], soaked through with the notion of creating and coevolving tools for collaboration. Socialtext was talking about bringing the learnings of the social software world into the collaborative workplace. Seemed a good fit.
In my early years there was enough to do that collaboration tools meant the wiki and working on the wiki really meant making it be good enough to be salable. While there remains a great deal to do in that vein, there are enough of us now that we can begin to have legitimate discussion about what the company means, where it is going, what does a world changed (somewhat) by Socialtext look like? As a company we can still say, "We want people to be able to collaborate more effectively through the use of a social software tools, techniques, processes, learnings (//etc etc//)". As individuals in the company (and people interacting with the company in other ways), however, there is now room for an aspect of specialization that balances, mediates and improves each contributors interests. We all come from diverse enough backgrounds that considered input ought to mean refinement and improvement not conflict.
This has led me to think about what my own particular species of collaboration really is. At Blue Oxen I thought it was high performance asynchronous collaboration but a) that's too general to really be a species with focus and b) that's high faluting language that may be too value laden to really deliver any meaning.
The function to which I keep returning is information reuse. My fascination with [[Purple Numbers]] and [[Transclusion]], the ideas embedded in [[In Other Words|https://www.socialtext.net/data/workspaces/open/pages/in_other_words]], my resistance to [[freelinks]], the importance of apis, email archives and syndication. I'm pleasantly keen on tools that help people create, use and record information in the moment, but the part that matters most to me is making the information available later so that it can be used in a forward surge of learning. If information is available for reuse it can support a dialectic and the future will be created.
So while someone might say a most important thing we can do is make it easy to get people's thoughts into a wiki, another person saying a most important thing is making it easy to read that person's thoughts later does not conflict. Those two combine to form facets in a supportive ecosystem.
What are the facets? Which facet matters to you?
[img[http://static.flickr.com/198/501796959_fb70c3da54_m.jpg][http://www.flickr.com/photos/cdent/501796959/]]
! KTHXBAI
At the end of this week, the period of my full time employment with Socialtext will be over. It's time for me to move on and explore other opportunities.
This is standard fare for me: A cycle of learning, followed by execution, followed by learning, then execution, then learning...
It is time for me to go back into the learning phase. Over the last three years the internets have changed and grown. I want to get back into that mix. Into the mix of mashups and APIs, Atom, web services, REST as the accepted proper way to do those web services, OpenID, collaborative and social software.
The web is continuing its progression toward its destiny: a system that enables people and loosely associated groups to easily and cheaply learn, explore and combine ideas. A system that doesn't require the investment and resources of the old school enterprise. I want to get back to the web and experiment, build and add to the tool chest.
I've met a ton of great people as a result of my association with Socialtext. People who have broadened my horizons and my expectations. The world looks full of possibilities.
I'm leaving Socialtext without a ready job. I plan to explore, do some freelancing, and learn as much as possible. I want to do awesome things. If you are doing awesome things and you think I might be able to help or like to be involved, please contact me.
Like every good web guy, I'm in the process of writing my own blogging engine. It's called simper, is written in Python (import antigravity) and will likely end up being some kind of bliki, atom and [[closet]] hybrid. When it's close enough to ready I'll start blogging on http://www.burningchrome.com/. If you're interested, please find me there.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="301 225 48 52"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 333.00003 234 L 306 258.75003 L 301.5 270 L 312.75 265.50003 L 339.75 240.74998 Z M 337.5 229.50002
L 335.24988 231.75008 L 341.99997 238.50003 L 344.24997 236.24995 Z M 342 225.00003 L 339.74988 227.25009
L 346.5 234.00005 L 348.75 231.75003 Z M 301.5 273.9719 C 301.5 273.9719 309.59888 277.99927 317.70013 273.97183
C 325.80066 269.94437 341.99997 276.65686 341.99997 276.65686 L 341.99997 273.97195
C 341.99997 273.97195 325.80014 267.2594 317.70013 271.28687 C 309.6 275.31451 301.5 271.28683 301.5 271.28683 Z"
fill="#101010" class="glyph"/>
</g>
</g>
</svg>
/***
https://github.com/tiddlyweb/chrjs/raw/master/main.js
***/
//{{{
// TiddlyWeb adaptor
// v0.14.2
/*jslint vars: true, unparam: true, nomen: true, white: true */
/*global jQuery */
var tiddlyweb = (function($) {
"use strict";
var tw = {
routes: {
// host is the TiddlyWeb instance's URI (including server_prefix)
// placeholders "_type" & "name" refer to the respective bag/recipe
root : "{host}/",
bags : "{host}/bags",
bag : "{host}/bags/{name}",
recipes : "{host}/recipes",
recipe : "{host}/recipes/{name}",
tiddlers : "{host}/{_type}s/{name}/tiddlers",
tiddler : "{host}/{_type}s/{name}/tiddlers/{title}",
revisions: "{host}/{_type}s/{name}/tiddlers/{title}/revisions",
revision : "{host}/{_type}s/{name}/tiddlers/{title}/revisions/{revision}",
search : "{host}/search?q={query}"
}
};
var convertTimestamp, supplant;
// host (optional) is the URI of the originating TiddlyWeb instance
tw.Resource = function(type, host) {
if(arguments.length) { // initialization
this._type = type;
if(host !== false) {
this.host = host !== undefined ? host.replace(/\/$/, "") : null;
}
}
};
$.extend(tw.Resource.prototype, {
// retrieves resource from server
// callback is passed resource, status, XHR (cf. jQuery.ajax success)
// errback is passed XHR, error, exception, resource (cf. jQuery.ajax error)
// filters is an optional filter string (e.g. "select=tag:foo;limit=5")
get: function(callback, errback, filters) {
var uri = this.route();
if(filters) {
var separator = uri.indexOf("?") === -1 ? "?" : ";";
uri += separator + filters;
}
var self = this;
return $.ajax({
url: uri,
type: "GET",
dataType: "json",
success: function(data, status, xhr) {
var resource = self.parse(data);
resource.etag = xhr.getResponseHeader("Etag");
callback(resource, status, xhr);
},
error: function(xhr, error, exc) {
errback(xhr, error, exc, self);
}
});
},
// sends resource to server
// callback is passed data, status, XHR (cf. jQuery.ajax success)
// errback is passed XHR, error, exception, resource (cf. jQuery.ajax error)
put: function(callback, errback) {
var self = this;
var options = {
url: this.route(),
type: "PUT",
contentType: "application/json",
data: JSON.stringify(this.baseData()),
success: function(data, status, xhr) {
callback(self, status, xhr);
},
error: function(xhr, error, exc) {
errback(xhr, error, exc, self);
}
};
if(this.ajaxSetup) {
this.ajaxSetup(options);
}
return $.ajax(options);
},
// deletes resource on server
// callback is passed data, status, XHR (cf. jQuery.ajax success)
// errback is passed XHR, error, exception, resource (cf. jQuery.ajax error)
"delete": function(callback, errback) {
var self = this;
var options = {
url: this.route(),
type: "DELETE",
success: function(data, status, xhr) {
callback(self, status, xhr);
},
error: function(xhr, error, exc) {
errback(xhr, error, exc, self);
}
};
if(this.ajaxSetup) {
this.ajaxSetup(options);
}
return $.ajax(options);
},
// returns an object carrying only the essential information of the resource
baseData: function() {
var data = {},
self = this;
$.each(this.data, function(i, item) {
var value = self[item];
if(value !== undefined) {
data[item] = value;
}
});
return data;
},
// returns corresponding instance from a raw object (if applicable)
parse: function(data) {
return data;
},
// list of accepted keys in serialization
data: [],
// returns resource's URI
route: function() {
return supplant(tw.routes[this._type], this);
}
});
var Container = function(type, name, host) {
if(arguments.length) { // initialization
tw.Resource.apply(this, [type, host]);
this.name = name;
this.desc = "";
this.policy = new tw.Policy({});
}
};
Container.prototype = new tw.Resource();
$.extend(Container.prototype, {
tiddlers: function() {
return new tw.TiddlerCollection(this);
},
parse: function(data) {
var type = tw._capitalize(this._type),
container = new tw[type](this.name, this.host);
data.policy = new tw.Policy(data.policy);
return $.extend(container, data);
},
data: ["desc", "policy"]
});
// attribs is an object whose members are merged into the instance (e.g. query)
tw.Collection = function(type, host, attribs) {
if(arguments.length) { // initialization
tw.Resource.apply(this, [type, host]);
$.extend(this, attribs);
}
};
tw.Collection.prototype = new tw.Resource();
tw.TiddlerCollection = function(container, tiddler) {
if(arguments.length) { // initialization
tw.Collection.apply(this, [tiddler ? "revisions" : "tiddlers"]);
this.container = container || null;
this.tiddler = tiddler || null;
}
};
tw.TiddlerCollection.prototype = new tw.Collection();
$.extend(tw.TiddlerCollection.prototype, {
parse: function(data) {
var container = this.container;
return $.map(data, function(item, i) {
var tiddler = new tw.Tiddler(item.title, container),
bag = item.bag;
tiddler = tw.Tiddler.prototype.parse.apply(tiddler, [item]);
if(!tiddler.bag && bag) { // XXX: bag always present!?
tiddler.bag = new tw.Bag(bag, container.host);
}
if(!tiddler.recipe && item.recipe) {
tiddler.recipe = new tw.Recipe(item.recipe, container.host);
}
delete item.recipe;
return $.extend(tiddler, item);
});
},
route: function() {
var params = this.container;
if(this.tiddler) {
var container = this.tiddler.bag || this.tiddler.recipe;
params = {
_type: container._type,
host: container.host,
name: container.name,
title: this.tiddler.title
};
}
return supplant(tw.routes[this._type], params);
}
});
tw.Search = function(query, host) {
tw.Collection.apply(this, ["search", host]);
this.query = query;
};
tw.Search.prototype = new tw.Collection();
$.extend(tw.Search.prototype, {
parse: function(data) {
this.container = { // XXX: hacky
_type: "bag",
host: this.host
};
var tiddlers = tw.TiddlerCollection.prototype.parse.apply(this, arguments);
delete this.container;
return tiddlers;
}
});
// title is the name of the tiddler
// container (optional) is an instance of either Bag or Recipe
// optionally accepts a single object representing tiddler attributes
tw.Tiddler = function(title, container) {
tw.Resource.apply(this, ["tiddler", false]);
this.title = title;
this.bag = container && container._type === "bag" ? container : null;
this.recipe = container && container._type === "recipe" ? container : null;
var self = this;
$.each(this.data, function(i, item) {
self[item] = undefined; // exposes list of standard attributes for inspectability
});
if(title && title.title) { // title is an object of tiddler attributes
$.extend(this, title);
}
};
tw.Tiddler.prototype = new tw.Resource();
$.extend(tw.Tiddler.prototype, {
revisions: function() {
return new tw.TiddlerCollection(this.bag || this.recipe, this);
},
route: function() {
var container = this.bag || this.recipe;
var params = $.extend({}, this, {
host: container ? container.host : null,
_type: this.bag ? "bag" : (this.recipe ? "recipe" : null),
name: container ? container.name : null
});
return supplant(tw.routes[this._type], params);
},
parse: function(data) {
var tiddler = new tw.Tiddler(this.title),
container = this.bag || this.recipe;
if(data.bag) {
tiddler.bag = new tw.Bag(data.bag, container.host);
delete data.bag;
}
delete data.recipe;
tiddler.created = data.created ? convertTimestamp(data.created) : new Date();
delete data.created;
tiddler.modified = data.modified ? convertTimestamp(data.modified) : new Date();
delete data.modified;
if(this.recipe) {
tiddler.recipe = this.recipe;
}
return $.extend(tiddler, data);
},
data: ["created", "creator", "modifier", "modified", "tags", "type", "text",
"fields"],
ajaxSetup: function(options) {
var self = this;
if(this.etag && (options.type === "PUT" || options.type === "DELETE")) {
options.beforeSend = function(xhr) {
xhr.setRequestHeader("If-Match", self.etag);
};
}
if(options.type === "PUT") {
var callback = options.success;
options.success = function(data, status, xhr) {
var loc = xhr.getResponseHeader("Location"),
etag = xhr.getResponseHeader("Etag");
if(loc && etag) {
self.etag = etag;
if(!self.bag) {
var bag = loc.split("/bags/").pop().split("/")[0];
self.bag = new tw.Bag(bag, self.recipe.host);
}
callback(self, status, xhr);
} else { // IE
self.get(callback, options.error);
}
};
}
}
});
tw.Revision = function(id, tiddler) {
var container = tiddler.bag || tiddler.recipe;
tw.Tiddler.apply(this, [tiddler.title, container]);
this._type = "revision";
this.revision = id;
};
tw.Revision.prototype = new tw.Tiddler();
$.extend(tw.Revision.prototype, {
revisions: false,
data: false,
put: false,
"delete": false
});
tw.Bag = function(name, host) {
Container.apply(this, ["bag", name, host]);
};
tw.Bag.prototype = new Container();
tw.Recipe = function(name, host) {
Container.apply(this, ["recipe", name, host]);
this.recipe = [];
};
tw.Recipe.prototype = new Container();
$.extend(tw.Recipe.prototype, {
data: ["recipe"].concat(Container.prototype.data)
});
tw.Policy = function(constraints) { // TODO: validation?
var self = this;
$.each(this.constraints, function(i, item) {
self[item] = constraints[item];
});
};
tw.Policy.prototype.constraints = ["read", "write", "create", "delete",
"manage", "accept", "owner"];
/*
* utilities
*/
tw._capitalize = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
// convert YYYYMMDDhhmmss timestamp to Date instance
convertTimestamp = function(t) {
return new Date(Date.UTC(
parseInt(t.substr(0, 4), 10),
parseInt(t.substr(4, 2), 10) - 1,
parseInt(t.substr(6, 2), 10),
parseInt(t.substr(8, 2), 10),
parseInt(t.substr(10, 2), 10),
parseInt(t.substr(12, 2) || "0", 10),
parseInt(t.substr(14, 3) || "0", 10)
));
};
// adapted from Crockford (http://javascript.crockford.com/remedial.html)
supplant = function(str, obj) {
return str.replace(/{([^{}]*)}/g, function (a, b) {
var r = obj[b];
r = typeof r === "string" || typeof r === "number" ? r : a;
return $.inArray(b, ["host", "query"]) !== -1 ? r : encodeURIComponent(r); // XXX: special-casing
});
};
return tw;
}(jQuery));
//}}}
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AP8zZgAAAGYA//8zAADMMwD/mTMAAGYzAP8zMwAAADMAzP8AAADMAADMmQAAAGYAAMwzAAAAAADuzAAA3QAAALvMAACqAAAAiMwAAHcAAABVmQAARAAAACKZAAARAADuAJkA3QAAALsAmQCqAAAAiACZAHcAAABVAJkARAAAACIAZgARAADuAABm3QAAALsAAGaqAAAAiAAAZncAAABVAABmRAAAACIAAGYRAAAA7u7uM93d3QC7u7szqqqqAIiIiDN3d3cAVVVVM0RERAAiIiIzERERAAAAADMBAQEBAQEBAQEBpXl5eXl5eXl5eXmlAQEBAQEBAQEBAQEBAQEBAQEBgHl5eXl5eXl5eXl5eXl5gAEBAQEBAQEBAQEBAQEB/Xp5eXlVT04qKioqTk9VeXl5ev0BAQEBAQEBAQEBAaV5eXlPKioqKioqKioqKioqT3l5eaUBAQEBAQEBAQGAeXlVTioqKioqKioqKioqKioqTlV5eYABAQEBAQEBgHl5VSoqKioqKioqKioqKioqKioqKlV5eYABAQEBAaV5eVUqKioqKioqKioqKioqKioqKioqKlV5eaUBAQEBeXlVKioqKioqKioqKioqKioqKioqKioqKlV5eQEBAXl5eU4qKioqKioqKjExMTExMSoqKioqKioqTnl5eQEBeXlPKioqKioqKjEyMjIyMjIyMjEqKioqKioqT3l5AXp5eSoqKioqKioxMjIxBwcHBzEyMjEqKioqKioqeXl6eXlVKioqKioqMTIxBwcHBwcHBwcxMjEqKioqKipVeXl5eU8qKioqKioyMgcHBwcHBwcHBwcyMioqKioqKk95eXl5TioqKioqMTIxBwcHBwcHBwcHBzEyMSoqKioqTnl5eXkqKioqKioxMgcHBwcHBwcHBwcHBzIxKioqKioqeXl5eSoqKioqKjEyBwcHBwcHBwcHBwcHMjEqKioqKip5eXl5KioqKioqMTIHBwcHBwcHBwcHBwcyMSoqKioqKnl5eXkqKioqKioxMgcHBwcHBwcHBwcHBzIxKioqKioqeXl5eU4qKioqKjEyMQcHBwcHBwcHBwcxMjEqKioqKk55eXl5TyoqKioqKjIyBwcHBwcHBwcHBzIyKioqKioqT3l5eXlVKioqKioqMTIxBwcHBwcHBwcxMjEqKioqKipVeXl6eXkqKioqKioqMTIyMQcHBwcxMjIxKioqKioqKnl5egF5eU8qKioqKioqMTIyMjIyMjIyMSoqKioqKipPeXkBAXl5eU4qKioqKioqKjExMTExMSoqKioqKioqTnl5eQEBAXl5VSoqKioqKioqKioqKioqKioqKioqKipVeXkBAQEB+nl5VSoqKioqKioqKioqKioqKioqKioqVXl5+gEBAQEBenl5VSoqKioqKioqKioqKioqKioqKlV5eXoBAQEBAQEBeXl5VU4qKioqKioqKioqKioqKk5VeXl5AQEBAQEBAQEBenl5eU8qKioqKioqKioqKipPeXl5egEBAQEBAQEBAQEB+nl5eXlVT04qKioqTk9VeXl5efoBAQEBAQEBAQEBAQEBAXl5eXl5eXl5eXl5eXl5eXkBAQEBAQEBAQEBAQEBAQEBAQF6eXl5eXl5eXl5eXoBAQEBAQEBAQEB/+AH//+AAf/+AAB//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAYAAAAHAAAADwAAAA+AAAAfwAAAP+AAAH/wAAD/+AAB//4AB///gB/8oAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAACAAAAAwAAAAMAAAADAAAABG1bSA61m3JXuqB4mbuhd8m9o3jqvaF4+b2hePm9o3jqu6F3ybqgeJm1m3JXbVtIDgAAAAQAAAADAAAAAwAAAAMAAAACAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAwAAAAUAAAAGAAAACI98Wye0nXWavKF4876kev++pHr/vqR6/76kev++pHr/vqR6/76kev++pHr/vqR6/76kev+8oXjztJ11mo98WycAAAAIAAAABgAAAAUAAAADAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAQAAAAHAAAAChwcHBKulnGJvaN5+L6kev++pHr/y7GJ/9/Fnv/s1K7/9t25//rivv/64r7/9t25/+zUrv/fxZ7/y7GJ/76kev++pHr/vaN5+K6WcYkcHBwSAAAACgAAAAcAAAAEAAAAAgAAAAEAAAAAAAAAAAAAAAEAAAADAAAABwAAAAtuYkUst552z76kev++pHr+1LuS//Latf/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD/8tq1/9S7kv++pHr+vqR6/7eeds9uYkUsAAAACwAAAAcAAAADAAAAAQAAAAAAAAABAAAAAgAAAAQAAAAIi3hbNbqgd+a+pHr/xayD/+3Vr//85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA/+3Vr//FrIP/vqR6/7qgd+aJdVg0AAAACAAAAAQAAAACAAAAAQAAAAEAAAACAAAABIl8WSW8oXjlvqR6/8yyiv/54Lz//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//ngvP/Msor/vqR6/7yheOWJfFklAAAABAAAAAIAAAABAAAAAAAAAAFfXz8Iu6F4zL6kev/Msor/+uK+//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//rivv/Msor/vqR6/7uheMxfXz8IAAAAAQAAAAAAAAAAAAAAAbqid4K+pHr/xayD//ngvP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//fgvP/FrIP/vqR6/7qid4IAAAABAAAAAAAAAAC3l28gvaN5+L6kev7t1a///OXA//zlwP/85cD//OXA//zlwP/85cD//OXA/+/Twv/Qq8f/u5HK/7OGzP+zhsz/u5HK/9Crx//v08L//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA/+3Vr/++pHr+vaN5+LeXbyAAAAAAAAAAALuheJa+pHr/1LuS//zlwP/85cD//OXA//zlwP/85cD//OXA//riwP/Pq8f/r4HM/6+Bzf+vgc3/r4HN/6+Bzf+vgc3/r4HN/6+BzP/Pq8f/+uLA//zlwP/85cD//OXA//zlwP/85cD//OXA/9S5kv++pHr/u6F4lgAAAACii3MLvKF4876kev/y2rX//OXA//zlwP/85cD//OXA//zlwP/64sD/w5vJ/6+Bzf+vg83/w5vc/9W06v/dwPD/3cDw/9W06v/Dm9z/r4PN/6+Bzf/Dm8n/+uLA//zlwP/85cD//OXA//zlwP/85cD/8tq1/76kev+8oXjzootzC72feFW+pHr/y7GJ//zlwP/85cD//OXA//zlwP/85cD//OXA/8+rx/+vgc3/tIfQ/9a16//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//Wtev/tIfQ/6+Bzf/Pq8f//OXA//zlwP/85cD//OXA//zlwP/85cD/y7GJ/76kev+9n3hVvaF4mL6kev/fxZ7//OXA//zlwP/85cD//OXA//zlwP/v08L/r4HM/6+Dzf/Wtev/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//Wtev/r4PN/6+BzP/v08L//OXA//zlwP/85cD//OXA//zlwP/fxZ7/vqR6/72heJi8oXfIvqR6/+zUrv/85cD//OXA//zlwP/85cD//OXA/9Crx/+vgc3/w5vc/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//Dm9z/r4HN/9Crx//85cD//OXA//zlwP/85cD//OXA/+zUrv++pHr/vKF3yL2jeOq+pHr/9t25//zlwP/85cD//OXA//zlwP/85cD/u5HK/6+Bzf/VtOr/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/9W06v+vgc3/u5HK//zlwP/85cD//OXA//zlwP/85cD/9t25/76kev+9o3jqvaF4+b6kev/64r7//OXA//zlwP/85cD//OXA//zlwP+zhsz/r4HN/93A8P/hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/3cDw/6+Bzf+zhsz//OXA//zlwP/85cD//OXA//zlwP/64r7/vqR6/72hePm9oXj5vqR6//rivv/85cD//OXA//zlwP/85cD//OXA/7OGzP+vgc3/3cDw/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//dwPD/r4HN/7OGzP/85cD//OXA//zlwP/85cD//OXA//rivv++pHr/vaF4+b2jeOq+pHr/9t25//zlwP/85cD//OXA//zlwP/85cD/u5HK/6+Bzf/VtOr/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/9W06v+vgc3/u5HK//zlwP/85cD//OXA//zlwP/85cD/9t25/76kev+9o3jqvKF3yL6kev/s1K7//OXA//zlwP/85cD//OXA//zlwP/Qq8f/r4HN/8Ob3P/hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/w5vc/6+Bzf/Qq8f//OXA//zlwP/85cD//OXA//zlwP/s1K7/vqR6/7yhd8i9oXiYvqR6/9/Fnv/85cD//OXA//zlwP/85cD//OXA/+/Twv+vgcz/r4PN/9a16//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//hw/P/4cPz/9a16/+vg83/r4HM/+/Twv/85cD//OXA//zlwP/85cD//OXA/9/Fnv++pHr/vaF4mL2feFW+pHr/y7GJ//zlwP/85cD//OXA//zlwP/85cD//OXA/8+rx/+vgc3/tIfQ/9a16//hw/P/4cPz/+HD8//hw/P/4cPz/+HD8//Wtev/tIfQ/6+Bzf/Pq8f//OXA//zlwP/85cD//OXA//zlwP/85cD/y7GJ/76kev+9n3hVootzC7yhePO+pHr/8tq1//zlwP/85cD//OXA//zlwP/85cD/+uLA/8Obyf+vgc3/r4PN/8Ob3P/VtOr/3cDw/93A8P/VtOr/w5vc/6+Dzf+vgc3/w5vJ//riwP/85cD//OXA//zlwP/85cD//OXA//Latf++pHr/vKF486KLcwsAAAAAu6N3l76kev/Uu5L//OXA//zlwP/85cD//OXA//zlwP/85cD/+uLA/8+rx/+vgcz/r4HN/6+Bzf+vgc3/r4HN/6+Bzf+vgc3/r4HM/8+rx//64sD//OXA//zlwP/85cD//OXA//zlwP/85cD/1LmS/76kev+7oXiWAAAAAAAAAAC3l28gvaN5+L6kev7t1a///OXA//zlwP/85cD//OXA//zlwP/85cD//OXA/+/Twv/Qq8f/u5HK/7OGzP+zhsz/u5HK/9Crx//v08L//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA/+3Vr/++pHr+vaN5+LeXbyAAAAAAAAAAAAAAAAC6oneCvqR6/8Wsg//54Lz//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/34Lz/xayD/76kev+6oneCAAAAAAAAAAAAAAAAAAAAAH9/VQa8oHjLvqR6/8yyiv/64r7//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD/+uK+/8yyiv++pHr/vKB4y39/VQYAAAAAAAAAAAAAAAAAAAAAAAAAALKhbh67o3nkvqR6/8yyiv/54Lz//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//ngvP/Msor/vqR6/7ujeeSyoW4eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALqbdim7o3nkvqR6/8Wsg//t1a///OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/t1a//xayD/76kev+7o3nkupt2KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALKhbh68oHjLvqR6/76kev7Uu5L/8tq1//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/85cD//OXA//zlwP/y2rX/1LmS/76kev6+pHr/vKB4y7Khbh4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH9/VQa6oneCvaN5+L6kev++pHr/y7GJ/9/Fnv/s1K7/9t25//rivv/64r7/9t25/+zUrv/fxZ7/y7GJ/76kev++pHr/vaN5+Lqid4J/f1UGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3l28gu6F4lryhePO+pHr/vqR6/76kev++pHr/vqR6/76kev++pHr/vqR6/76kev++pHr/vKF487uheJa3l28gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAootzC72feFW9oXiYvKF3yL2jeOq9oXj5vaF4+b2jeOq8oXfIvaF4mL2feFWii3MLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gB///gAH//gAAf/wAAD/4AAAf8AAAD+AAAAfAAAADwAAAA4AAAAGAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//gAAf/+AAf//4Af/
[img[http://static.flickr.com/95/232250019_21e991a838_m.jpg][http://www.flickr.com/photos/cdent/232250019/]]
! Blogging and Headspace
Tonight Matthew asked that I join in and do more technical blogging: The sort of blogging that allows people to learn from each other's experience. For example, I'm learning some Python at the moment, so could be reporting here about what I've learned, doing some compare and constrast with Perl and generally a bit of an expose on the experience. Or perhaps more on the details of proper HTTP handling when making a REST API. Or more about [[In Other Words]] and why it is //one of// the right ways to design a wiki.
But the simple truth is that often I just don't have the headspace for it. Writing, at least for me, requires some free space in the head where ideas can be tossed around freely and watched for a while to see where they settle. In a further free moment I then go back through that space and scoop up the good bits in an organized pile and dump them out on the page. In the current suite of project handling for the [[API Team]] I don't have the room in my head. Too many projects are in my process space, either right in the foreground, or waking up every now and again with a little bit of info here, and a little bit of info there. At the end of the day I'm usually spread out and want to do something stupid like watch TV. Which is very lame, but there it is.
So I bought a better TV. It's nice, but now I fear I'm going to have to build back up to the fancy audio system I used to have before I moved out to Seattle. My speakers liked a lot of current: When held in the powerful grasp of amps to spare, they produced sounds of beauty, grace and precision. An amplifier which is able to produce a large amount of current, in excess of the average demands, is said to have "headspace".
To be fully creative and innovative, I need more headspace.
/***
|''Name''|BinaryUploadPlugin|
|''Version''|0.3.15|
|''Author''|Ben Gillies and Jon Robson|
|''Type''|plugin|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/BinaryUploadPlugin.js|
|''Description''|Upload a binary file to TiddlyWeb|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlySpaceConfig TiddlyWebConfig|
!Usage
{{{
<<binaryUpload bag:<name> edit:tags edit:title tags:<default tags> title:<title> >>
}}}
* {{{bag:<name>}}}: optional; if left out, the file will be saved to the current workspace
* {{{edit:tags}}}: specifies that you want to tag the file being uploaded
* {{{edit:title}}}: specifies that you want to set the title to something other than the filename
* {{{tags:<default tags>}}}: specifies a default set of tags to apply to the file (requires {{{edit:tags}}} to be set)
* {{{title:<title>}}}: predefines the title of the binary tiddler
!Requires
TiddlyWeb
tiddlywebplugins.form
!Code
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var macro = config.macros.binaryUpload = {
locale: {
titleDefaultValue: "Please enter a title...",
tagsDefaultValue: "Please enter some tags...",
titlePrefix: "title: ",
tagsPrefix: "tags: ",
loadSuccess: 'Tiddler %0 successfully uploaded',
loadError: "An error occurred when uploading the tiddler %0",
uploadInProgress: "Please wait while the file is uploaded...",
membersOnly: "Only members can upload."
},
renderInputFields: function(container, options) {
var locale = macro.locale;
var editableFields = options.edit;
var includeFields = {
tags: editableFields && editableFields.contains("tags") ? true : false,
title: editableFields && editableFields.contains("title") ? true : false
};
var fields = ["title", "tags"];
for(var i = 0; i < fields.length; i++) {
var fieldName = fields[i];
var userDefault = options[fieldName];
var defaultValue = userDefault ? userDefault[0] : false;
if(includeFields[fieldName] || defaultValue) {
var localeDefault = locale["%0DefaultValue".format(fieldName)];
var className = defaultValue ? "userInput" : "userInput notEdited";
var inputEl;
var val = defaultValue || localeDefault || "";
var iContainer = $("<div />").addClass("binaryUpload%0".format(fieldName)).
appendTo(container);
if(defaultValue && !includeFields[fieldName]) {
var label = locale["%0Prefix".format(fieldName)];
$("<span />").text(label).appendTo(iContainer);
$("<span />").addClass("disabledInput").text(val).appendTo(iContainer);
inputEl = $("<input />").attr("type", "hidden");
} else {
inputEl = $("<input />").attr("type", "text");
}
inputEl.attr("name", fieldName).
addClass("%0Edit".format(fieldName)).
val(val).addClass(className).appendTo(iContainer);
}
}
},
getTiddlerName: function(fileName) {
var fStart = fileName.lastIndexOf("\\");
var fStart2 = fileName.lastIndexOf("/");
fStart = fStart < fStart2 ? fStart2 : fStart;
fileName = fileName.substr(fStart+1);
return fileName;
},
errorHandler: function(fileName) {
displayMessage("upload of file %0 failed".format(fileName));
},
uploadFile: function(place, baseURL, workspace, options) {
var pleaseWait = $(".uploadProgress", place);
var iframeName = options.target;
var form = $("form", place);
var existingVal = $("input[name=title]", form).val();
var fileName = existingVal || $('input:file', form).val();
if(!fileName) {
return false; // the user hasn't selected a file yet
}
fileName = macro.getTiddlerName(fileName);
$("input[name=title]", place).val(fileName);
// we need to go somewhere afterwards to ensure the onload event triggers
var redirectTo = "/%0/tiddlers.txt?select=title:%1".
format(workspace, fileName);
var token = tiddlyspace ? tiddlyspace.getCSRFToken() : "";
var action = "%0?csrf_token=%1&redirect=%2"
.format(baseURL, token, redirectTo);
form[0].action = action; // dont use jquery to work with ie
form[0].target = iframeName;
// do not refactor following line... won't work in IE6 otherwise
$(place).append($('<iframe name="' + iframeName + '" id="' + iframeName + '"/>').css('display','none'));
macro.iFrameLoader(iframeName, function() {
var content = document.getElementById(iframeName).contentWindow.document.documentElement;
if($(content).text().indexOf(fileName) > -1) {
options.callback(place, fileName, workspace, baseURL);
} else {
macro.errorHandler(fileName);
}
form.show(1000);
pleaseWait.hide(1000);
});
form.hide(1000);
pleaseWait.show(1000);
return true;
},
createUploadForm: function(place, options) {
var locale = macro.locale;
if(readOnly) {
$('<div class="annotation" />').text(locale.membersOnly).
appendTo(place);
return;
}
var bag = options.bag;
options.callback = options.callback ? options.callback :
function(place, fileName, workspace, baseurl) {
macro.displayFile(place, fileName, workspace);
displayMessage(locale.loadSuccess.format(fileName));
$("input[type=text]", place).val("");
};
var defaults = config.defaultCustomFields;
place = $("<div />").addClass("container").appendTo(place)[0];
var workspace = bag ? "bags/%0".format(bag) : config.defaultCustomFields["server.workspace"];
var baseURL = defaults["server.host"];
baseURL += (baseURL[baseURL.length - 1] !== "/") ? "/" : "";
baseURL = "%0%1/tiddlers".format(baseURL, workspace);
//create the upload form, complete with invisible iframe
var iframeName = "binaryUploadiframe%0".format(Math.random());
// do not refactor following line of code to work in IE6.
var form = $('<form action="%0" method="POST" enctype="multipart/form-data" />'.
format(baseURL)).addClass("binaryUploadForm").
appendTo(place)[0];
macro.renderInputFields(form, options);
$(form).
append('<div class="binaryUploadFile"><input type="file" name="file" /></div>').
append('<div class="binaryUploadSubmit"><input type="submit" value="Upload" /></div>').
submit(function(ev) {
this.target = iframeName;
options.target = iframeName;
macro.uploadFile(place, baseURL, workspace, options);
});
$('<div />').addClass("uploadProgress").text(locale.uploadInProgress).hide().appendTo(place);
$("input[name=file]", place).change(function(ev) {
var target = $(ev.target);
var fileName = target.val();
var title = $("input[type=text][name=title]", place);
if(!title.val()) {
title.val(fileName);
}
});
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
params = paramString.parseParams(null, null, true);
macro.createUploadForm(place, params[0]);
},
iFrameLoader: function(iframeName, callback) {
var iframe = document.getElementById(iframeName); //jQuery doesn't seem to want to do this!?
var locale = macro.locale;
$(".userInput").addClass("notEdited"); // reset editing
var finishedLoading = function() {
callback();
};
var iFrameLoadHandler = function() {
finishedLoading.apply();
return;
};
iframe.onload = iFrameLoadHandler;
//IE
completeReadyStateChanges = 0;
iframe.onreadystatechange = function() {
if (++(completeReadyStateChanges) == 3) {
iFrameLoadHandler();
}
};
},
displayFile: function(place, title, workspace) {
var adaptor = store.getTiddlers()[0].getAdaptor();
var context = {
workspace: workspace,
host: config.defaultCustomFields['server.host']
};
adaptor.getTiddler(title, context, null, function(context) {
if(context.status) {
store.addTiddler(context.tiddler);
story.displayTiddler(place, title);
var image = config.macros.image;
if(image && image.refreshImage) {
image.refreshImage("/%0/tiddlers/%1".format(workspace, title));
image.refreshImage(title);
image.refreshImage("/%0".format(title));
image.refreshImage("%0/%1/tiddlers/%2".format(config.extensions.tiddlyweb.host, workspace, title));
}
} else {
displayMessage(macro.locale.loadError.format(title));
}
});
}
};
if(tiddlyspace) {
config.macros.binaryUploadPublic = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var options = paramString.parseParams(null, null, true)[0];
var bag = tiddlyspace.getCurrentBag("public");
options.bag = bag;
macro.createUploadForm(place, options);
}
};
config.messages.privacySetting = config.options.chkPrivateMode ?
"private" : "public";
config.macros.binaryUpload.defaultWorkspace = tiddlyspace.
getCurrentWorkspace(config.messages.privacySetting);
}
})(jQuery);
//}}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>TiddlySpace Apps</title>
<link rel="stylesheet" href="/bags/common/tiddlers/reset.css" />
<link rel="stylesheet" href="/bags/common/tiddlers/appspage.css" />
<!--[if lt IE 7 ]>
<link rel="stylesheet" href="/bags/common/tiddlers/appspageie6.css" />
<![endif]-->
</head>
<body>
<div id="wrapper">
<div id="TSbar"></div>
<div id="main-content">
<div id="space-details">
<a href="/_space"><img class="siteicon"></a>
<div id="title-subtitle">
<h1 class="spaceaddress">
<span class="spaceName"></span><span class="hostName"></span>
</h1>
<p class="tagline"><span class="subTitle"></span><a class="managespaces" href="/_space">manage space</a></p>
</div>
</div>
<div id="holder">
<div id="appswitcher-wrapper">
<div id="appswitcher">
<h2>Your Apps</h2>
<ul id="app-list">
<li class="write"><a href="/takenote">
<img src="/bags/common/tiddlers/pencil_blue.png" alt="Icon for Takenote" class="app-img" />
WRITE
</a>
</li>
<li class="htmlserialisation">
<a href="/tiddlers.html?select=tag:!excludeLists;sort=-modified">
<img src="/bags/common/tiddlers/browse_read_blue.png" alt="Icon for the HTML Serialisation" class="app-img" />
BROWSE
</a>
</li>
<li class="links">
<a href="/links">
<img src="/bags/common/tiddlers/share_blue.png" alt="Icon for the Links App" class="app-img" />
SHARE
</a>
</li>
<li class="tiddlywiki">
<a href="/tiddlers.wiki">
<img src="/bags/common/tiddlers/tiddlywiki2_blue.png" alt="Icon for TiddlyWiki" class="app-img" />
TIDDLYWIKI
</a>
</li>
</ul>
<div id="addapp">
<button class="inactive">Add More!</button>
</div>
</div>
<div id="app-desc">
<ul>
<li class="writedesc"><p>write your notes on the web. Link them, tag them, share them. Your notes are available everywhere, on and offline.</p></li>
<li class="htmlserialisationdesc"><p>an easy to understand HTML representation of your content.</p></li>
<li class="linksdesc"><p>share direct links to your content. The share app provides you with quick and easy access to the key links within your TiddlySpace.</p></li>
<li class="tiddlywikidesc"><p>use TiddlyWiki to create, edit and organise your content.</p></li>
</ul>
</div>
<div style="clear: both;"></div>
</div>
</div>
</div>
<div id="footer"><!-- ie doesn't support footer tag -->
<div id="footer-content">
<div class="links">
<a href="http://blog.tiddlyspace.com">blog</a>
<a href="http://featured.tiddlyspace.com">featured</a>
<a href="http://docs.tiddlyspace.com">documentation</a>
<a href="https://github.com/TiddlySpace/tiddlyspace">source</a>
</div>
<p>TiddlySpace 2011, created by <a href="http://osmosoft.com">Osmosoft</a></p>
</div>
</div>
</div>
<script type="text/javascript" src="/bags/common/tiddlers/backstage.js"></script>
<script type="text/javascript" src="/bags/common/tiddlers/jquery.js"></script>
<script type="text/javascript" src="/bags/tiddlyspace/tiddlers/chrjs"></script>
<script type="text/javascript" src="/bags/common/tiddlers/chrjs-store.js"></script>
<script type="text/javascript" src="/bags/common/tiddlers/jquery-json.js"></script>
<script type="text/javascript" src="/bags/common/tiddlers/appspage.js"></script>
</body>
</html>
<!--{{{-->
<div class='header'>
<div id='sidebarSearch'>
<span macro='search'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='clearFloat'></div>
</div>
<div id='menuBar'>
<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<!--}}}-->
/***
|''Name''|TiddlySpaceInitialization|
|''Version''|0.7.2|
|''Description''|Initializes new TiddlySpaces the first time they are created|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/blob/master/src/plugins/TiddlySpaceInit.js|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlySpaceConfig RandomColorPalettePlugin chrjs ImageMacroPlugin|
!TODO
* robust error notification and recovery
!MarkupPreHead
<!--{{{-->
<link rel="shortcut icon" href="/recipes/%0_public/tiddlers/favicon.ico" />
<link href="/bags/%0_public/tiddlers.atom" rel="alternate"
type="application/atom+xml" title="%0's public feed" />
<link rel="canonical" href="%1/" />
<!--}}}-->
!Code
***/
//{{{
(function($) {
var versionField = "tiddlyspaceinit_version";
var markupPreHead = store.getTiddlerText(tiddler.title + "##MarkupPreHead", "");
var tiddlyspace = config.extensions.tiddlyspace;
var currentSpace = tiddlyspace.currentSpace;
var tweb = config.extensions.tiddlyweb;
var plugin = config.extensions.TiddlySpaceInit = {
version: "0.6",
SiteTitle: "%0",
SiteSubtitle: "a TiddlySpace",
flagTitle: "%0SetupFlag",
flagWarning: "Please do not modify this tiddler; it was created " +
"automatically upon space creation.",
dispatch: function(ev) {
var title = plugin.flagTitle.format([currentSpace.name]);
config.annotations[title] = plugin.flagWarning;
if(currentSpace.type != "private") {
return;
}
var tiddlers = [];
var tid = store.getTiddler(title);
if(tid) {
curVersion = parseFloat(tid.fields[versionField]);
reqVersion = parseFloat(plugin.version);
if(curVersion < reqVersion) {
plugin.update(curVersion, tid);
tid.fields[versionField] = plugin.version;
tid.incChangeCount();
tid = store.saveTiddler(tid);
tiddlers.push(tid);
}
} else { // first run
tid = new Tiddler(title);
tid.tags = ["excludeLists", "excludeSearch", "excludePublisher"];
tid.fields = $.extend({}, config.defaultCustomFields);
tid.fields[versionField] = plugin.version;
tid.text = "@@%0@@".format([plugin.flagWarning]);
tid = store.saveTiddler(tid);
tiddlers = tiddlers.concat(plugin.firstRun(), tid);
}
autoSaveChanges(null, tiddlers);
},
update: function(curVersion, flagTiddler) {
if(curVersion < 0.2) {
this.createAvatar();
}
if(curVersion < 0.3) {
flagTiddler.tags.pushUnique("excludePublisher"); // XXX: never persisted
}
if(curVersion < 0.5) { // v0.4 was faulty
this.setupMarkupPreHead();
}
if(curVersion < 0.6) {
this.purgeSystemSettings();
}
},
pubTid: {
tags: ["excludeLists", "excludeSearch"],
fields: $.extend({}, config.defaultCustomFields, {
"server.workspace": tiddlyspace.getCurrentWorkspace("public")
})
},
makeTiddlerIfNot: function(tiddler) {
if (!store.tiddlerExists(tiddler.title)) {
$.extend(true, tiddler, plugin.pubTid);
return [store.saveTiddler(tiddler)];
} else {
return [];
}
},
firstRun: function() {
var tiddlers = [];
// generate Site*itle
$.each(["SiteTitle", "SiteSubtitle"], function(i, item) {
var tid = new Tiddler(item);
tid.text = plugin[item].format([currentSpace.name]);
tiddlers.push.apply(tiddlers,
plugin.makeTiddlerIfNot(tid));
});
// generate public ColorPalette
var tid = new Tiddler("ColorPalette");
tid.text = config.macros.RandomColorPalette.generatePalette({
saturation_pale: 0.67, saturation_light: 0.53,
saturation_mid: 0.43, saturation_dark: 0.06,
pale: 0.99, light: 0.85, mid: 0.5, dark: 0.31
},
false);
tiddlers.push.apply(tiddlers, plugin.makeTiddlerIfNot(tid));
this.createAvatar();
this.setupMarkupPreHead();
return tiddlers;
},
// remove _cookie slices (TiddlyWiki 2.6.2 beta 6 remnants)
purgeSystemSettings: function() {
var ss = store.getTiddler("SystemSettings");
if(ss) {
var lines = ss.text.split("\n");
var persistentOptions = $.grep(lines, function(line, i) {
return line.indexOf("_cookie:") == -1;
});
ss.text = persistentOptions.join("\n");
ss = store.saveTiddler(ss);
autoSaveChanges(null, [ss]);
}
},
createAvatar: function() {
var avatar = "SiteIcon";
var host = tweb.host;
var notify = function(xhr, error, exc) {
displayMessage("ERROR: could not create avatar - " + // TODO: i18n
"%0: %1".format([xhr.statusText, xhr.responseText]));
// TODO: resolve!?
};
var pubBag = tiddlyspace.getCurrentBag("public");
var tid = new tiddlyweb.Tiddler(avatar);
tid.bag = new tiddlyweb.Bag(pubBag, host);
var callback = function(data, status, xhr) {}; // avatar already exists; do nothing
var errback = function(xhr, error, exc) {
if(xhr.status != 404) {
return;
}
// copy default avatar
var _notify = function(tid, status, xhr) {
displayMessage("created avatar"); // TODO: i18n
var image = config.macros.image;
if(image && image.refreshImage) {
var uri = "/%0/tiddlers/SiteIcon".
format(tiddlyspace.getCurrentWorkspace("public"));
image.refreshImage(uri);
image.refreshImage("SiteIcon");
}
};
var _callback = function(tid, status, xhr) {
tid.title = avatar;
tid.bag.name = pubBag;
delete tid.etag;
tid.put(_notify, notify); // TODO: add to current session document (via adaptor?)
};
tweb.getUserInfo(function(user) {
var avatarTitle = currentSpace.name == user.name ?
"defaultUserIcon" : "defaultSiteIcon";
var tid = new tiddlyweb.Tiddler(avatarTitle);
tid.bag = new tiddlyweb.Bag("common", host);
tid.get(_callback, notify);
});
};
tid.get(callback, errback);
},
setupMarkupPreHead: function() {
var pubWorkspace = tiddlyspace.getCurrentWorkspace("public");
var existing = store.getTiddler("MarkupPreHead");
if(!existing || existing.fields["server.workspace"] != pubWorkspace) {
tweb.getStatus(function(status) {
var tid = new Tiddler("MarkupPreHead");
tid.text = markupPreHead.format(currentSpace.name, tiddlyspace.getHost(status.server_host,
currentSpace.name));
tid.tags = ["excludeLists"];
tid.fields = $.extend({}, config.defaultCustomFields);
tid.fields["server.workspace"] = pubWorkspace;
tid.fields["server.page.revision"] = "false";
tid = store.saveTiddler(tid);
autoSaveChanges(null, [tid]);
});
}
}
};
$(document).bind("startup", plugin.dispatch);
})(jQuery);
//}}}
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
/***
|''Name:''|TiddlySpaceLinkPlugin|
|''Description:''|Formatter to reference other spaces from wikitext |
|''Author:''|PaulDowney (psd (at) osmosoft (dot) com) |
|''Source:''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceLinkPlugin.js|
|''Version:''|1.4.2|
|''License:''|[[BSD License|http://www.opensource.org/licenses/bsd-license.php]] |
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''~CoreVersion:''|2.4|
!!Documentation
This plugin provides wikitext formatters for referencing another [[space|Space]] on the same TiddlySpace server, as in the following examples:
<<<
{{{@space}}} -- @space
{{{~@space}}} -- ~@space
{{{Tiddler@space}}} -- Tiddler@space
{{{[[Tiddler Title]]@space}}} -- [[Tiddler Title]]@space
{{{[[Link text|Tiddler Title]]@space}}} -- [[Link text|Tiddler Title]]@space
<<<
Links to tiddlers with a title begining with an "@" remain as tiddlyLinks:
<<<
{{{[[@tiddler]]}}} -- [[@tiddler]]
<<<
and these may be changed into a space link using {{{@@}}}:
<<<
{{{[[@@space]]}}} -- [[@@space]]
{{{[[Link to an another space|@@space]]}}} -- [[Link to another space|@@space]]
{{{[[@space|@@space]]}}} -- [[@space|@@space]]
<<<
TiddlySpace includes the [[TiddlySpaceLinkPlugin]] which provides WikiText markup for linking to other spaces on the same server. For example @glossary is a link to the {{{glossary}}} space and [[Small Trusted Group]]@glossary a link to an individual tiddler in the @glossary space. Prefixing the link with a tilde escapes the link, for example {{{~@space}}}.
Email addresses, for example joe.bloggs@example.com and mary@had.a.little.lamb.org should be unaffected.
!!Features
The plugin provides external links decorated so that other plugins may be included to add features such as the ability to dynamically pull externally linked tiddlers into the current TiddlyWiki.
Wikitext linking to a space on another server, for example from a tiddler in a space on tiddlyspace.com to a tiddler or a space on example.com, isn't currently supported.
!!Code
***/
//{{{
/*jslint onevar: false nomen: false plusplus: false */
/*global jQuery config createTiddlyText createExternalLink createTiddlyLink */
function createSpaceLink(place, spaceName, title, alt, isBag) {
var link, a, currentSpaceName, label;
try {
if (spaceName === config.extensions.tiddlyspace.currentSpace.name) {
title = title || spaceName;
a = createTiddlyLink(place, title, false);
jQuery(a).text(alt || title);
return a;
}
} catch (ex1) {
currentSpaceName = false;
}
a = jQuery("<a />").addClass('tiddlySpaceLink externalLink').appendTo(place)[0];
if(title) {
jQuery(a).attr('tiddler', title);
}
if(isBag) {
jQuery(a).attr('bag', spaceName);
} else {
jQuery(a).attr('tiddlyspace', spaceName);
}
config.extensions.tiddlyweb.getStatus(function(status) {
link = status.server_host.url;
if (title) {
label = alt || title;
link = link + "/" + encodeURIComponent(title);
} else {
label = alt || spaceName;
}
// assumes a http URI without user:pass@ prefix
if(!isBag) {
link = link.replace("http://", "http://" + spaceName.toLowerCase() + ".");
} else {
link += "/bags/" + spaceName + "/tiddlers.wiki";
}
jQuery(a).attr("href", link).text(label);
});
return a;
}
(function ($) {
config.textPrimitives.spaceName = "[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]";
config.textPrimitives.spaceNameStrict = "[a-z][a-z0-9-]*";
config.textPrimitives.bareTiddlerLetter = config.textPrimitives.anyLetterStrict;
config.formatters.splice(0, 0, {
name: "spacenameLink",
match: config.textPrimitives.unWikiLink + "?" + config.textPrimitives.bareTiddlerLetter + "*@" + config.textPrimitives.spaceName + "\\.?.?",
lookaheadRegExp: new RegExp(config.textPrimitives.unWikiLink + "?(" + config.textPrimitives.bareTiddlerLetter + "*)@(" + config.textPrimitives.spaceName + ")", "mg"),
handler: function (w) {
if (w.matchText.substr(w.matchText.length - 2, 1) === '.' && w.matchText.substr(w.matchText.length - 1, 1).match(/[a-zA-Z]/)) {
w.outputText(w.output, w.matchStart, w.nextMatch);
return;
}
if (w.matchText.substr(0, 1) === config.textPrimitives.unWikiLink) {
w.outputText(w.output, w.matchStart + 1, w.nextMatch);
return;
}
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index === w.matchStart) {
createSpaceLink(w.output, lookaheadMatch[2], lookaheadMatch[1]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "tiddlySpaceLink",
match: "\\[\\[[^\\|\\]]*\\|*@@" + config.textPrimitives.spaceName + "\\]",
lookaheadRegExp: new RegExp("\\[\\[(.*?)(?:\\|@@(.*?))?\\]\\]", "mg"),
handler: function (w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index === w.matchStart) {
var alt = lookaheadMatch[2] ? lookaheadMatch[1] : lookaheadMatch[1].replace(/^@@/, "");
var space = lookaheadMatch[2] || alt;
createSpaceLink(w.output, space, "", alt);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "tiddlyLinkSpacenameLink",
match: "\\[\\[[^\\[]*\\]\\]@",
lookaheadRegExp: new RegExp("\\[\\[(.*?)(?:\\|(.*?))?\\]\\]@(" + config.textPrimitives.spaceName + ")", "mg"),
handler: function (w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index === w.matchStart) {
var title = lookaheadMatch[2] || lookaheadMatch[1];
var alt = lookaheadMatch[1] || lookaheadMatch[2];
createSpaceLink(w.output, lookaheadMatch[3], title, alt);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
});
// ensure space links don't appear as missing links
config.textPrimitives.brackettedLink = "\\[\\[([^\\]][^@\\]][^\\]]*)\\]\\](?=[^@])";
config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\](?=[^@])";
// reevaluate derrived expressions ..
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
// treat space links in titledBracketedLink as external links
var missingTiddlySpaceLink = new RegExp("^@@" + config.textPrimitives.spaceName + "$", "");
var isExternalLink = config.formatterHelpers.isExternalLink;
config.formatterHelpers.isExternalLink = function(link) {
return missingTiddlySpaceLink.test(link) || isExternalLink(link);
};
}(jQuery));
//}}}
[img[http://static.flickr.com/152/347583367_86c4e975bc_m.jpg][http://www.flickr.com/photos/cdent/347583367/]]
! Is that a Wiki?
A lot of folk, before ever really using a wiki much, seem to want to know if it will support features more appropriate for a content management system. Most of these thing are related to more control:
* fine grained control of editing permissions
* fine grained control of content layout, color, appearance, what not
* strong indications of identity (this page is so and sos)
* strong indications of who is reading what and how much something is being read
* ways of rating content
There's nothing particularly wrong with any of these things, but in my experience if you focus on those things, you end up with something that is not a wiki. In a wiki, content trumps control and identity; linking trumps statistics and ratings.
For the sake of argument I'm going to toss out an incomplete strawman: The original wiki gang was more concerned with content, learning, exploration and information relationships. The blog gang is more concerned about expression, identity, and social relationships. Wiki + Blog + Corporations yields Enterprise 2.0 wanna be content management system hybrid thing.
Again, not a bad thing, but not really a wiki.
It's interesting that the social relationships which grow out of the information relationships exposed by a wiki are often quite robust. I suspect there is some kind of "wikis are better for communities of practice" statement that could be made here.
How many wiki personalities have you heard of? Compare that with how many blog personalities you've heard of.
I'll wager that the more control and identity play a part in a commerical wiki system the less useful it will be as an information resource [1] but the more satisfying it will be perceived to be for people making purchases and using the system.
In the end it all comes down to being able to create and follow links from one page to another [2]. When that constraint is blown apart or made more complicated, the affordances of the system change greatly. Maybe in good ways, but maybe not.
----
[1] This could be measured by a few different factors, primarily how often pages are linked to from within the wiki and from other resources (for example do emails in the community point back to the wiki).
[2] Note to self: what about links from one page to a set of pages (tags, search results, aggregate lists)? I suspect this, though useful, breaks the constraint and is part of what hinders the formation of cow paths in a wiki. We need only look at the incredible proliferation of new pages and few page to page links in the socialtext internal wiki to see this happening.
|~ViewToolbar|+editTiddler +cloneTiddler > fields refreshTiddler changeToPublic changeToPrivate revisions syncing permalink references jump closeOthers < closeTiddler|
|~EditToolbar|+saveTiddler saveDraft -cancelTiddler deleteTiddler|
|~RevisionToolbar|> fields revert|
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
highlights from an old blog
/***
|''Name''|TiddlySpaceRevisionView|
|''Description''|Show tiddler revisions in a stack of cards view|
|''Author''|BenGillies|
|''Version''|0.2.0|
|''Status''|beta|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor|
!Usage
The viewRevisions macro can be attached to any element, which should be passed
in as a parameter.
For example:
<<viewRevisions page:10 link:"<<view modified date>>">>
would show the revisions "stack of cards" view, 10 at a time, when the modified
date is clicked.
!Code
***/
//{{{
(function($) {
var me = config.macros.viewRevisions = {
revisionTemplate: "RevisionTemplate",
revSuffix: " [rev. #%0]", // text to append to each tiddler title
defaultPageSize: 5, // default number of revisions to show
defaultLinkText: "View Revisions", // when there's nothing else to use
offsetTop: 30, // in px
offsetLeft: 10, // in px
shiftDownDelay: 50, // in ms
visibleSlideAmount: 20, // amount of revisions to show on left hand edge after sliding
zIndex: 100, // default z-index
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
params = paramString.parseParams(null, null, true)[0];
var tiddlerElem = story.findContainingTiddler(place);
var revButton;
var pageSize = parseInt(params.page[0], 10) || me.defaultPageSize;
var linkObj = params.link ? params.link[0] || me.defaultLinkText : false;
if(linkObj) {
revButton = $('<span class="button openRevisions" />')
.appendTo(place);
wikify(linkObj, revButton[0], null, tiddler);
} else {
revButton = place;
}
$(revButton).click(function() {
if (!$(tiddlerElem).hasClass("revisions")) {
me.showRevisions(tiddlerElem, tiddler, pageSize);
} else {
me.closeRevisions(tiddlerElem);
}
});
},
// initialisation for revision view
showRevisions: function(tiddlerElem, tiddler, pageSize) {
var context = {
host: tiddler.fields["server.host"],
workspace: tiddler.fields["server.workspace"]
};
$(tiddlerElem).addClass("revisions").attr("revName", tiddler.title);
// ensure toolbar commands deactivate RevisionsView
$("a", ".toolbar", tiddlerElem).each(function(index, btn) {
var _onclick = btn.onclick;
btn.onclick = function(e) {
me.closeRevisions(tiddlerElem);
_onclick.apply(this, arguments);
};
});
// ensure default action deactivates RevisionsView
var _ondblclick = tiddlerElem.ondblclick;
tiddlerElem.ondblclick = function(e) {
me.closeRevisions(tiddlerElem);
_ondblclick.apply(this, arguments);
};
var type = tiddler.fields["server.type"];
var adaptor = new config.adaptors[type]();
var userParams = {
tiddlerElem: tiddlerElem,
pageSize: pageSize,
title: tiddler.title
};
me.createCloak(tiddlerElem);
adaptor.getTiddlerRevisionList(tiddler.title, null, context, userParams,
function(context, userParams) {
// strip the current revision
context.revisions.shift();
me.expandStack(context, userParams);
});
},
// fetch the actual revision and put it in the tiddler div
showRevision: function(place, revision, callback) {
var context = {
host: revision.fields["server.host"],
workspace: revision.fields["server.workspace"]
};
var userParams = {
revElem: place
};
var type = revision.fields["server.type"];
var adaptor = new config.adaptors[type]();
var revNo = revision.fields["server.page.revision"];
adaptor.getTiddlerRevision(revision.title, revNo, context, userParams,
function(context, userParams) {
var tiddler = context.tiddler;
tiddler.title += me.revSuffix
.format([$(place).attr("revision")]);
tiddler.fields.doNotSave = true;
if (store.getTiddler(tiddler.title)) {
store.deleteTiddler(tiddler.title);
}
store.addTiddler(tiddler);
//now, populate the existing div
var revElem = userParams.revElem;
$(revElem).attr("id", story.tiddlerId(tiddler.title));
$(revElem).attr("refresh", "tiddler");
var getTemplate = function() {
var themeName = config.options.txtTheme;
if (themeName) {
return store.getTiddlerSlice(themeName,
me.revisionTemplate) || me.revisionTemplate ||
"ViewTemplate";
} else {
return (store.getTiddler(me.revisionTemplate)) ?
me.revisionTemplate : "ViewTemplate";
}
};
var template = getTemplate();
story.refreshTiddler(tiddler.title, template, true);
callback(tiddler);
});
},
createCloak: function(promoteElem) {
var el = $(promoteElem);
// cache styles for resetting later
el.data({
top: el.css("top"),
left: el.css("left"),
zIndex: el.css("z-index")
});
$('<div class="revisionCloak" />').css("z-index", me.zIndex)
.click(function() {
me.closeRevisions(promoteElem);
})
.appendTo(document.body);
el.css("z-index", me.zIndex + 1);
},
// clean up, removing all evidence of revision view
closeRevisions: function(promoteElem) {
var el = $(promoteElem);
// revert the original tiddler back to its previous state
el.removeAttr("revName").removeClass("revisions").css({
top: el.data("top"),
left: el.data("left"),
zIndex: el.data("zIndex")
});
// remove any revisions still in the store
var revisions = $(".revisions");
revisions.each(function(index, revision) {
var revAttributes = revision.attributes;
if ((revAttributes.revname) &&
(revAttributes.revision)) {
var revName = revAttributes.revname.value;
var revNo = revAttributes.revision.value;
var title = revName + me.revSuffix.format([revNo]);
if (store.getTiddler(title)) {
store.deleteTiddler(title);
}
}
});
// delete the previous revisions
revisions.remove();
// remove the cloak
$(".revisionCloak").remove();
},
// calback from getting list of revisions
expandStack: function(context, userParams) {
var pageSize = userParams.pageSize;
var from = userParams.from || 0;
var tiddlerElem = userParams.tiddlerElem;
userParams.defaultHeight = $(tiddlerElem).height();
userParams.defaultWidth = $(tiddlerElem).width();
if (from < context.revisions.length) {
me.displayNextRevision(tiddlerElem, userParams, context, from,
from + pageSize - 1);
}
},
// place the next div above and behind the previous one
displayNextRevision: function(tiddlerElem, userParams, context, from, to) {
var revision = context.revisions[from];
var callback = function() {
var revText = revBtn.getRevisionText(tiddlerElem, revision);
tiddlerElem = me.createRevisionObject(tiddlerElem, context,
userParams, revText);
$(tiddlerElem)
.attr("revision", (context.revisions.length - from));
if ((from < to) && ((from + 1) < context.revisions.length)){
me.displayNextRevision(tiddlerElem, userParams, context,
from + 1, to);
} else if ((context.revisions.length - 1) > to) {
me.showMoreButton(tiddlerElem, context, userParams, to + 1);
}
}
me.shiftVisibleDown(userParams.title, callback);
},
createRevisionObject: function(tiddlerElem, context, userParams, text) {
var newPosition = me.calculatePosition(tiddlerElem, context);
return $('<div class="revisions tiddler" />')
.css({
position: "absolute",
top: newPosition.top,
left: newPosition.left,
"z-index": me.zIndex + 1,
height: userParams.defaultHeight,
width: userParams.defaultWidth
})
.attr("revName", userParams.title)
.append(text)
.insertBefore(tiddlerElem);
},
// move the already present revisions down by 1 to fit the next one in
shiftVisibleDown: function(title, callback) {
var revisions = $("[revName='%0'].revisions".format([title]));
var revisionCount = revisions.length;
$(revisions).animate({top: "+=" + me.offsetTop},
me.shiftDownDelay, function() {
revisionCount -= 1;
if ((callback) && (!revisionCount)) {
callback();
}
});
},
// where we put the new revision
calculatePosition: function(elem, context) {
var offset = $(elem).offset();
var currentPosition = $(elem).position();
var newPosition = {
top: currentPosition.top - me.offsetTop
};
if ((context.restrictLeft) ||
((offset.left - me.offsetLeft) <
$("#contentWrapper").offset().left)) {
newPosition.left = $(elem).position().left;
context.restrictLeft = true;
} else {
newPosition.left = currentPosition.left - me.offsetLeft;
}
return newPosition;
},
// equivalent of displayNextRevision, but for the more button
showMoreButton: function(tiddlerElem, context, userParams, moreIndex) {
userParams.from = moreIndex + 1;
me.shiftVisibleDown(userParams.title, function() {
var btn = me.createRevisionObject(tiddlerElem, context, userParams,
"");
var more = createTiddlyButton(btn[0], "more...", "show more revisions",
function() {
if ($(".viewRevision").length) {
return;
}
userParams.tiddlerElem = btn[0];
$(btn).text("")
.append(revBtn
.getRevisionText(btn[0], context.revisions[moreIndex]))
.attr("revision", context.revisions.length - moreIndex);
me.expandStack(context, userParams);
});
$(more).css("float", "right");
});
},
stripRevFromTitle: function(revisionTitle) {
return revisionTitle.split(/ ?\[rev\. #[0-9]+\]$/)[0];
},
onClickRevision: function(revElem, revision, callback) {
// don't do anything if we are still loading
if ($(".revisions").hasClass("loading")) {
return null;
}
var origTitle = me.stripRevFromTitle(revision.title);
if ($(revElem).hasClass("viewRevision")) {
$(".revisions").addClass("loading");
me.slideIn(revElem, revision, origTitle, function() {
store.deleteTiddler(revision.title);
revision.title = origTitle;
$(revElem).text("").append(revBtn.getRevisionText(revElem,
revision))
.removeAttr("tags").removeAttr("tiddler")
.removeAttr("refresh").removeAttr("template")
.removeAttr("id");
$(".revisions").removeClass("loading");
if (callback) {
callback();
}
});
$(revElem).removeAttr("prevPos").removeClass("viewRevision");
} else {
var viewRevision = function() {
var prevPos = $(revElem).position().left;
$(revElem).addClass("viewRevision").attr("prevPos", prevPos);
$(".revisions").addClass("loading");
me.showRevision(revElem, revision, function(rev) {
me.slideOut(revElem, rev, origTitle, function() {
$(".revisions").removeClass("loading");
});
});
};
// make sure another revision isn't already out
if ($(".viewRevision").length) {
var newRevElem = $(".viewRevision")[0];
var newRevision = store.getTiddler($(newRevElem)
.attr("tiddler"));
me.onClickRevision(newRevElem, newRevision, viewRevision);
} else {
viewRevision();
}
}
},
slideOut: function(revElem, revision, title, callback) {
var leftMostPos = $("[revName='%0'].revisions".format([title]))
.offset().left;
var width = $(revElem).width();
var originalLeftPos = $(story.getTiddler(title))
.position().left;
var slideAmount = leftMostPos + width - me.visibleSlideAmount;
$("[revName='%0'].revisions:not(.viewRevision)".format([title]))
.animate({left: "-=" + slideAmount}, 1000);
$(revElem)
.attr("baseHeight", $(revElem).css("height"))
.css("height", "auto")
.animate({left: originalLeftPos}, 1000, callback);
},
slideIn: function(revElem, revision, title, callback) {
var slideAmount = $(revElem).offset().left -
$(story.getTiddler(title)).offset().left;
var origRevPos = $(revElem).attr("prevPos");
$("[revName='%0'].revisions:not(.viewRevision)".format([title]))
.animate({left: "+=" + slideAmount}, 1000);
$(revElem).animate({left: origRevPos}, 1000, function() {
$(revElem)
.css("height", $(revElem).attr("baseHeight"))
.removeAttr("baseHeight");
callback();
});
}
};
var revBtn;
config.macros.slideRevision = revBtn = {
btnText: "created by %0 at %1 on %2",
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var btn = revBtn.getRevisionText(place, tiddler);
$(place).append(btn);
},
getRevisionText: function(place, revision) {
var text = revBtn.btnText.format([revision.modifier,
revision.modified.formatString("0hh:0mm"),
revision.modified.formatString("0DD MMM YYYY")]);
var btn = $('<a href="javascript:;" class="button revButton" />')
.text(text)
.click(function() {
var revElem = story.findContainingTiddler(this);
me.onClickRevision(revElem, revision);
});
return btn;
}
};
})(jQuery);
//}}}
[[StyleSheetTiddlySpace]]
<!DOCTYPE html>
<html manifest="/bags/common/tiddlers/takenote_manifest.appcache">
<head>
<title>takenote</title>
<link type="text/css" rel="stylesheet" href="/bags/common/tiddlers/notabene.css" />
<link type="text/css" rel="stylesheet" href="/bags/common/tiddlers/jquery-ui.custom.css">
<link rel="apple-touch-icon" href="/bags/common/tiddlers/touchicon_takenote.png"/>
<link rel="apple-touch-icon-precomposed" href="/bags/common/tiddlers/touchicon_takenote.png"/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=0,initial-scale=1.0">
</head>
<body>
<ul id="backstage">
<!-- no not add a newline between li elements or you will get a margin with inline blocks -->
<li><a href="/dashboard">dashboard</a></li><li><a href="/takenote">takenote</a></li>
<li><a class='connectionStatus'></a></li>
</ul>
<div class="takenotedashboard">
<div class='messageArea' style="display: none;"></div>
<div class="header">
<div class='siteicon'></div>
</div>
<div class="toolpanel viewer">
<div class='section searchSection requiresConnection'>
<h2>All Notes</h2>
<input class="findnote" type="search" placeholder="type search term" />
<ul>
<li>Access all notes in this space from <a href="/tiddlers?select=tag:!excludeLists">/tiddlers</a></li>
</ul>
</div>
<div class="section incompleteSection">
<h2>New Notes</h2>
<ul id="createNotes"></ul>
<h2>Recently Started Notes</h2>
<ul id="incomplete"></ul>
<a class='syncButton' title="save all notes in the list to the web">sync these notes</a>
</div>
<div class="section recentSection">
<h2>Recently Created Notes</h2>
<ul id="recentnotes"></ul>
</div>
</div>
<div class='footer'>
<span class='version'>v. 0.7.5</span>
</div>
</div>
<noscript>
Takenote requires javascript to work correctly. Sorry!
</noscript>
<script src="/bags/common/tiddlers/backstage.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/bookmark_bubble.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/jquery-ui.custom.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/tiddlyspace/tiddlers/chrjs" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/chrjs-store.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/jquery-json.js" type="text/javascript" charset="utf-8"></script>
<script src="/bags/common/tiddlers/notabene.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
dashboard($(".takenotedashboard")[0], {
host: "/"
});
</script>
</body>
</html>
Chris Dent's Socialtext Blog
<!doctype html>
<html>
<head>
<title>TiddlyApp Share</title>
<link rel="stylesheet" href="/bags/common/tiddlers/profile.css" type="text/css"/>
<script src="/bags/common/tiddlers/jquery.js" type="text/javascript"></script>
<script src="/bags/tiddlyspace/tiddlers/chrjs" type="text/javascript"></script>
<script src="/bags/common/tiddlers/chrjs-store.js" type="text/javascript"></script>
<script src="/bags/common/tiddlers/chrjs.users" type="text/javascript"></script>
<script type="text/javascript" src="/bags/common/tiddlers/backstage.js"></script>
<script>
// The templates of the links to display on the page
var links = [
{
title: "Tiddlers",
info: "Show a list of all the tiddlers in this space that a visitor currently has access to.",
href: "{host}/tiddlers"
}, {
title: "Public Tiddlers",
info: "Show a list of all the public tiddlers in this space in alphabetical order.",
href: "{host}/bags/{subdomain}_public/tiddlers?select=tag:!excludeLists;sort=title"
}, {
title: "Atom Feed",
info: "Get the Atom feed of the most recently changed tiddlers.",
href: "{host}/tiddlers.atom?select=tag:!excludeLists;sort=-modified;limit=20"
}, {
title: "Specific Tiddler",
info: "Show a specific tiddler in your space (GettingStarted in this example) as HTML, JSON or just plain text.",
href: ["{host}/GettingStarted", "{host}/GettingStarted.json", "{host}/GettingStarted.txt"],
more: "http://tiddlyweb.peermore.com/wiki/"
}, {
title: "Search for text",
info: "Search tiddlers for some text contained inside a tiddler.",
href: "{host}/search?q={subdomain}"
}, {
title: "List tiddlers with a tag",
info: "Display all the tiddlers that have the tag specified (here the tag is systemConfig).",
href: "{host}/tiddlers?select=tag:systemConfig"
}
];
$(function() {
var subdomain = document.location.host.split(".")[0];
// Filter curly braces to display information
for (var i in links) {
var href = links[i].href;
if (typeof href === "string")
href = [href];
for (var j in href) {
href[j] = href[j].replace("{host}", (document.protocol || "http:") + "//" + document.location.host);
href[j] = href[j].replace("{subdomain}", subdomain);
}
links[i].href = href;
}
// Display links to user
var insertInto = $('#share_links');
for (var i in links) {
var linkBody = $('<div class="share_link"></div>');
var linkDisplay = $('<div class="link_display"></div>');
for (var j in links[i].href) {
var a = $('<a class="link_href" target="_blank"/>');
a.html(links[i].href[j]);
a.attr('href', links[i].href[j]);
linkDisplay.append(a);
linkDisplay.append("<br/>");
}
var linkTitle = $('<b class="link_title"/>');
linkTitle.html(links[i].title);
var linkInfo = $('<div class="link_info"/>');
linkInfo.html(links[i].info);
linkBody.append(linkTitle);
linkBody.append('<br/>');
linkBody.append(linkInfo);
linkBody.append(linkDisplay);
if (links[i].more) {
var linkMore = $('<a class="link_more">Learn more</a>');
linkMore.attr('href', links[i].more);
linkBody.append(linkMore);
}
insertInto.append(linkBody);
}
});
</script>
<style type="text/css">
.link_display {
display: block;
background: #f2f2f2;
border: 1px solid #aaa;
padding: 10px;
margin-top: 4px;
word-wrap: break-word;
}
.link_more {
display: block;
text-align: right;
}
.share_link {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="container">
<div id="header">
<h1>Share Space</h1>
</div>
<div class="main section">
<p>Here are a number of methods that you can use to share this Space and its content in different ways.</p>
<span id="share_links"></span>
</div>
</div>
</body>
</html>
[img[http://static.flickr.com/33/47028615_5cfb5856ca_m.jpg][http://www.flickr.com/photos/cdent/47028615/]]
A few things to say after this long seeming week. Some observations:
[[Matthew]] was here for most of the week, and other than bringing some foul germ with him, unleashing fever upon the land, it was great having him around. This was a week of project swagging, so we were on the phone a lot with other folk and the pain of being on the phone was far less by being able to raise my eyebrows to one of the participants.
This may seem obvious, but people are a lot better at making wild ass guesses when they know what they were guessing about. I think I participated in four or five different project discussions. The best was centered about [[Socialtext Unplugged Project Plan]]. The group talking had had time to read, head of time, a bunch of shared artifacts I had created. We knew what we were talking about. We spent most of our time discussing how long something would take and very little about what something was. That was not always the case with some of the other projects. For some the question of "what is this supposed to do?" lumbered heavily in the air.
In a perfect world what we would do when that happens is send the project back to the pond and move on to something that matters enough to have had proper attention. But this is not a perfect world, and we all did our best to flesh things out and it all came out fine in the end.
In between the swagging, on Wednesday, Matthew and I trucked down I-5 to meet [[JJP]] for the 1st Annual International [[Centralia Wikithon]]. I feel sorry for my cow orkers who didn't get to hang with folk for their wikithons. We didn't get a ton done in Centralia (the fever was landing) but hanging out was fun, therapeutic and aligning.
Matthew and I did what we planned to do: we made a [[Turbogears]] app that proxies to the Socialtext [[REST API]] in the model. It shows pages, recent changes, a list of all pages, list of all tags, list of pages with a particular tag, editing. It's nice. What's best was how little effort was required. We built the controllers and a very simple view before bothering with fulfilling the model. Once we we knew what was needed, we imported the [[python API client|https://repo.socialtext.net:8999/svn/socialtext-clients/trunk/rest/python/socialtext/rest/]] made at the last wikithon into model.py and connected the dots. The code's here: https://repo.socialtext.net:8999/svn/socialtext-clients/trunk/rest/python/st/
The Socialtext code isn't very hygienic. No real sense of Model View Controller anywhere, and many classes are big mishmashes of stuff that sort of topically related. It's not clear what's a class method, what's an object method, on and on...@whine. So it was with great happiness when today I helped JJP review a change he had made to extract the code that handles inviting a user to a workspace from {{{UserSettingsPlugin}}} (huh?) to {{{WorkspaceInvitation}}}. The new class has two public methods, {{{new(<various parameters>)}}} and {{{send()}}}, and it does one thing: It makes an invitation which can then be sent. And it doesn't require a `Socialtext::Hub`. And now it can be used from anything which needs to send invitations. I long to extract class all over the code. Big ups to JJP.
Good hygiene in the codebase, including things like template files and configurability, is one of the keys to including other folk in the development of the code. If we're going to do inclusive things like [[democratise development]] it has to be easier for people to get in there, mess around, turn the knobs, and build new bits. I assume people are more likely to do that when the windows are all open.
Speaking of including: In grand style, [[DHH]] writes [[You're not on a fucking plane (and if you are, it doesn't matter)! |http://www.37signals.com/svn/posts/347-youre-not-on-a-fucking-plane-and-if-you-are-it-doesnt-matter]] complaining: "The idea of offline web applications is getting an undue amount of attention." (tip o the hat to [[Bill Seitz]] for the pointer, I had to stop reading svn, it just made me too frustrated with unreality I face every day)
When I first read the you don't need offline screed, a felt a bit pricked. I'm down with this [[Socialtext Unplugged]] stuff and I've said before that I think [[TiddlyWiki is Going to Change the World (again)]]. The synchronization capability in recent TiddlyWikis, on one facet, is all about allowing a [[slick-haired asshole in a monkey suit|http://notrocketsurgery.com/articles/2007/04/02/the-mile-high-club-37signals-fuck-yeahs-and-productivity-stock-art]] to satisfy his or her sense self-importance: "This work I'm doing, it's //really// important, by golly."
But that's just one facet. On another face, the synchronization features in TiddlyWiki -- which can talk to multiple servers and multiple //types// of servers -- are all about inclusiveness. About mashing things up. About merging your wiki content from over there with your other wiki content from over //there//. Placing things in new contexts. To learn from them.
With a few more months of work, TiddlyWiki could [[ReContextualize]] all sorts of stuff, not just wiki pages.
And you could do it with some snakes on a plane if that made you feel good.
/***
|''Name''|TiddlySpaceToolbar|
|''Description''|augments tiddler toolbar commands with SVG icons|
|''Author''|Osmosoft|
|''Version''|0.6.6|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceToolbar.js|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.5.0|
|''Requires''|ImageMacroPlugin|
|''Keywords''|toolbar icons SVG|
!Description
replaces tiddler toolbar commands with SVG icons if available
!Notes
requires [[ImageMacroPlugin|http://svn.tiddlywiki.org/Trunk/contributors/JonRobson/plugins/ImageMacroPlugin/plugins/ImageMacroPlugin.tid]]
SVG icons are drawn from tiddlers titled {{{<command>.svg}}}
In readonly mode a tiddler called {{{<command>ReadOnly.svg}}} will be used if it exists.
!TODO
* rename (IconToolbarPlugin?)
* support more than one more popup menu in the toolbar.
!Code
***/
//{{{
(function($) {
if(!config.macros.image) {
throw "Missing dependency: ImageMacroPlugin";
}
var macro = config.macros.toolbar;
macro.icons = {
cloneTiddler: "editTiddler"
};
var _handler = macro.handler;
macro.handler = function(place, macroName, params, wikifier,
paramString, tiddler) {
var toolbar = $(place);
toolbar.attr({
refresh: "macro",
macroName: macroName
}).data("args", arguments);
var status = _handler.apply(this, arguments);
if(tiddler.isReadOnly()) {
toolbar.addClass("toolbarReadOnly");
} else {
toolbar.removeClass("toolbarReadOnly");
}
var parsedParams = paramString.parseParams("name")[0];
if(parsedParams.icons && parsedParams.icons == "yes") {
this.augmentCommandButtons(place);
}
if(parsedParams.more && parsedParams.more == "popup") {
// note we must override the onclick event like in createTiddlyButton
// otherwise the click event is the popup AND the slider
$(".moreCommand", place).each(function(i, el) {
el.onclick = macro.onClickMorePopUp;
});
// buttons that are after a less command should not be in more menu.
$(".lessCommand ~ .button", place).appendTo(place);
$(".lessCommand", place).remove();
}
return status;
};
macro.refresh = function(place, params) {
var args = $(place).empty().data("args");
this.handler.apply(this, args);
};
var imageMacro = config.macros.image;
macro.augmentCommandButtons = function(toolbar) {
$(".button", toolbar).each(function(i, el) {
var cmd = $(el).attr("commandname");
cmd = cmd ? cmd : "moreCommand"; // XXX: special-casing of moreCommand due to ticket #1234
var icon = store.tiddlerExists(cmd) ? cmd : macro.icons[cmd];
var text = $(el).text();
if(readOnly) {
var readOnlyAlternative = "%0ReadOnly".format([icon]);
if(store.tiddlerExists(readOnlyAlternative)) {
icon = readOnlyAlternative;
}
}
if(store.tiddlerExists(icon)) {
$(el).css({display: "inline-block"}).empty();
imageMacro.renderImage(el, icon, { alt: text });
}
});
};
// provide onClickMore to provide extra commands in a popup
macro.onClickMorePopUp = function(ev) {
ev = ev || window.event;
var sibling = this.nextSibling;
if(sibling) {
var commands = sibling.childNodes;
var popup = Popup.create(this);
$(popup).addClass("taggedTiddlerList");
for(var i = 0; i < commands.length; i++) {
var li = createTiddlyElement(popup, "li", null);
var oldCommand = commands[i];
var command = oldCommand.cloneNode(true);
command.onclick = oldCommand.onclick;
li.appendChild(command);
}
Popup.show();
}
ev.cancelBubble = true;
if(ev.stopPropagation) {
ev.stopPropagation();
}
return false;
};
})(jQuery);
//}}}
/***
|''Name''|TiddlySpaceSearcher|
|''Version''|0.2.5|
|''Requires''|TiddlySpaceConfig TiddlySpaceFollowingPlugin|
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var tsScan = config.macros.tsScan;
config.shadowTiddlers.SearchTemplate = "<<view server.bag SiteIcon label:no width:24 height:24 preserveAspectRatio:yes>> <<view server.bag spaceLink title external:no>> in space <<view server.bag spaceLink>>";
config.shadowTiddlers.StyleSheetSearch = [".resultsArea .siteIcon { display: inline; }",
".searchForm {text-align: left;}"].join("\n");
store.addNotification("StyleSheetSearch", refreshStyles);
var search = config.macros.tsSearch = {
locale: {
advanced: "Advanced Options",
header: "Search",
resultsHeader: "Results (%0)",
find: "find",
noResults: "No tiddlers matched your search query",
query: "QUERY: ",
error: "please provide a search query or a tag, modifier or title!",
titleAdvanced: "where the title is",
modifierAdvanced: "where the last modifier is",
spaceAdvanced: "only in the space: ",
notspaceAdvanced: "but not in the spaces: ",
tagsAdvanced: "with the tags: "
},
andConstructor: function(container, label, fieldname, negationMode) {
var tags = $("<div />").appendTo(container);
$('<span />').text(label).appendTo(tags);
var id = "area" + Math.random();
container = $("<span />").attr("id", id).appendTo(tags)[0];
function add(container) {
var el = $('<input type="text" />').attr("field", fieldname).appendTo(container);
if(negationMode) {
el.attr("negation", "true");
}
}
add(container);
var el = $("<button />").text("AND").click(function(ev) {
add($(ev.target).data("container"));
ev.preventDefault();
}).appendTo(tags);
$(el).data("container", container);
},
fieldConstructor: function(container, label, field) {
container = $("<div />").appendTo(container)[0];
$("<span />").text(label).appendTo(container);
$("<input />").attr("text", "input").attr("field", field).appendTo(container);
},
advancedOptions: function(form) {
var locale = search.locale;
var container = $("<div />").addClass("tsAdvancedOptions").appendTo(form)[0];
$("<h2/ >").text(search.locale.advanced).appendTo(container);
$("<div />").addClass("separator").appendTo(container);
search.fieldConstructor(container, locale.titleAdvanced, "title");
search.fieldConstructor(container, locale.modifierAdvanced, "modifier");
search.fieldConstructor(container, locale.spaceAdvanced, "space");
search.andConstructor(container, locale.notspaceAdvanced, "space", true);
search.andConstructor(container, locale.tagsAdvanced, "tag");
},
constructSearchQuery: function(form) {
var data = [], select = [];
var query = $("[name=q]", form).val();
if(query) {
data.push("q=%0".format(query));
}
// add tags, fields etc..
$("[field]", form).each(function(i, el) {
var val = $(el).val();
var name = $(el).attr("field");
var negate = $(el).attr("negation") == "true";
if(val && name) {
val = encodeURIComponent(val);
val = negate ? "!" + val : val;
if(name == "space") {
val += "_public";
name = "bag";
}
if(negate) {
select.push("select=%0:%1".format(name,val));
} else {
var prefix = data.length === 0 ? "q=" : "";
data.push('%0%1:"%2"'.format(prefix, name, val));
}
}
});
var dataString = data.join(" ");
if(dataString.length === 0 && !query) {
return false;
}
var selectStatement = select.join("&");
if(dataString.length > 0 && selectStatement.length > 0) {
dataString += "&";
}
dataString += selectStatement;
return "/search?%0".format(dataString);
},
constructForm: function(place) {
var locale = search.locale;
$("<h1 />").text(locale.header).appendTo(place);
var form = $("<form />").appendTo(place)[0];
$('<input type="text" name="q" />').appendTo(form);
$('<input type="submit" />').val(locale.find).appendTo(form);
search.advancedOptions(form);
var query = $('<h2 class="query"/>').appendTo(place)[0];
var results = $("<div />").appendTo(place).addClass("resultsArea")[0];
var lookup = function(url) {
if(!url) {
results.empty().addClass("error").text(locale.error);
return;
}
config.extensions.tiddlyweb.getStatus(function(status) {
$(query).text(locale.query);
var href = status.server_host.url + url;
$("<a />").attr("href", href).text(href).appendTo(query);
tsScan.scan(results, { url: url, emptyMessage: search.locale.noResults, cache: true,
template: "SearchTemplate", sort: "title", callback: function(tiddlers) {
$("<h2 />").text(locale.resultsHeader.format(tiddlers.length)).prependTo(results);
}
});
});
};
$(form).submit(function(ev) {
ev.preventDefault();
var url = search.constructSearchQuery(form);
config.macros.tsSearch.lastSearch = url;
lookup(url);
});
if(search.lastSearch) {
lookup(search.lastSearch);
}
return form;
},
handler: function(place) {
var container = $("<div />").addClass("searchForm").appendTo(place)[0];
search.constructForm(container);
}
}
})(jQuery);
//}}}
cdent-meaning_public
glossary_public
<!--{{{-->
<div class='toolbar'
macro='toolbar [[ToolbarCommands::EditToolbar]] icons:yes'>
</div>
<div class='heading editorHeading'>
<div class='editor title' macro='edit title'></div>
<div class='tagClear'></div>
</div>
<div class='annotationsBox' macro='annotations'>
<div class='editSpaceSiteIcon'
macro='tiddlerOrigin height:16 width:16 label:no interactive:no'>
</div>
<div class="privacyEdit" macro='setPrivacy label:no interactive:no'></div>
<div class='tagClear'></div>
</div>
<div class='editor' macro='edit text'></div>
<div class='editorFooter'>
<div class='tagTitle'>tags</div>
<div class='editor' macro='edit tags'></div>
<div class='tagAnnotation'>
<span macro='message views.editor.tagPrompt'></span>
<span macro='tagChooser excludeLists'></span>
</div>
<div macro="editFields"></div>
</div>
<!--}}}-->
/***
|''Name''|TiddlySpaceFilters|
|''Description''|provide TiddlySpace-specific filter extensions|
|''Author''|Jon Robson|
|''Version''|0.6.1|
|''Status''|@@beta@@|
|''CoreVersion''|2.6.2|
|''Requires''|TiddlySpaceConfig|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
!Usage
{{{
<<tsList Private>>
<<tsList Public>>
<<tsList Draft>>
}}}
!Code
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var privateBag = tiddlyspace.getCurrentBag("private");
var publicBag = tiddlyspace.getCurrentBag("public");
config.filterHelpers = {
is: {
"private": function(tiddler) {
var bag = tiddler.fields["server.bag"];
return bag == privateBag;
},
"public": function(tiddler) {
var bag = tiddler.fields["server.bag"];
return bag == publicBag;
},
draft: function(tiddler) {
var fields = tiddler.fields;
var bag = fields["server.bag"];
return (privateBag == bag && fields["publish.name"]) ? true : false;
},
local: function(tiddler) {
return config.filterHelpers.is["public"](tiddler) ||
config.filterHelpers.is["private"](tiddler);
},
unsynced: function(tiddler) {
return tiddler ? tiddler.isTouched() : false;
}
}
};
config.filters.is = function(results, match) {
var candidates = store.getTiddlers("title");
var type = match[3];
for (var i = 0; i < candidates.length; i++) {
var tiddler = candidates[i];
var helper = config.filterHelpers.is[type];
if(helper && helper(tiddler)) {
results.pushUnique(tiddler);
}
}
return results;
};
})(jQuery);
//}}}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="450 366 38 57"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 452.1094 421.2422 L 450 421.2422 L 450 423 L 487.9688 423 L 487.9688 421.2422 L 485.8595 421.2422
L 485.8595 377.29688 L 487.9688 377.29688 L 487.9688 375.53906 L 485.8595 375.53906
C 485.8595 375.53906 481.12463 371.59341 473.02023 370.52802 C 472.6824 368.9689 471.72098 366.75 468.9844 366.75
C 466.24783 366.75 465.28638 368.9689 464.94864 370.52802
C 456.84418 371.59341 452.1094 375.53906 452.1094 375.53906 L 450 375.53906 L 450 377.29688 L 452.1094 377.29688
Z M 467.12247 370.32086 L 467.12247 370.32086 C 467.3805 369.42395 467.90762 368.50781 468.9844 368.50781
C 470.0612 368.50781 470.5883 369.42395 470.84634 370.32086
C 470.24136 370.2848 469.62054 370.26562 468.9844 370.26562
C 468.34827 370.26562 467.72748 370.2848 467.12247 370.32086 Z M 454.21875 420.92804 L 454.21875 420.92804
C 455.46762 420.42087 456.32816 419.35281 456.32816 418.11716 L 456.32816 377.29688 L 458.4375 377.29688
L 458.4375 421.2422 L 454.21875 421.2422 Z M 460.5469 420.92804 L 460.5469 420.92804
C 461.79578 420.42087 462.65625 419.35281 462.65625 418.11716 L 462.65625 377.29688 L 464.76566 377.29688
L 464.76566 421.2422 L 460.5469 421.2422 Z M 466.87503 420.92804 L 466.87503 420.92804
C 468.1239 420.42087 468.9844 419.35281 468.9844 418.11716 L 468.9844 377.29688 L 471.09378 377.29688
L 471.09378 421.2422 L 466.87503 421.2422 Z M 473.2032 420.92804 L 473.2032 420.92804
C 474.45203 420.42087 475.31256 419.35281 475.31256 418.11716 L 475.31256 377.29688 L 477.4219 377.29688
L 477.4219 421.2422 L 473.2032 421.2422 Z M 479.5313 420.92804 L 479.5313 420.92804
C 480.78018 420.42087 481.64066 419.35281 481.64066 418.11716 L 481.64066 377.29688 L 483.75006 377.29688
L 483.75006 421.2422 L 479.5313 421.2422 Z" fill="black" class="glyph"/>
</g>
</g>
</svg>
/***
|''Name''|RandomColorPalettePlugin|
|''Description''|Adds a random color palette to TiddlyWiki|
|''Author''|Jon Robson|
|''Version''|1.4.0|
|''Status''|stable|
|''Source''|https://github.com/jdlrobson/TiddlyWikiPlugins/raw/master/plugins/RandomColorPalettePlugin/RandomColorPalettePlugin.js|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
!Usage
{{{
<<RandomColorPalette>>
}}}
Sets and saves a random color palette on execution
{{{
<<RandomColorPaletteButton>>
}}}
Creates a button, which when clicked will change the color palette
More information at http://macros.tiddlyspace.com/#%5B%5BRandomColorPaletteButton%20macro%5D%5D
!Code
***/
//{{{
RGB.prototype.toRGBString = function() {
return "rgb(%0,%1,%2)".format(parseInt(this.r * 255, 10),
parseInt(this.g * 255, 10), parseInt(this.b * 255, 10))
}
function HSL_TO_RGB(h, s, l) { // h (hue) between 0 and 360, s (saturation) & l (lightness) between 0 and 1
var c = l <= 0.5 ? 2 * l * s : ( 2 - (2 * l)) * s;
var h1 = h / 60;
var x = c * (1 - Math.abs((h1 % 2) - 1));
var r, g, b;
if(typeof(h) == 'undefined') {
r = 0;
g = 0;
b = 0;
} else if(0 <= h1 && h1 < 1) {
r = c;
g = x;
b = 0;
} else if(1 <= h1 && h1 < 2) {
r = x;
g = c;
b = 0;
} else if(2 <= h1 && h1 < 3) {
r = 0;
g = c;
b = x;
} else if(3 <= h1 && h1 < 4) {
r = 0;
g = x;
b = c;
} else if(4 <= h1 && h1 < 5) {
r = x;
g = 0;
b = c;
} else if(5 <= h1 && h1 < 6) {
r = c;
g = 0;
b = x;
}
m = l - (0.5 * c);
return new RGB(r + m, g + m, b + m);
}
(function($){
var macro = config.macros.RandomColorPalette = {
messagesOn: false,
changedPaletteText: "We have assigned you a random theme by adjusting the [[ColorPalette]] tiddler.\nDon't like it? Click <<RandomColorPalette>> for another one.",
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
paramString = paramString || "";
var options = macro.getOptions(paramString);
macro.generatePalette(options, true);
},
optionTypes: {
floats: ["hue", "saturation", "darkest", "lightness", "huevariance", "dark", "pale", "light", "mid",
"saturation_light", "saturation_pale", "saturation_mid", "saturation_dark"
]
},
getOptions: function(paramString) {
var args = paramString.parseParams("name", null, true, false, true)[0];
var options = {};
var numbers = macro.optionTypes.floats;
for(var i in args) {
options[i] = numbers.indexOf(i) > -1 ? parseFloat(args[i][0], 10) : args[i][0];
}
return options;
},
generateRandomNumber: function(min, max, info) {
var num = (Math.random() * 1);
info = !info ? { attempts:0 } : info;
info.attempts += 1;
var good = true;
if(min == max) {
return max;
}
if(min && num < min) {
good = false;
} else if(max && num > max) {
good = false;
}
if(!good) {
if(info.attempts < 5) {
return macro.generateRandomNumber(min, max, info);
} else {
if(max) {
return max;
} else if(min) {
return min;
} else {
return 1;
}
}
}
return num;
},
getExistingPalette: function(asJSON) {
var title = "ColorPalette";
var tiddlerText;
if(store.tiddlerExists(title)) {
tiddlerText = store.getTiddlerText(title);
} else if(store.isShadowTiddler(title)){
tiddlerText = config.shadowTiddlers[title];
}
if(asJSON) {
var json = {};
if(tiddlerText) {
var lines = tiddlerText.split("\n");
for(var i = 0; i < lines.length; i++) {
var definition = lines[i].split(":");
if(definition.length == 2) {
var name = definition[0].trim();
var value = definition[1].trim();
json[name] = value;
}
}
}
return json;
} else {
return tiddlerText;
}
},
generatePalette: function(options, save) {
var outputRGB = options.rgb;
var palette = macro.getExistingPalette(true);
var hue = options.hue || Math.floor(Math.random() * 359);
var saturation = options.saturation || macro.generateRandomNumber(0.3, 0.7);
var dark = options.dark || options.darkest || macro.generateRandomNumber(0, 0.10);
var pale = options.pale || options.lightness || macro.generateRandomNumber(0.90, 1);
var delta = ( ( pale - dark ) / 3 );
var mid = options.mid || dark + delta;
var light = options.light || dark + (delta * 2);
var lightness_values = {Dark: dark, Mid: mid, Light: light, Pale: pale};
var saturation_values = {};
for(i in lightness_values) {
if(true) {
saturation_values[i] = options["saturation_" + i.toLowerCase()] || saturation;
}
}
var opposite_hue = (hue + 180) % 360;
var seed = options.huevariance || Math.floor((85 * Math.random()) + 5); // we want it to be at least 5 degrees
var huetwo = (opposite_hue + seed) % 360;
var huethree = (opposite_hue - seed) % 360;
if(huetwo < 0) {
huetwo = 360 + huetwo;
}
if(huethree < 0) {
huethree = 360 + huethree;
}
for(var j in lightness_values) {
if(true) {
var saturation = saturation_values[j];
palette["Primary" + j] = HSL_TO_RGB(hue, saturation, lightness_values[j]);
palette["Secondary" + j] = HSL_TO_RGB(huetwo, saturation, lightness_values[j]);
palette["Tertiary" + j] = HSL_TO_RGB(huethree, saturation, lightness_values[j]);
}
}
palette.Background = HSL_TO_RGB(hue, saturation, 0.92);
palette.Foreground = HSL_TO_RGB(hue, saturation, 0.08);
palette.ColorPaletteParameters = ["HSL([", hue, "|", seed, "], [", saturation_values.Pale, "|",
saturation_values.Light, "|", saturation_values.Mid, "|", saturation_values.Dark, "],",
"[", dark, "|", mid, "|", light, "|", pale, "])"].join("");
// construct new ColorPalette
var text = ["/*{{{*/\n"];
var colorcode;
for(var id in palette) {
if(true) {
var color = palette[id];
colorcode = outputRGB ? color.toRGBString() : color.toString();
text.push("%0: %1\n".format(id, colorcode));
}
}
text.push("/*}}}*/");
text = text.join("");
if(save) {
macro.saveColorPalette(text);
}
return text;
},
saveColorPalette: function(text) {
var tid = store.getTiddler("ColorPalette");
if(!tid) {
tid = new Tiddler("ColorPalette");
tid.fields = merge({}, config.defaultCustomFields);
} // TODO: detect that the ColorPalette in the space comes from outside recipe
tid.fields["server.page.revision"] = "false"; // edit conflicts dont matter
// save the color palette in tid
tid = store.saveTiddler(tid.title, tid.title, text, tid.modifier, tid.modified,
tid.tags, tid.fields, false, tid.created, tid.creator);
// an interval is used to cope with users clicking on the palette button quickly.
if(macro._nextSave) {
window.clearTimeout(macro._nextSave);
}
macro._nextSave = window.setTimeout(function() {
autoSaveChanges(null, [tid]);
}, 2000);
// temporary workaround for IE.
$.twStylesheet.remove({ id: "StyleSheetColors" });
$.twStylesheet.remove({ id: "StyleSheet" });
refreshAll();
macro.reportChange();
return tid;
},
reportChange: function() {
if(macro.messagesOn) { // only display message once..
var msgPlace = getMessageDiv();
if(!$(".changedPalette", msgPlace)[0]) {
var tempPlace = document.createElement("div");
wikify("{{changedPalette{" + macro.changedPaletteText + "}}}", tempPlace);
msgPlace.appendChild(tempPlace);
}
}
}
};
var btnMacro = config.macros.RandomColorPaletteButton = {
text: "New ColorPalette",
tooltip: "Generate a random colour scheme for your TiddlyWiki",
makeButton: function(place, options) {
var btnHandler = function(ev) {
var t = $(ev.target);
var options = t.data("options");
macro.generatePalette(options, true);
ev.preventDefault();
return false;
};
var btn = createTiddlyButton(place, this.text, this.tooltip, btnHandler);
$(btn).data("options", options);
return btn;
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var options = macro.getOptions(paramString);
btnMacro.makeButton(place, options);
}
};
})(jQuery);
//}}}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="78 222 60 60"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 107.92718 244.14815 L 86.651474 222.89253 L 78.85206 230.69925 L 100.120415 251.9476 L 78.774 273.27396
L 86.57342 281.08075 L 107.927216 259.74707 L 129.39981 281.19946 L 137.19922 273.39267 L 115.73397 251.94763
L 137.121155 230.58054 L 129.32175 222.77374 Z" fill="black" class="glyph"/>
</g>
</g>
</svg>
/***
|''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);
//}}}
/***
|''Name''|ToggleTiddlerPrivacyPlugin|
|''Version''|0.7.0|
|''Status''|@@beta@@|
|''Description''|Allows you to set the privacy of new tiddlers and external tiddlers within an EditTemplate, and allows you to set a default privacy setting|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlySpaceConfig|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/ToggleTiddlerPrivacyPlugin.js|
!Notes
When used in conjunction with TiddlySpaceTiddlerIconsPlugin changing the privacy setting will also interact with any privacy icons.
Currently use of
{{{<<setPrivacy defaultValue:public>>}}} is in conflict with {{{<<newTiddler fields:"server.workspace:x_private">>}}}
There is an option, found in the tweak tab of the backstage, called txtPrivacyMode. Set this to either ''public'' or ''private'' depending on your security preference. If you choose not to set it then it will default to ''public''.
!Params
defaultValue:[private|public]
Allows you to set the default privacy value (Default is private)
!Code
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var macro = config.macros.setPrivacy = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(readOnly) {
return;
}
var el = $(story.findContainingTiddler(place));
var args = paramString.parseParams("name", null, true, false, true)[0];
var container = $("<div />").addClass("privacySettings").appendTo(place)[0];
var currentSpace = tiddlyspace.currentSpace.name;
var currentBag = tiddler ? tiddler.fields["server.bag"] : false;
var isNewTiddler = el.hasClass("missing") || !currentBag; // XXX: is this reliable?
var status = tiddlyspace.getTiddlerStatusType(tiddler);
var customFields = el.attr("tiddlyfields");
customFields = customFields ? customFields.decodeHashMap() : {};
if(isNewTiddler || !["public", "private", "unsyncedPrivate", "unsyncedPublic"].contains(status)) {
var defaultValue = "public";
if(args.defaultValue) {
defaultValue = args.defaultValue[0].toLowerCase();
} else {
defaultValue = config.options.chkPrivateMode ? "private" : "public";
}
defaultValue = defaultValue ?
"%0_%1".format(currentSpace, defaultValue) : customFields["server.bag"];
var options = config.macros.tiddlerOrigin ?
config.macros.tiddlerOrigin.getOptions(paramString) : {};
this.createRoundel(container, tiddler, currentSpace, defaultValue, options);
}
},
updateEditFields: function(tiddlerEl, bag) {
var saveBagField = $('[edit="server.bag"]', tiddlerEl);
var saveWorkspaceField = $('[edit="server.workspace"]', tiddlerEl);
var input = $("<input />").attr("type", "hidden");
if(saveBagField.length === 0) {
input.clone().attr("edit", "server.bag").val(bag).appendTo(tiddlerEl);
} else {
saveBagField.val(bag);
}
$(tiddlerEl).attr("tiddlyFields", ""); // reset to prevent side effects
var workspace = "bags/" + bag;
if(saveWorkspaceField.length === 0) {
input.clone().attr("edit", "server.workspace").val(workspace).appendTo(tiddlerEl);
} else {
saveWorkspaceField.val(workspace);
}
},
setBag: function(tiddlerEl, newBag, options) {
var title = $(tiddlerEl).attr("tiddler");
var tiddler = store.getTiddler(title);
var originButton = $(".originButton", tiddlerEl)[0];
var refreshIcon = function(type) {
var originMacro = config.macros.tiddlerOrigin;
if(originButton && originMacro) {
options.noclick = true;
originMacro.showPrivacyRoundel(tiddler, type, originButton, options);
}
};
macro.updateEditFields(tiddlerEl, newBag);
var newWorkspace = "bags/" + newBag;
if(tiddler) {
tiddler.fields["server.bag"] = newBag;
tiddler.fields["server.workspace"] = newWorkspace; // for external tiddlers
}
var rPrivate = $("input[type=radio].isPrivate", tiddlerEl);
var rPublic = $("input[type=radio].isPublic", tiddlerEl);
if(newBag.indexOf("_public") > -1) {
rPrivate.attr("checked", false);
rPublic.attr("checked", true);
status = "public";
} else {
rPublic.attr("checked", false); // explicitly do this for ie
rPrivate.attr("checked", true);
status = "private";
}
refreshIcon(status);
},
createRoundel: function(container, tiddler, currentSpace, defaultValue, options) {
var privateBag = "%0_private".format(currentSpace);
var publicBag = "%0_public".format(currentSpace);
var rbtn = $("<input />").attr("type", "radio").attr("name", tiddler.title);
var rPrivate = rbtn.clone().val("private").addClass("isPrivate").appendTo(container);
$("<label />").text("private").appendTo(container); // TODO: i18n
var rPublic = rbtn.clone().val("public").addClass("isPublic").appendTo(container);
$("<label />").text("public").appendTo(container); // TODO: i18n
var el = story.findContainingTiddler(container);
$("[type=radio]", container).click(function(ev) {
var btn = $(ev.target);
tiddler.fields["server.page.revision"] = "false";
if(btn.hasClass("isPrivate")) { // private button clicked.
$(el).addClass("isPrivate").removeClass("isPublic");
macro.setBag(el, privateBag, options);
} else {
$(el).addClass("isPublic").removeClass("isPrivate");
macro.setBag(el, publicBag, options);
}
});
window.setTimeout(function() {
macro.setBag(el, defaultValue, options);
}, 100);
// annoyingly this is needed as customFields are added to end of EditTemplate so are not present yet
// and don't seem to respect any existing customFields.
}
};
})(jQuery);
//}}}
!SpaceUnplugged
{{unpluggedSpaceTab{
{{wizard{
<<image unsyncedIcon width:48>> <<message messages.syncExplanation>>
}}}
{{wizard syncKey{
{{keyHeading{
Key}}}
{{changedServer keyItem{<<message macros.sync.syncStatusList.changedServer.text>>}}} {{changedBoth keyItem{<<message macros.sync.syncStatusList.changedBoth.text>>}}} {{changedLocally keyItem{<<message macros.sync.syncStatusList.changedLocally.text>>}}} {{notFound keyItem{<<message macros.sync.syncStatusList.notFound.text>>}}}
}}}
<<sync>>
!Menu
<<message messages.memberStatus>> <<homeLink>>
{{unsyncedList{<<message messages.syncListHeading>> <<list filter [is[unsynced]]>>}}}
<<tabs txtTiddlySpaceTab
search "search across TiddlySpace" Backstage##Search
tiddlers "tiddlers control panel" Backstage##BackstageTiddlers
plugins "Manage installed plugins" PluginManager
batch "Perform batch operations on public/private tiddlers" Backstage##BatchOps
tweaks "Tweak the appearance and behaviour of TiddlyWiki" AdvancedOptions
"import/export" "Import/export tiddlers from/to a TiddlyWiki" Backstage##ImportExport
>>
running TiddlySpace@glossary version <<message extensions.tiddlyweb.status.tiddlyspace_version>>
{{autotable{
<<tiddler Backstage##Resources>>
}}}
!Resources
|[[blog|@@blog]]|[[documentation|@@docs]]|[[featured spaces|@@featured]]|
!Search
<<tsSearch>>
!ImportExport
<<fileImport>>
You can download this TiddlySpace as an offline TiddlyWiki:
{{chunkyButton{<<exportSpace>>}}}
!BackstageTiddlers
|upload a <<message messages.privacySetting>> file: <<binaryUpload>>|<<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>>|
|>|<<search>>|
|>|<<tiddler Backstage##Tiddlers>>|
!Tiddlers
<<tabs
txtMainTab
"Recent" "Recently edited tiddlers" TabTimeline
"All" "All tiddlers" TabAll
"Public" "All public tiddlers" [[TiddlySpaceTabs##Public]]
"Private" "All private tiddlers" [[TiddlySpaceTabs##Private]]
"Tags" "All tags" TabTags
"Spaces" "Tiddlers grouped by space" [[TiddlySpaceTabs##Spaces]]
"Missing" "Missing tiddlers" TabMoreMissing
"Orphans" "Orphaned tiddlers" TabMoreOrphans
"Shadows" "Shadowed tiddlers" TabMoreShadowed
>>
!BatchOps
<<tabs
txtPublisherTab
"Private" "Move tiddlers from private to public" Backstage##BatchPrivate
"Public" "Move tiddlers from public to private" Backstage##BatchPublic
>>
!BatchPrivate
<<TiddlySpacePublisher type:private>>
!BatchPublic
<<TiddlySpacePublisher type:public>>
/***
|''Name''|TiddlySpaceRevertRevision|
|''Description''|Revert to a previous revision|
|''Author''|BenGillies|
|''Version''|0.1|
|''Status''|unstable|
|''Source''|http://github.com/TiddlySpace/tiddlyspace|
|''CodeRepository''|http://github.com/TiddlySpace/tiddlyspace|
|''License''|[[BSD|http://www.opensource.org/licenses/bsd-license.php]]|
|''CoreVersion''|2.6.0|
|''Requires''|TiddlyWebAdaptor TiddlySpaceRevisionView|
!Usage
Add a control button to revert to a particular revision.
The button must be called from within a revision, as generated by TiddlySpaceRevisionView
!Code
***/
//{{{
(function($) {
config.commands.revert = {
text: "revert",
tooltip: "make this revision the current one",
handler: function(ev, src, title) {
var revElem = story.getTiddler(title);
var tidToRevert = store.getTiddler($(revElem).attr("revName"));
var revision = store.getTiddler(title);
if ((revision) && (tidToRevert)) {
tidToRevert.text = revision.text;
var newFields = merge({}, revision.fields);
for (var fieldName in newFields) {
if (fieldName.substr(0, 7) === "server.") {
delete newFields[fieldName];
}
}
merge(tidToRevert.fields, newFields);
tidToRevert.tags = merge([], revision.tags);
tidToRevert.fields.changecount = 1;
delete tidToRevert.fields.doNotSave;
store.saveTiddler(tidToRevert.title, tidToRevert.title,
tidToRevert.text, null, null, tidToRevert.tags,
tidToRevert.fields, false, tidToRevert.created, tidToRevert.creator);
autoSaveChanges(true);
}
}
};
})(jQuery);
//}}}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="2 724 68 55"
width="30" height="30">
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<path d="M 2.25 756 L 11.25 747 L 24.75 760.4994 L 60.750004 724.4994 L 69.75 733.49902
L 24.749977 778.49976 Z" fill="#101010" class="glyph"/>
</g>
</g>
</svg>
Once you have some content then you may choose to determine a tiddler, or set of tiddlers to display each time you load ~TiddlySpace. This is determined by the [[DefaultTiddlers]].
<html><hr><html>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>This Space</title>
<script type='text/javascript' src='/bags/common/tiddlers/backstage.js'></script>
<link href="/bags/common/tiddlers/profile.css" type='text/css' rel='stylesheet' >
<link href="/bags/common/tiddlers/admin.css" type='text/css' rel='stylesheet' >
</head>
<body>
<div id="container">
<div id="text-html" class="main section">
<a class="app" href="/apps">back to apps</a>
<div class="left">
<h2>About this space <button class='toggleNext'>show</button></button></h2>
<div id="siteinfo"></div>
<h2>Site Icon</h2>
<div>
<img id="siteicon" class="siteicon">
<form id="upload" method="POST" enctype="multipart/form-data">
<input type="hidden" name="title" value="SiteIcon" />
<input type="hidden" name="tags" value="excludeLists">
<input type="hidden" name="csrf_token" class="csrf" />
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</div>
<h2>Vital Statistics</h2>
<div id="info">please wait while information is loaded about this space...</div>
</div>
<div class="right">
<div class="ts-membership">
<h2>
Add Member
<a href="http://docs.tiddlyspace.com/What%20is%20a%20member%3F" title="What is a Member?" class="help">What is a Member?</a>
</h2>
<div>
<p>Add a new member to your space by entering their name below. Enter a space to add all existing members of that space.</p>
<form class="ts-members">
<input class="inputBox" type="text" name="username">
<input type="submit" value="Add Member" />
</form>
</div>
<h2>
Existing Members <button class='toggleNext'><</button></button>
</h2>
<div>
Your space currently has the following members:
<ul class="ts-members"></ul>
</div>
<h2>
Include Space
<a class="help" href="http://docs.tiddlyspace.com/What%20is%20space%20inclusion%3F" title="What is inclusion?">What is Inclusion?</a>
</h2>
<form class="ts-includes">
<input class="inputBox" type="text" name="spacename">
<input type="submit" value="Include Space" />
</form>
</div>
<div>
<h2>Included Spaces <button class='toggleNext'><</button></button></h2>
<div>
This space includes the following spaces:
<ul class="ts-includes"></ul>
</div>
</div>
</div>
<div class="clear"></div>
</div>
</div>
<script type='text/javascript' src='/bags/common/tiddlers/jquery.js'></script>
<script type='text/javascript' src='/bags/tiddlyspace/tiddlers/chrjs'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.space'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.users'></script>
<script type='text/javascript' src='/bags/common/tiddlers/chrjs.identities'></script>
<script type='text/javascript' src='/bags/tiddlyspace/tiddlers/TiddlySpaceCSRF'></script>
<script type='text/javascript' src='/bags/common/tiddlers/jquery-form.js'></script>
<script type='text/javascript' src="/bags/common/tiddlers/siteiconupload.js"></script>
<script type='text/javascript' src="/bags/common/tiddlers/ts.js"></script>
<script type="text/javascript">
ts.init(function(ts) {
if(!ts.currentSpace) {
return;
}
var address = window.location.hostname.split(".");
var spaceName = address[0];
var publicBag = spaceName + "_public";
$("#siteUrl").text(window.location.hostname);
initSiteIconUpload(spaceName);
function countTiddlers(members) {
var numMembers = members ? members.length : false;
var publicBagUrl = "/bags/" + publicBag + "/tiddlers";
var url = members ? "/bags/" + spaceName + "_private/tiddlers" :
publicBagUrl;
$.ajax({ url: url, dataType: "text",
success: function(tiddlers) {
var numTiddlers = $.trim(tiddlers).split("\n").length;
var html = "";
function printFullInfo(numPublicTiddlers) {
var totalTiddlers = numPublicTiddlers + numTiddlers;
html += ['This space has ', numMembers,
' members, <a href="/tiddlers">', totalTiddlers,
' local tiddlers</a>, <a href="' + url + '">',
numTiddlers, ' are private</a> and <a href="',
publicBagUrl, '">',
numPublicTiddlers, ' public</a>.'].join("");
$("#info").html(html);
}
if(numMembers) {
$.ajax({
url: publicBagUrl,
dataType: "text",
success: function(tiddlers) {
printFullInfo($.trim(tiddlers).split("\n").length);
}
});
} else {
html += 'This space has <a href="' + url + '">' + numTiddlers + " public tiddlers</a>";
$("#info").html(html);
}
}
});
}
var space = new tiddlyweb.Space(spaceName, "/");
space.members().get(function(members) {
countTiddlers(members);
}, function() {
countTiddlers();
});
function complete(tiddler) {
$("#siteinfo .edit").show();
$("#siteinfo .value").data("tiddler", tiddler).
empty().html(tiddler.render);
}
function siteInfoEditor(tiddler) {
var errback = function() {
$("#siteinfo .edit").click();
$("<div class='error' />").text("Error occurred whilst saving.").prependTo("#siteinfo .value");
};
$("<button class='edit' />").click(function(ev) {
var editBtn = $(ev.target);
$(editBtn).hide();
var val = $(".value", $(ev.target).parent("#siteinfo")[0]);
var wikitext = $(val).data("tiddler").text;
var html = $(val).html();
$(val).empty();
$("<textarea />").val(wikitext).appendTo(val);
$("<button />").text("save").click(function(ev) {
var text = $("textarea", val).val();
$(val).empty().text("saving...");
tiddler.text = text;
tiddler.put(function() {
tiddler.get(function(tid) {
tiddler = tid;
complete(tid);
}, errback, "render=y");
}, errback);
$(editBtn).show();
}).appendTo(val);
$("<button />").text("cancel").
click(function(ev) {
complete(tiddler);
}).appendTo(val);
}).text("edit").appendTo("#siteinfo");
}
var tiddler = new tiddlyweb.Tiddler("SiteInfo", new tiddlyweb.Bag(publicBag, "/"));
$("<div class='value' />").text("(Loading SiteInfo tiddler)").data("tiddler", tiddler).appendTo("#siteinfo");
tiddler.get(
function(tid) {
tiddler = tid;
$("#siteinfo .value").data("tiddler", tid).html(tid.render || tid.text);
if($(document.body).hasClass("ts-member")) {
siteInfoEditor(tid);
}
},
function() {
$("#siteinfo .value").text("This space has not published any information about itself.");
if($(document.body).hasClass("ts-member")) {
siteInfoEditor(tiddler);
}
}, "render=1");
});
function toggleNext(ev) {
var label1 = "hide";
var label2 = "show";
var target = $(ev.target).parent().next();
var visible = $(ev.target).hasClass("open") ? true : false;
var label = visible ? label1 : label2;
$(target).stop(true, true);
if(!visible) {
$(target).slideDown(200);
$(ev.target).addClass("open").text(label1);
} else {
if($(target).parents().is(":hidden")) {
// see http://forum.jquery.com/topic/slideup-doesn-t-work-with-hidden-parent
$(target).hide();
} else {
$(target).slideUp(200);
}
$(ev.target).removeClass("open").text(label2);
}
}
// setup hide/show sliders
$(".toggleNext").each(function(i, el) {
$(el).addClass("open").click(toggleNext);
toggleNext({ target: el });
});
if(window != window.top) {
$("html").addClass("iframeMode");
$("a").live("click",function(ev) {
$(ev.target).attr("target", "_blank");
});
}
</script>
<!--[if lt IE 8]>
<script type="text/javascript" src="/bags/common/tiddlers/json2.js"></script>
<![endif]-->
</body>
</html>
User-agent: *
Disallow: /bags/*/tiddlers.wiki
Disallow: /recipes/*/tiddlers.wiki
/***
|''Name''|ErrorHandlerPlugin|
|''Version''|0.4.3|
|''Author''|Jon Robson|
|''Description''|Localised tiddler save errors including edit conflict resolution.|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlySpaceConfig|
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var currentSpace = tiddlyspace.currentSpace.name;
tiddlyspace.getLocalTitle = function(title, workspace, suffix) {
var endsWith = config.extensions.BinaryTiddlersPlugin.endsWith;
if(!suffix) {
var isPublic = endsWith(workspace, "_public");
suffix = tiddlyspace.resolveSpaceName(workspace);
if(currentSpace == suffix) {
suffix = isPublic ? "public" : "private";
} else {
suffix = "@%0".format(suffix);
}
}
return "%0 *(%1)*".format(title, suffix);
};
var sssp = config.extensions.ServerSideSavingPlugin;
var msgs = config.messages.editConflict = {
loading: "Loading..",
resolve: "[[Edit Conflict]]@glossary: this tiddler may have been changed by someone else.",
reviewDiff: "review (recommended)",
reviewDiffTooltip: "review changes made to this tiddler",
reviewDiffError: "error retrieving revision.",
save: "overwrite",
saveTooltip: "make this revision the top revision of this tiddler",
discard: "cancel",
discardTooltip: "undo changes to this tiddler and get most recent version",
diffTitle: "%0",
diffFieldTitle: "%0 - fields",
diffTextTitle: "%0 - text",
updating: "updating your version...",
diffHeader: ["Review the changes that have been made whilst you were editing this tiddler. ",
"Fold relevant changes back into your version.\n",
"{{removed{Red}}} highlight shows content removed. ",
"{{added{Green}}} highlight shows content added.\n"].join(""),
diffTextHeader: "View changes in text",
diffFieldsHeader: "View changes in fields"
};
var plugin = config.extensions.errorHandler = {
diffTags: ["excludeLists", "excludeMissing", "excludeSearch"],
displayMessage: function(message, tiddler, context) {
var desc = context && context.httpStatus ? context.statusText :
sssp.locale.connectionError;
var reportArea = plugin.reportError(tiddler.title);
var msg = $("<div />").appendTo(reportArea);
if(message == "saveConflict") {
wikify(msgs.resolve, msg[0]);
var choiceArea = $("<div />").appendTo(reportArea)[0];
plugin.editConflictHandler(choiceArea, tiddler);
} else {
msg.text(sssp.locale[message].format(tiddler.title, desc));
}
},
editConflictHandler: function(container, tiddler) {
var title = tiddler.title;
var myrev = tiddler.fields["server.page.revision"];
// note user now needs to edit, fix problem and save.
// TODO: make sure this gets reset in save callback
store.getTiddler(title).fields["server.page.revision"] = "false";
var diffBtn = createTiddlyButton(container, msgs.reviewDiff, msgs.reviewDiffTooltip, function(ev) {
var title = $(ev.target).data("title");
plugin.displayDiff(ev.target, store.getTiddler(title), myrev);
});
var saveBtn = createTiddlyButton(container, msgs.save, msgs.saveTooltip, function(ev) {
var title = $(ev.target).data("title");
var tid = store.saveTiddler(store.getTiddler(title));
autoSaveChanges(null, [tid]);
});
var ignoreBtn = createTiddlyButton(container, msgs.discard, msgs.discardTooltip, function(ev) {
var title = $(ev.target).text(msgs.updating).data("title");
plugin.resetToServerVersion(store.getTiddler(title));
});
$([diffBtn, ignoreBtn, saveBtn]).data("title", title);
},
getDiffTiddlerTexts: function(diffText) {
var chunks = diffText.split("\n \n");
if(chunks.length < 2) {
return [chunks[0], ""];
} else {
var diffFieldsText = "{{diff{\n%0\n}}}".format(chunks[0]);
diffText = '{{diff{\n%0\n}}}'.format(chunks.splice(1, chunks.length).join("\n"));
return [diffText, diffFieldsText];
}
},
makeDiffTiddler: function(title, diff) {
var newTiddler = new Tiddler(title);
var tags = plugin.diffTags;
newTiddler.text = msgs.loading;
newTiddler.fields.doNotSave = true;
newTiddler.tags = diff ? tags.concat(["diff"]) : tags;
newTiddler = store.saveTiddler(newTiddler);
$.extend(store.getTiddler(title).fields,
config.defaultCustomFields); // allow option to save it
return newTiddler;
},
displayDiff: function(src, tiddler, latestRevision) {
var adaptor = tiddler.getAdaptor();
var title = tiddler.title;
var ts = new Date().formatString("0hh:0mm:0ss");
var suffix = "edit conflict %0".format(ts);
var diffTitle = tiddlyspace.getLocalTitle(msgs.diffTitle.format(title), "", suffix);
var diffTextTitle = tiddlyspace.getLocalTitle(msgs.diffTextTitle.format(title), "", suffix);
var diffFieldsTitle = tiddlyspace.getLocalTitle(msgs.diffFieldTitle.format(title), "", suffix);
plugin.makeDiffTiddler(diffTextTitle, true);
plugin.makeDiffTiddler(diffFieldsTitle, true);
var newTiddler = plugin.makeDiffTiddler(diffTitle, false);
newTiddler.text = ['%0\n<<slider chkViewDiffText "%1" "%2">>\n',
'<<slider chkViewDiffField "%3" "%4">>'].join("").
format(msgs.diffHeader, diffTextTitle, msgs.diffTextHeader,
diffFieldsTitle, msgs.diffFieldsHeader);
store.saveTiddler(newTiddler);
var callback = function(r) {
var text = plugin.getDiffTiddlerTexts(r);
store.getTiddler(diffTextTitle).text = text[0];
store.getTiddler(diffFieldsTitle).text = text[1];
story.refreshTiddler(diffTitle, null, true);
};
var workspace = "bags/%0".format(tiddler.fields["server.bag"]);
ajaxReq({
type: "get",
dataType: "text",
url: "/diff?format=unified&rev1=%0/%1/%2&rev2=%0/%1".format(workspace, title, latestRevision),
success: callback,
error: function() {
displayMessage(msgs.reviewDiffError);
}
});
story.displayTiddler(src, diffTitle);
},
resetToServerVersion: function(tiddler) {
var adaptor = tiddler.getAdaptor();
var ctx = {
host: tiddler.fields["server.host"],
workspace: "bags/" + tiddler.fields["server.bag"]
};
adaptor.getTiddler(tiddler.title, ctx, null, function(context) {
store.saveTiddler(context.tiddler);
story.refreshTiddler(tiddler.title);
store.setDirty(false);
});
},
reportError: function(title) {
var el = story.getTiddler(title);
if(!el) {
el = story.displayTiddler(null, title);
}
return $("<div />").addClass("error annotation").prependTo(el)[0];
}
};
sssp.reportFailure = function(message, tiddler, context) {
config.options.chkViewDiffText = config.options.chkViewDiffText === undefined ?
true : config.options.chkViewDiffText;
config.options.chkViewDiffFields = config.options.chkViewDiffFields || false;
plugin.displayMessage(message, tiddler, context);
};
})(jQuery);
//}}}
/***
|''Name''|TiddlySpaceTiddlerIconsPlugin|
|''Version''|0.8.10|
|''Status''|@@beta@@|
|''Author''|Jon Robson|
|''Description''|Provides ability to render SiteIcons and icons that correspond to the home location of given tiddlers|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceTiddlerIconsPlugin.js|
|''Requires''|TiddlySpaceConfig BinaryTiddlersPlugin ImageMacroPlugin TiddlySpacePublishingCommands|
!Notes
{{{<<tiddlerOrigin>>}}} shows the origin of the tiddler it is being run on.
In TiddlySpace terms this means it will determine whether the tiddler is external, public or private.
Where private it will analyse whether a public version exists and distinguish between the different scenarios.
If a tiddler is external, the SiteIcon of that external space will be shown
!Parameters
width / height : define a width or height of the outputted icon
label: if label parameter is set to yes, a label will accompany the icon.
!Code
***/
//{{{
(function($) {
if(!config.macros.image) {
throw "Missing dependency: ImageMacroPlugin";
}
var imageMacro = config.macros.image;
var tiddlyspace = config.extensions.tiddlyspace;
var tweb = config.extensions.tiddlyweb;
var cmds = config.commands;
var cmd = cmds.publishTiddler;
tiddlyspace.resolveSpaceName = function(value) {
var endsWith = config.extensions.BinaryTiddlersPlugin.endsWith;
if(value) {
value = value.indexOf("bags/") === 0 ? value.substr(5) : value;
value = value.indexOf("recipes/") === 0 ? value.substr(8) : value;
if(value.indexOf("@") === 0) {
value = value.substr(1);
}
if(endsWith(value, "_public")) {
value = value.substr(0, value.length - 7);
} else if(endsWith(value, "_private")) {
value = value.substr(0, value.length - 8);
}
value = value.toLowerCase();
}
return value;
};
tiddlyspace.renderAvatar = function(place, value, options) {
options = options ? options : {};
options.labelOptions = options.labelOptions ? options.labelOptions : { include: false, height: 48, width: 48 };
options.imageOptions = options.imageOptions ? options.imageOptions : {};
options.imageOptions.altImage = "/bags/common/tiddlers/defaultUserIcon";
var container = $('<div class="siteIcon" />').appendTo(place);
value = tiddlyspace.resolveSpaceName(value);
tweb.getStatus(function(status) {
var link, noLabel;
if(!value || value == config.views.wikified.defaultModifier ||
value == config.views.wikified.shadowModifier) {
var icon = config.views.wikified.shadowModifier == value ? "shadowIcon" : "missingIcon";
if(store.tiddlerExists(icon)) {
imageMacro.renderImage(container, icon, options.imageOptions);
} else {
noLabel = true;
}
} else {
var spaceURI;
if(value != tiddlyspace.currentSpace.name) {
spaceURI = options.notSpace ? tiddlyspace.getHost(status.server_host) :
tiddlyspace.getHost(status.server_host, value);
}
link = spaceURI ? $("<a />").attr("href", spaceURI) : $("<span />");
link.text(value);
var imageOptions = options.imageOptions;
if(options.spaceLink && !imageOptions.link) {
imageOptions.link = spaceURI;
}
var avatar = options.notSpace ? false : value;
var uri = tiddlyspace.getAvatar(status.server_host, avatar);
imageMacro.renderImage(container, uri, options.imageOptions);
if(!value) {
value = "tiddlyspace";
}
}
if(!noLabel && options.labelOptions.include) {
var prefix = $("<span />").text(options.labelOptions.prefix || "")[0];
var suffix = $("<span />").text(options.labelOptions.suffix || "")[0];
$('<div class="label" />').append(prefix).append(link).
append(suffix).appendTo(container);
}
});
if(value) {
var prefix = options.labelOptions.prefix || "";
var suffix = options.labelOptions.suffix || "";
var label = "%0%1%2".format(prefix, value, suffix);
$(container).attr("title", label);
}
};
var originMacro = config.macros.tiddlerOrigin = {
locale: {
"shadow": "shadow tiddler",
"missing": "missing tiddler",
"private": "private",
"unknown": "unknown state",
"public": "public",
"unsyncedPrivate": "unsynced and private",
"unsyncedPublic": "unsynced and public",
externalPrefix: "from ",
externalBagSuffix: " bag",
externalSuffix: " space",
publishPrivateDeletePrivate: "Are you sure you want to make this tiddler public?",
moveToPrivate: "Are you sure you want to make this tiddler private? Only members will be able to see it.",
pleaseWait: "please wait..",
keepPublic: "keep public",
cannotPublishDirtyTiddler: "The current tiddler is unsaved so cannot be published. Please save the tiddler first.",
keepPrivate: "keep private",
makePublic: "make public",
makePrivate: "make private"
},
handler: function(place, macroName, params,wikifier, paramString, tiddler){
var adaptor = tiddler.getAdaptor();
var btn = $("<div />").addClass("originButton").attr("params", paramString).
attr("refresh", "macro").attr("macroName", macroName).appendTo(place)[0];
$(btn).data("tiddler", tiddler);
originMacro.refresh(btn);
},
refresh: function(btn) {
$(btn).empty();
var paramString = $(btn).attr("params");
var tiddler = $(btn).data("tiddler");
var options = originMacro.getOptions(paramString);
var type = tiddlyspace.getTiddlerStatusType(tiddler);
originMacro.renderIcon(tiddler, type, btn, options);
},
getOptions: function(paramString) {
paramString = "%0 label:no width:48 height:48 spaceLink:yes preserveAspectRatio:yes".format(paramString);
var parsedParams = paramString.parseParams("name");
var params = parsedParams[0].name;
var options = {
labelOptions: originMacro._getLabelOptions(parsedParams),
imageOptions: imageMacro.getArguments(paramString, []),
noclick: parsedParams[0].interactive &&
parsedParams[0].interactive[0] == "no" ? true : false
};
if(!options.noclick) {
var spaceLink = parsedParams[0].spaceLink;
options.spaceLink = spaceLink && spaceLink[0] == "no" ? false : true;
} else {
options.spaceLink = false;
}
return options;
},
_getLabelOptions: function(parsedParams) {
parsedParams = parsedParams[0];
var includeLabel = !parsedParams.label || ( parsedParams.label && parsedParams.label[0] == "yes" );
var prefix = parsedParams.labelPrefix ? parsedParams.labelPrefix[0] : false;
var suffix = parsedParams.labelSuffix ? parsedParams.labelSuffix[0] : false;
return { include: includeLabel, suffix: suffix, prefix: prefix };
},
_isSpace: function(value) {
value = value ? value : "";
var endsWith = config.extensions.BinaryTiddlersPlugin.endsWith;
if(endsWith(value, "_private") || endsWith(value, "_public")) {
return true;
} else {
return false;
}
},
renderIcon: function(tiddler, type, button, options) {
var locale = originMacro.locale;
originMacro.annotateTiddler(button, type);
if(type != "external") {
originMacro.showPrivacyRoundel(tiddler, type, button,
options);
} else {
var prefix = options.labelOptions.prefix, suffix = options.labelOptions.suffix;
var space = tiddler.fields["server.bag"];
options.notSpace = !originMacro._isSpace(space);
options.labelOptions.prefix = prefix ? prefix : locale.externalPrefix;
options.labelOptions.suffix = suffix ? suffix : (options.notSpace ? locale.externalBagSuffix : locale.externalSuffix);
tiddlyspace.renderAvatar(button, space, options);
}
},
showPrivacyRoundel: function(thisTiddler, privacyType, button, options) {
// there is a public tiddler as well as the current tiddler!
// TODO: not this is not enough.. we also need to check if the public tiddler is the same as..
// .. the private tiddler to determine whether this is a draft
// use of hashes would be useful here.
$(button).empty();
var icon = "%0Icon".format(privacyType);
if(privacyType.indexOf("unsynced") === 0 && !store.tiddlerExists(icon)) {
icon = "unsyncedIcon";
}
if(privacyType == "shadow") {
if(!store.tiddlerExists(icon)) {
icon = "bags/tiddlyspace/tiddlers/SiteIcon";
}
}
if(privacyType == "missing" && !store.tiddlerExists(icon)) {
return; // the user is not making use of the missingIcon
} else {
imageMacro.renderImage(button, icon, options.imageOptions);
originMacro.showLabel(button, privacyType, options.labelOptions);
var cmd = originMacro.iconCommands[privacyType];
if(cmd && thisTiddler && !options.noclick) {
$(button).click(function(ev) {
cmd(ev, thisTiddler);
});
}
}
},
annotateTiddler: function(place, type) {
var tidEl = $(story.findContainingTiddler(place));
tidEl.
removeClass("private public external privateAndPublic privateNotPublic shadow").
addClass(type);
},
showLabel: function(button, type, options) {
var locale = originMacro.locale;
var label = options.label ? options.label : locale[type];
label = label ? label : locale.unknown;
if(options && options.include) {
$('<div class="roundelLabel" />').html(label).appendTo(button);
}
$(button).attr("title", label);
},
confirm: function(ev, msg, onYes, options) {
options = options ? options : {};
onYes = onYes ? onYes : function(ev) {};
var btn = $(".originButton", $(ev.target).parents())[0];
var popup = Popup.create(btn);
$(popup).addClass("confirmationPopup");
$("<div />").addClass("message").text(msg).appendTo(popup);
$("<button />").addClass("button").text(options.yesLabel || "yes").appendTo(popup).click(onYes);
$("<button />").addClass("button").text(options.noLabel || "no").click(function(ev) {
Popup.remove();
}).appendTo(popup);
Popup.show();
ev.stopPropagation();
return false;
},
alert: function(ev, msg) {
var popup = Popup.create(ev.target);
$(popup).addClass("confirmationPopup alert");
$("<div />").addClass("message").text(msg).appendTo(popup);
Popup.show();
ev.stopPropagation();
},
reportDirty: function(el) {
originMacro.alert(el, originMacro.locale.cannotPublishDirtyTiddler);
},
iconCommands: {
"public": function(ev, tiddler) {
if(!readOnly) {
var locale = originMacro.locale;
var msg = locale.moveToPrivate;
if(story.isDirty(tiddler.title)) {
originMacro.reportDirty(ev);
} else {
originMacro.confirm(ev, msg, function(ev) {
var target = $(ev.target);
var onComplete = function(info) {};
var privateBag = cmd.toggleBag(tiddler, "private");
cmd.moveTiddler(tiddler, {
title: tiddler.title,
fields: { "server.bag": privateBag }
}, onComplete);
}, { yesLabel: locale.makePrivate, noLabel: locale.keepPublic });
}
}
},
"private": function(ev, tiddler) {
if(!readOnly) {
var locale = originMacro.locale;
var adaptor = tiddler.getAdaptor();
var publishTo = tiddler.fields["publish.name"] || tiddler.title;
var workspace = "bags/%0".format(tiddler.fields["server.bag"]);
tiddler.fields["server.workspace"] = workspace;
var publicBag = cmd.toggleBag(tiddler, "public");
var msg;
msg = locale.publishPrivateDeletePrivate;
var title = tiddler.title;
var newTitle = publishTo || tiddler.title;
tiddler.fields["server.page.revision"] = "false";
store.addTiddler(tiddler);
if(story.isDirty(tiddler.title)) {
originMacro.reportDirty(ev);
} else {
originMacro.confirm(ev, msg, function(ev) {
var onComplete = function(info) {};
cmd.moveTiddler(tiddler, {
title: newTitle,
fields: { "server.bag": publicBag }
}, onComplete);
}, { yesLabel: locale.makePublic, noLabel: locale.keepPrivate });
}
}
}
}
};
})(jQuery);
//}}}
/***
|''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''|GroupByPlugin|
|''Description''|Mimics allTags macro to provide ways of creating lists grouping tiddlers by any field|
|''Version''|0.6.1|
|''Author''|Jon Robson|
|''Status''|beta|
!Usage
{{{<<groupBy tags>>}}}
mimics allTags macro
{{{<<groupBy server.bag>>}}}
groups by the server.bag field (this version contains TiddlySpace specific code for turning a bag into a space name)
{{{groupBy modified dateFormat:"YYYY"}}}
group tiddlers by year.
{{{<<groupBy tags exclude:excludeLists exclude:systemConfig>>}}}
group tiddlers by tag but exclude the tags with values excludeLists and systemConfig
Within that group you can also exclude things by filter
{{{groupBy modifier filter:[tag[film]]}}}
will group tiddlers tagged with film by modifier.
***/
//{{{
(function($) {
var taglocale = config.views.wikified.tag;
var macro = config.macros.groupBy = {
locale: {
tooltip: "all tiddlers in group %0",
noTiddlers: "no tiddlers",
openAllText: taglocale.openAllText,
openAllTooltip: taglocale.openAllTooltip,
openTiddler: "open tiddler with title %0"
},
morpher: {
// TODO: note currently the following 2 morphers are TiddlySpace specific and probably should be in separate plugin
"server.workspace": function(value, options) {
return macro.morpher["server.bag"](value.replace("bags/", "").replace("recipes/", ""));
},
"server.bag": function(value, options) {
if(typeof(value) !== "string") {
return false;
} else if(value.indexOf("_public") === -1 && value.indexOf("_private") === -1) {
value = "*%0".format(value); // add star for non-space bags.
}
return value.replace("_public", "").replace("_private", "");
},
created: function(value, options) {
return value.formatString(options.dateFormat || "DD MMM YYYY");
},
modified: function(value, options) {
return macro.morpher.created(value, options);
}
},
handler: function(place, macroName, params, wikifier, paramString) {
var field = params[0] || "server.workspace";
var dateFormat = params[1] || "DD MMM YYYY";
var container = $("<div />").attr("macroName", macroName).addClass("groupBy").
attr("refresh", "macro").attr("fieldName", field).
attr("paramString", paramString).
attr("dateFormat", dateFormat).appendTo(place)[0];
macro.refresh(container);
},
isTypeArray: function(value) {
var valueType = typeof value;
if(valueType === "object" && typeof value.length === "number" &&
!(value.propertyIsEnumerable("length")) &&
typeof value.splice === "function") { //is Array
return true;
} else {
return false;
}
},
_onClickGroup: function(ev, options) {
var i, target = ev.target, locale = macro.locale;
var tiddlers = $(target).closest(".templateContainer").data("tiddlers");
var popup = $(Popup.create(target)).addClass("taggedTiddlerList")[0];
var value = $(target).attr("value");
var openAll = createTiddlyButton($("<li />").appendTo(popup)[0],
locale.openAllText.format(value), locale.openAllTooltip);
$(openAll).click(function(ev) {
story.displayTiddlers(ev.target, tiddlers);
return false;
});
var listBreak = $("<li />").addClass("listBreak").html("<div />").appendTo(popup);
for(i = 0; i < tiddlers.length; i++) {
var item = $("<li />").appendTo(popup)[0];
var template = store.getTiddlerText(options.template) || macro.template;
wikify(template, item, null, tiddlers[i]);
}
listBreak.clone().appendTo(popup);
$(createTiddlyLink($("<li />").appendTo(popup)[0], value, false)).
text(locale.openTiddler.format(value));
Popup.show();
ev.stopPropagation();
return false;
},
_refresh: function(container, tiddlers, options) {
var totalGroups = 0, locale = macro.locale, i, j;
var excludeValues = options.exclude;
var values = {}, value_ids = [];
var field = options.field;
var morpher = macro.morpher[field] || function(value) {
return value;
};
for(i = 0; i < tiddlers.length; i++) {
var tiddler = tiddlers[i];
var value = tiddler[field] || tiddler.fields[field];
value = macro.isTypeArray(value) ? value : [ value ];
for(j = 0; j < value.length; j++) {
var v = morpher(value[j], options);
if(v && excludeValues.indexOf(v) === -1) {
totalGroups += 1;
if(!values[v]) {
values[v] = [];
}
values[v].push(tiddler);
value_ids.pushUnique(v);
}
}
}
var ul = $("<ul />").appendTo(container)[0];
if(totalGroups === 0) {
$("<li />").addClass("listTitle").text(locale.noTiddlers);
}
value_ids = value_ids.sort();
var groupTemplate = store.getTiddlerText(options.groupTemplate);
var onClick = function(ev) {
macro._onClickGroup(ev, options);
};
for(i = 0; i < value_ids.length; i++) {
var title = value_ids[i];
var info = getTiddlyLinkInfo(title);
tiddlers = values[title];
var btn = createTiddlyButton($("<li />").appendTo(ul)[0],
"%0 (%1)".format(title, tiddlers.length), locale.tooltip.format(title), null, info.classes);
if(groupTemplate) {
$(btn).empty();
wikify(groupTemplate, btn, null, tiddlers[0]);
}
$(btn).click(onClick).attr("value", title).attr("refresh", "link").attr("tiddlyLink", title);
$(btn).addClass("templateContainer").data("tiddlers", tiddlers);
}
},
refresh: function(container) {
container = $(container).empty();
var paramString = container.attr("paramString");
var args = paramString.parseParams("name", null, true, false, true)[0];
var options = { field: container.attr("fieldName"), dateFormat: container.attr("dateFormat"), exclude: args.exclude || [],
template: args.template ? args.template[0] : false, groupTemplate: args.groupTemplate ? args.groupTemplate[0] : "" };
var tiddlers = args.filter ? store.filterTiddlers(args.filter[0]) : store.getTiddlers("title");
macro._refresh(container, tiddlers, options);
},
template: "<<view title link>>"
};
}(jQuery));
//}}}
/***
|''Name''|TiddlySpaceViewTypes|
|''Version''|0.5.6|
|''Status''|@@beta@@|
|''Description''|Provides TiddlySpace specific view types|
|''Author''|Jon Robson|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceViewTypes.js|
|''Requires''|TiddlySpaceConfig TiddlySpaceTiddlerIconsPlugin|
!Usage
Provides replyLink, spaceLink and SiteIcon view types.
!!SiteIcon view parameters
* labelPrefix / labelSuffix : prefix or suffix the label with additional text. eg. labelPrefix:'modified by '
* spaceLink: if set to "yes" will make any avatars link to the corresponding space. {{{<<originMacro spaceLink:yes>>}}}
!Code
***/
//{{{
(function($) {
var tiddlyspace = config.extensions.tiddlyspace;
var originMacro = config.macros.tiddlerOrigin;
var tweb = config.extensions.tiddlyweb;
config.macros.view.replyLink = {
locale: {
label: "Reply to this tiddler"
}
};
config.macros.view.views.replyLink = function(value, place, params, wikifier,
paramString, tiddler) {
var valueField = params[0];
var imported;
if(valueField == "title") { // special casing for imported tiddlers
var localTitle = tiddler.title;
var serverTitle = tiddler.fields["server.title"];
if(serverTitle && localTitle != serverTitle) {
value = serverTitle ? serverTitle : localTitle;
imported = true;
}
} else {
title = tiddler[valueField] ? tiddler[valueField] : tiddler.fields[valueField];
}
var args = paramString.parseParams("anon")[0];
var label = (args.label) ? args.label : config.macros.view.replyLink.locale.label;
var space;
if(tiddler) {
var bag = tiddler.fields["server.bag"];
space = tiddlyspace.resolveSpaceName(bag);
}
var container = $('<span class="replyLink" />').appendTo(place)[0];
tweb.getUserInfo(function(user) {
if(!user.anon) {
if((space && user.name != space &&
user.name != tiddlyspace.currentSpace.name) || imported) {
createSpaceLink(container, user.name, value, label);
tweb.getStatus(function(status) { // force callback to run after existing callbacks
var url = config.extensions.tiddlyspace.getHost(status.server_host, user.name) + "#[[" + encodeURIComponent(value) + "]]";
jQuery("a", container).attr("href", url);
});
}
}
});
};
config.macros.view.views.spaceLink = function(value, place, params, wikifier,
paramString, tiddler) {
var spaceName = tiddlyspace.resolveSpaceName(value);
var isBag = params[0] == "server.bag" && value === spaceName ? true : false;
var args = paramString.parseParams("anon")[0];
var titleField = args.anon[2];
var labelField = args.labelField ? args.labelField[0] : false;
var label;
if(labelField) {
label = tiddler[labelField] ? tiddler[labelField] : tiddler.fields[labelField];
} else {
label = args.label ? args.label[0] : false;
}
var title = tiddler[titleField] ? tiddler[titleField] : tiddler.fields[titleField];
var link = createSpaceLink(place, spaceName, title, label, isBag);
if(args.external && args.external[0] == "no") {
$(link).click(function(ev) {
var el = $(ev.target);
var title = el.attr("tiddler");
var bag = el.attr("bag");
var space = el.attr("tiddlyspace");
bag = space ? space + "_public" : bag;
if(title && bag) {
ev.preventDefault();
tiddlyspace.displayServerTiddler(el[0], title,
"bags/" + bag);
}
return false;
});
}
};
config.macros.view.views.SiteIcon = function(value, place, params, wikifier,
paramString, tiddler) {
var options = originMacro.getOptions(paramString);
if(!tiddler || value == "None") { // some core tiddlers lack modifier
value = false;
}
var field = params[0];
if(field == "server.bag") {
options.notSpace = !originMacro._isSpace(value);
}
tiddlyspace.renderAvatar(place, value, options);
};
})(jQuery);
//}}}
/***
|''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";
})();
//}}}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="364 157 64 51" width="30" height="30"><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><g><path class="glyph" d="M 364.50006 184.50061 L 386.99985 207.00037 L 396 198.00002 L 373.50003 175.50066 Z M 403.02295 181.97704 C 400.38693 179.34099 396.11307 179.34099 393.47702 181.97704 C 390.841 184.61307 390.841 188.88695 393.47702 191.52298 C 396.11307 194.15903 400.38693 194.15903 403.02295 191.52298 C 405.65906 188.88695 405.65906 184.61307 403.02295 181.97704 M 414.27298 170.72704 C 411.63693 168.091 407.36307 168.091 404.72702 170.72704 C 402.091 173.36308 402.091 177.63693 404.72702 180.27296 C 407.36307 182.90901 411.63693 182.90901 414.27298 180.27296 C 416.90903 177.63693 416.90903 173.36308 414.27298 170.72704 M 425.523 159.47705 C 422.88696 156.841 418.6131 156.841 415.97705 159.47705 C 413.341 162.11308 413.341 166.38695 415.97705 169.02295 C 418.6131 171.65903 422.88696 171.65903 425.523 169.02295 C 428.15906 166.38695 428.15906 162.11308 425.523 159.47705" fill="#020202"/></g></g></svg>
/***
|''Name''|TiddlySpaceCloneCommand|
|''Version''|0.5.7|
|''Description''|provides a toolbar command for cloning external tiddlers|
|''Status''|stable|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceCloneCommand.js|
|''Requires''|TiddlySpaceConfig TiddlySpaceFilters|
!Code
***/
//{{{
(function($) {
var cmd = config.commands;
var tiddlyspace = config.extensions.tiddlyspace;
var fieldsCache = {};
cmd.cloneTiddler = {
text: cmd.editTiddler.text,
tooltip: "Create a copy of this tiddler in the current space",
errorMsg: "Error publishing %0: %1",
isEnabled: function(tiddler) {
return !config.filterHelpers.is.local(tiddler) && !readOnly;
},
handler: function(ev, src, title) {
var tiddler = store.getTiddler(title);
if(tiddler) {
fieldsCache[title] = $.extend({}, tiddler.fields);
tiddler.fields["server.workspace"] = tiddlyspace.getCurrentWorkspace("private");
tiddler.fields["server.permissions"] = "read, write, create"; // no delete
delete tiddler.fields["server.page.revision"];
delete tiddler.fields["server.title"];
delete tiddler.fields["server.etag"];
// special handling for pseudo-shadow tiddlers
if(tiddlyspace.coreBags.contains(tiddler.fields["server.bag"])) {
tiddler.tags.remove("excludeLists");
}
} else { // ensure workspace is the current space
var el = story.findContainingTiddler(src);
el = $(el);
var fields = el.attr("tiddlyfields");
if(fields) { // inherited via TiddlyLink
fields = fields.decodeHashMap();
fields["server.workspace"] = config.
defaultCustomFields["server.workspace"];
} else {
fields = config.defaultCustomFields;
}
fields = String.encodeHashMap(fields);
el.attr("tiddlyfields", fields);
}
cmd.editTiddler.handler.apply(this, arguments);
if(tiddler) {
tiddler.fields["server.permissions"] += ", delete";
}
return false;
}
};
cmd.editTiddler.isEnabled = function(tiddler) {
return !cmd.cloneTiddler.isEnabled.apply(this, arguments);
};
// hijack cancelTiddler to restore original fields
var _cancelHandler = cmd.cancelTiddler.handler;
cmd.cancelTiddler.handler = function(ev, src, title) {
var tiddler = store.getTiddler(title);
if(tiddler) {
tiddler.fields = fieldsCache[title] || tiddler.fields;
delete fieldsCache[title];
}
return _cancelHandler.apply(this, arguments);
};
// hijack saveTiddler to clear unused fields stash
var _saveHandler = cmd.saveTiddler.handler;
cmd.saveTiddler.handler = function(ev, src, title) {
delete fieldsCache[title];
return _saveHandler.apply(this, arguments);
};
})(jQuery);
//}}}
/***
|''Name''|EditTemplateFieldsPlugin|
|''Version''|0.4.6|
|''Status''|beta|
|''Author''|Jon Robson|
|''Description''|Provides editing of custom fields|
|''Requires''||
|''Source''||
!Usage
put {{{<div macro="editFields"></div>}}} into your EditTemplate.
or {{{<div macro="viewFields"></div>}}} into your ViewTemplate.
!Todo
Support newlines in input boxes.
!Code
***/
//{{{
(function($) {
var viewFields = config.macros.viewFields = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var fields = editFields.getFields(tiddler).concat(params);
var done = {};
for(var i = 0; i < fields.length; i++) {
var name = fields[i];
var val = tiddler.fields[name];
if(!done[name] && val) {
done[name] = true;
$("<div class='fieldValue' />").text("%0 : %1".format(name, val)).appendTo(place);
}
}
}
};
var editFields = config.macros.editFields = {
fieldType: {
images: ["image", "geo.marker"]
},
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var options = {
fields: params
};
this.createInterface(place, tiddler, options);
},
getFields: function(tiddler) {
var whitelisted = ["changecount"];
var fields = [];
for(var i in tiddler.fields) {
var val = tiddler.fields[i];
if(i.indexOf("server.") !== 0 && i.indexOf("_") !== 0 && typeof(val) == "string" && !whitelisted.contains(i) && val) {
fields.push(i);
}
}
return fields;
},
createInterface: function(place, tiddler, options) {
var fieldContainer = $("<div class='tiddlerCustomFields' />").appendTo(place);
var included = [];
if(tiddler) {
var fields = editFields.getFields(tiddler);
for(var i = 0; i < fields.length; i++) {
var name = fields[i];
var val = tiddler.fields[name];
this.addNewField(fieldContainer, name, val);
included.push(name);
}
}
for(var j = 0; j < options.fields.length; j++) {
var field = options.fields[j];
if(!included.contains(field)) {
this.addNewField(fieldContainer, field, "");
}
}
$("<button />").text("add new field").click(function() {
editFields.addNewField(fieldContainer, "", "");
}).appendTo(place);
},
addNewField: function(place, name, value) {
var container = $("<div />").appendTo(place);
if(editFields.fieldType.images.contains(name)) {
valueInput = $("<select />");
$("<option />").val("").text("").appendTo(valueInput);
$.each(store.filterTiddlers("[is[image]]"), function(i, tiddler) {
var opt = $("<option />").val(tiddler.title).text(tiddler.title).appendTo(valueInput)[0];
if(value === tiddler.title) {
$(opt).attr("selected", true);
}
});
} else {
type = value.indexOf("\n") > -1 ? "textarea" : "text";
var valueInput = type == "text" ? $("<input type='text' />") : $("<textarea />");
}
valueInput.attr("edit", name).val(value).appendTo(container);
$("<button class='delete' />").text("delete").click(function(ev) {
var answer = confirm("Are you sure you want to remove this field?");
if(answer) {
var attr = $(ev.target).attr("field");
var p = $(ev.target).parent();
$("[edit]", p).val("");
$(p).hide();
}
}).appendTo(container);
var nameInput = $("<input class='fieldName' type='text' />").val(name).
change(function(ev) {
var el = $(ev.target);
valueInput.attr("edit", el.val());
}).prependTo(container);
}
}
})(jQuery);
//}}}
<<tiddler Backstage##Tiddlers>>