Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
* 09/10/07 - refresh mechanism implemented in a way I don't fully understand - could be unstable?
* 16/10/07 - Creating a new tiddler doesn't add the unread field necessary to make the unread macro work properly
* 16/10/07 - list view looking for unread property on latest note, not on parent tiddler; notes don't even need unread properties (although they will probably keep them for ease)
* 16/10/07 - author name not working for imported content
* 16/10/07 - existing content is overwritten by other people's content; stop this and alert the user; overwrites notes too - maybe we can do something with the number extension? Although that might break the symmetry of content between TiddlyWikis. This becomes a big issue when there are more than two people, as this could happen frequently.
* 16/10/07 - if we're going to have public and published tags, then we need to: show whether public content is published or not in the list view; not need ChatterFeeds to be tagged public, as we don't want them showing up in the list view - or do we? There needs to be a clear semantic difference between something tagged public and something tagged published; also, Manage subscriptions appears to work with a tag it shouldn't - think it is published
* 16/10/07 - Tiddler says "No notes" if you import other notes
* 16/10/07 - Ken Girard - I discovered that the current 'New subscription' button over writes the current ChatterFeed tiddler, so put the following in a tiddler named what ever:
{{{|''Type:''|RSS|
|''URL:''|http://no-sin.com/wiki/tiddlychatter.xml|
|''Workspace:''|No-SinFeed|
|''TiddlerFilter:''|[tag[public]]|}}}
** Tag it: "systemServer TiddlyChatter public channel" and lets see where it goes.
|''Type:''|RSS|
|''URL:''|http://tiddlychatter.tiddlyspot.com/index.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #300
PrimaryMid: #800
PrimaryDark: #014
SecondaryPale: #D2B48C
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
{{{
/*** 16/10 - Patch not accepted, being revised ***/
ListView.getCommandHandler = function(callback,name,allowEmptySelection)
{
return function(e) {
var that = this;
var view = findRelated(this,"TABLE",null,"previousSibling");
var tiddlers = [];
ListView.forEachSelector(view,function(e,rowName) {
if(e.checked)
tiddlers.push(rowName);
});
if(tiddlers.length == 0 && !allowEmptySelection) {
alert(config.messages.nothingSelected);
} else {
if(this.nodeName.toLowerCase() == "select") {
callback.call(that,view,this.value,tiddlers);
this.selectedIndex = 0;
} else {
callback.call(that,view,name,tiddlers);
}
}
return false;
};
};
/*** 16/10 - Patch not submitted yet ***/
// Returns the number of days since the Date
Date.prototype.relativeDays = function() {
var now = new Date();
var interval = now.getTime() - this.getTime();
interval = Math.floor(interval / (1000 * 60 * 60 * 24));
return interval;
};
}}}
I served the meal on a silver platter, and they were offended it was not gold. When I transferred it to the gold platter, they asked "Are we not good enough for the platinum?".
One group wants us to print and ship 160lbs of their customized workbook...for free. Oh, and have it there by Wed, even though the training is on Friday.
The other group wants the tech challenged trainer to show up early, hook her laptop to their projector and make sure it all works (Do my wires match your holes?), and then stay late to remove it all...and not be charged for it, which means we eat the bill for her extra time. The customer has a computer in the presentation room, but it is not a laptop.
10 Head to desk
20 Goto 10
Why do they always think it needs to be a laptop? Because that is what they have always seen used with a projector, duh!
Stupidity is painful, but not to those afflicted with it.
[[Welcome to TiddlyChatter]]
//{{{
// Gets the list of tiddlers within a given workspace
FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
{
if(!this.store)
return FileAdaptor.NotLoadedError;
if(!context)
context = {};
// context.tiddlers = [];
// this.store.forEachTiddler(function(title,tiddler)
// {
// var t = new Tiddler(title);
// t.text = tiddler.text;
// t.modified = tiddler.modified;
// t.modifier = tiddler.modifier;
// t.fields['server.page.revision'] = tiddler.modified.convertToYYYYMMDDHHMM();
// t.tags = tiddler.tags;
// context.tiddlers.push(t);
// });
context.tiddlers = this.store.filterTiddlers(filter);
for(var t=0; t<context.tiddlers.length; t++) {
context.tiddlers[t].fields['server.page.revision'] = context.tiddlers[t].modified.convertToYYYYMMDDHHMM();
}
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
//}}}
[evolved from the original [[blog post|http://jayfresh.wordpress.com/2007/09/24/tiddlychatter-decentralized-collaboration/]] on the subject]
Here’s an illustration of what ~TiddlyChatter is all about:
* Jon and his class have been set some tricky homework, so he creates a stub of what he’s working on and publishes it, mentioning to Liz, his classmate, that they ought to work on this together
* Liz subscribes to Jon’s feed and the stub turns up on Liz’s computer for her to see and/or comment on
* Liz adds a note about a useful resource
* Jon subscribes to Liz’s feed and her note turns up in place on Jon’s computer, which turns out to be very helpful…
This doesn’t sound so different from normal collaboration, but there are a couple of important differences:
* ''opt-in:'' If Jon decides Liz is no good as a partner, he can stop watching her feed and he never sees any of the notes Liz makes
** it is only when their teacher, Alice, eventually "opts-in" to Jon's feed (at his request... :() that she will see the result of the group’s work.
* ''decentralized:'' If Ben comes in and subscribes to Jon’s feed, he can make his own comments, and Jon will see them if he subscribes to Ben's feed, regardless of what servers or software Ben is using.
** Extending this slightly, Ben, Liz and Jon can all share and work on the information together; if someone else wants to join the group, it only takes one member to subscribe to the new person's feed for the new content to filter through to the rest of the group.
* ''work offline:'' Everyone's bits of work appear on their computers separate to everyone else, so they can walk away with their ~TiddlyWikis and only need to sync-up when they get back online
/***
|''Name:''|ImportWorkspacePlugin|
|''Description:''|Commands to access hosted TiddlyWiki data|
|''Author:''|Martin Budden (mjbudden (at) gmail (dot) com)|
|''Source:''|http://www.martinswiki.com/#AdaptorMacrosPlugin |
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/MartinBudden/adaptors/ImportWorkspacePlugin.js |
|''Version:''|0.0.1|
|''Date:''|Aug 23, 2007|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]] |
|''~CoreVersion:''|2.2.0|
|''Tag for import''|<<option txtImportTag>>|
|''Import workspace on startup''|<<option chkImportWorkspaceOnStartup>>|
|''Label for go button''|<<option txtImportLabel>>|
***/
// CHANGE: I have removed this line to tailor this plugin to select systemServer tiddlers by tag:
// |''Feed for import - DON'T USE''|<<option txtImportFeed>>|
//{{{
// Ensure that the plugin is only installed once.
if(!version.extensions.ImportWorkspacePlugin) {
version.extensions.ImportWorkspacePlugin = {installed:true};
if(config.options.txtImportTag == undefined)
{config.options.txtImportTag = '';}
if(config.options.chkImportWorkspaceOnStartup == undefined)
{config.options.chkImportWorkspaceOnStartup = false;}
config.messages.hostOpened = "Host '%0' opened";
config.messages.workspaceOpened = "Workspace '%0' opened";
config.messages.workspaceTiddlers = "%0 tiddlers in workspace, importing %1 of them";
config.messages.tiddlerImported = 'Tiddler: "%0" imported';
// import all the tiddlers from a given workspace on a given host
config.macros.importWorkspace = {};
merge(config.macros.importWorkspace,{
label: "import workspace",
prompt: "Import tiddlers in workspace",
done: "Tiddlers imported"});
config.macros.importWorkspace.init = function()
{
var customFields = config.defaultCustomFields;
if(!customFields['server.type']) {
var tag = config.options.txtImportTag;
var title = "";
if(tag=='') {
var tiddlers = store.getTaggedTiddlers("systemServer");
if(tiddlers.length==0)
return;
title = tiddlers[0].title;
} else {
var tiddlers = store.getTaggedTiddlers(tag);
if(tiddlers.length==0)
return;
title = tiddlers[0].title;
}
config.defaultCustomFields['server.type'] = store.getTiddlerSlice(title,'Type');
config.defaultCustomFields['server.host'] = store.getTiddlerSlice(title,'URL');
config.defaultCustomFields['server.workspace'] = store.getTiddlerSlice(title,'Workspace');
config.defaultCustomFields['server.filter'] = store.getTiddlerSlice(title,'TiddlerFilter');
}
if(config.options.chkImportWorkspaceOnStartup)
config.macros.importWorkspace.getTiddlers(customFields);
};
// I'm finding that this runs before the init function!
// My evidence for this is through config.log calls, so I assume that they execute in the order they are called
config.macros.importWorkspace.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams('anon',null,true,false,false);
var customFields = getParam(params,'fields',false);
if(!customFields) {
customFields = config.defaultCustomFields;
}
if(!customFields['server.type']) {
var title = "";
var tag = config.options.txtImportTag;
if (tag=='') {
title = getParam(params,'anon');
if(!title) {
var tiddlers = store.getTaggedTiddlers("systemServer");
if(tiddlers.length>0)
title = tiddlers[0].title;
}
} else {
// if we get here, the user has not provided field params and they have not been
// set in the init function, and we have a tag to use for looking up a tiddler
var tiddlers = store.getTaggedTiddlers(tag);
if(tiddlers.length==0)
return;
title = tiddlers[0].title;
}
if(title) {
customFields = {};
customFields['server.type'] = store.getTiddlerSlice(title,'Type');
customFields['server.host'] = store.getTiddlerSlice(title,'URL');
customFields['server.workspace'] = store.getTiddlerSlice(title,'Workspace');
customFields['server.filter'] = store.getTiddlerSlice(title,'TiddlerFilter');
}
}
customFields = String.encodeHashMap(customFields);
if(config.options.txtImportLabel) this.label = config.options.txtImportLabel;
var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick);
btn.setAttribute('customFields',customFields);
};
config.macros.importWorkspace.onClick = function(e)
{
clearMessage();
// displayMessage("Starting import...");
var customFields = this.getAttribute('customFields');
var fields = customFields ? customFields.decodeHashMap() : config.defaultCustomFields;
config.macros.importWorkspace.getTiddlers(fields);
};
config.macros.importWorkspace.getTiddlers = function(fields)
{
if(!fields['server.type']) {
var tiddlers = store.getTaggedTiddlers("systemServer");
var title = tiddlers[0].title;
fields = {};
fields['server.type'] = store.getTiddlerSlice(title,'Type');
fields['server.host'] = store.getTiddlerSlice(title,'URL');
fields['server.workspace'] = store.getTiddlerSlice(title,'Workspace');
fields['server.filter'] = store.getTiddlerSlice(title,'TiddlerFilter');
}
var serverType = fields['server.type'];
if(!serverType)
serverType = fields['wikiformat'];
if(!serverType)
return false;
var adaptor = new config.adaptors[serverType];
if(adaptor) {
var context = {};
context.host = fields['server.host'];
context.workspace = fields['server.workspace'];
context.adaptor = adaptor;
context.filter = fields['server.filter'];
adaptor.openHost(context.host,context,null,config.macros.importWorkspace.openHostCallback);
}
};
config.macros.importWorkspace.openHostCallback = function(context,userParams)
{
displayMessage(config.messages.hostOpened.format([context.host]));
//window.setTimeout(context.adaptor.openWorkspace,0,context.workspace,context,config.macros.importWorkspace.openWorkspaceCallback);
context.adaptor.openWorkspace(context.workspace,context,userParams,config.macros.importWorkspace.openWorkspaceCallback);
};
config.macros.importWorkspace.openWorkspaceCallback = function(context,userParams)
{
displayMessage(config.messages.workspaceOpened.format([context.workspace]));
//window.setTimeout(context.adaptor.openWorkspace,0,context.workspace,context,config.macros.importWorkspace.getTiddlerListCallback);
displayMessage("using import filter: " + context.filter);
context.adaptor.getTiddlerList(context,userParams,config.macros.importWorkspace.getTiddlerListCallback,context.filter);
};
config.macros.importWorkspace.getTiddlerListCallback = function(context,userParams)
{
if(context.status) {
var tiddlers = context.tiddlers;
var sortField = 'modified';
tiddlers.sort(function(a,b) {return a[sortField] < b[sortField] ? +1 : (a[sortField] == b[sortField] ? 0 : -1);});
var length = tiddlers.length;
if(userParams && userParams.maxCount && length > userParams.maxCount)
length = userParams.maxCount;
displayMessage(config.messages.workspaceTiddlers.format([tiddlers.length,length]));
var import_count = 0;
for(var i=0; i<length; i++) {
tiddler = tiddlers[i];
var local_tiddler = store.fetchTiddler(tiddler.title);
// only import the tiddler if it doesn't exist locally or, if it does, hasn't been edited
if(!local_tiddler || !local_tiddler.isTouched()) {
context.adaptor.getTiddler(tiddler.title,null,null,config.macros.importWorkspace.getTiddlerCallback);
import_count++;
}
}
}
// THIS IS A VERY BAD THING TO DO, AS IS IT SPECIFIC, NOT BUILT FOR RE-USE
// I SUGGEST WE CHANGE THE PLUGIN TO TAKE A CALLBACK FOR EXECUTING AT THE END OF THE IMPORT I.E. HERE
// now everything's done, open up the TiddlyChatterIncoming tiddler
// config.macros.importWorkspace.finalise(context);
};
config.macros.importWorkspace.getTiddlerCallback = function(context,userParams)
{
displayMessage("getting " + context.tiddler.title);
if(context.status) {
var tiddler = context.tiddler;
// add in an extended field to save unread state
tiddler.fields["unread"] = true;
store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
story.refreshTiddler(tiddler.title,1,true);
} else {
displayMessage(context.statusText);
}
story.refreshAllTiddlers();
};
} // end of 'install only once'
//}}}
<<tiddlyChatterIncoming>>
# You can install ~TiddlyChatter directly from your ~TiddlySpot ~TiddlyWiki, even if you are online, thanks to [[the work|http://groups.google.com/group/TiddlyWikiDev/browse_thread/thread/6571e482fc99f738/e5add4f7f9cc2391#e5add4f7f9cc2391]] of [[BidiX|http://bidix.info]] and [[Simon Baird|http://tiddlyspot.com/?page=about]]. If your ~TiddlySpot ~TiddlyWiki was created after 17/10/07, you will have the necessary plugin installed by default; otherwise, you'll need to download a local copy and import LoadRemoteFileThroughProxy from http://tiddlywiki.bidix.info/.
# To get hold of the set of tiddlers needed for ~TiddlyChatter, you need to use the ~ImportPlugin (often via the backstage area) in your own ~TiddlyWiki. When asked for the type of server, choose "File" and for the URL to download from, use this URL and then click "open":
#* http://tiddlychatter.tiddlyspot.com/index.html
# You don't need to bother setting a value for the workspace, just click "open" again. When you are presented with the list of tiddlers, choose the ones tagged <<tag TiddlyChatterPackage>>. You can check in TiddlyChatterDocumentation to see which other plugins are used and which are modified. I have made an effort to make sure that any modifications are backwards-compatible and that overwriting the originals you already have is safe. If you have any problems, please report them on the [[TiddlyWikiDev|http://groups.google.com/TiddlyWikiDev]] Google Group.
Once you have ~TiddlyChatter installed on your own ~TiddlyWiki, you get a [[feed|TiddlyChatterPackage]] to use for future synchronisation with this version of ~TiddlyChatter.
/***
|''Name:''|LoadRemoteFileThroughProxy (previous LoadRemoteFileHijack)|
|''Description:''|When the TiddlyWiki file is located on the web (view over http) the content of [[SiteProxy]] tiddler is added in front of the file url. If [[SiteProxy]] does not exist "/proxy/" is added. |
|''Version:''|1.1.0|
|''Date:''|mar 17, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#LoadRemoteFileHijack|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
***/
//{{{
version.extensions.LoadRemoteFileThroughProxy = {
major: 1, minor: 1, revision: 0,
date: new Date("mar 17, 2007"),
source: "http://tiddlywiki.bidix.info/#LoadRemoteFileThroughProxy"};
if (!window.bidix) window.bidix = {}; // bidix namespace
if (!bidix.core) bidix.core = {};
bidix.core.loadRemoteFile = loadRemoteFile;
loadRemoteFile = function(url,callback,params)
{
if ((document.location.toString().substr(0,4) == "http") && (url.substr(0,4) == "http")){
url = store.getTiddlerText("SiteProxy", "/proxy/") + url;
}
return bidix.core.loadRemoteFile(url,callback,params);
}
//}}}
[[Welcome|Welcome to TiddlyChatter]]
[[Set-up info|Setting up TiddlyChatter]]
[[BUGS]]
~TiddlyChatter controls
----
<<newTiddler label:"New Chatter" title:NewChatter tag:public text:"Type some text and then press DONE">>
[[Control panel|TiddlyChatter control panel]]
<<importWorkspace>>
[[Incoming!]]
<script language="javascript" type="text/javascript" src="../../../firebuglite/firebug/firebug.js"></script>
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
<link href="http://no-sin.comimages/favicon.ico" rel="SHORTCUT ICON">
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-80126-1";
urchinTracker();
</script>
<!--}}}-->
Seems that I am not only a calender model, but now also the subject of a web comic:
Leaf Blower Incident at Dragon Con 2007
[img[http://no-sin.com/images/leafblower.jpg][http://devilspanties.keenspot.com/d/20071012.html]]
Thanks, Jenni. http://devilspanties.keenspot.com
At least she didn't try and show if I was wearing my kilt correctly or not.
But after seeing this, I think I need to go on a diet.
<html>Help me! Help me! I have quantum physics to do... anyone?</html>
People have asked "Why No-Sin?". The answer is simple. The first 15 or so names I wanted where taken, and No Sin seemed kinda rebellious against the closed minded. Also given my interpretation of what sin is, 'no sin' fit me. The dash is just to keep the words apart. Without it you would currently be at 'nosin' and no one would understand what I was trying to say.
>No: an adjective used to indicate that there is not any, or not one person or thing.
>Sin: Having regret for one's actions, inactions or thoughts.
Your interpretation of these words may be something else. Good for you.
|''Type:''|RSS|
|''URL:''|http://no-sin.com/wiki/tiddlychatter.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
/***
|''Name:''|NotesPlugin|
|''Description:''||
|''Author:''|Saq Imtiaz ( lewcid@gmail.com )|
|''Source:''|http://tw.lewcid.org/#NotesPlugin|
|''Code Repository:''|http://tw.lewcid.org/svn/plugins|
|''Version:''|2.0|
|''Date:''||
|''License:''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
|''~CoreVersion:''|2.2.3|
!!Usage:
*
***/
// /%
//!BEGIN-PLUGIN-CODE
function createTiddlyElement(theParent,theElement,theID,theClass,theText,attribs)
{
var e = document.createElement(theElement);
if(theClass != null)
e.className = theClass;
if(theID != null)
e.setAttribute("id",theID);
if(theText != null)
e.appendChild(document.createTextNode(theText));
if(attribs){
for(var n in attribs){
e.setAttribute(n,attribs[n]);
}
}
if(theParent != null)
theParent.appendChild(e);
return e;
}
function createTiddlyButton(theParent,theText,theTooltip,theAction,theClass,theId,theAccessKey,attribs)
{
var theButton = document.createElement("a");
if(theAction) {
theButton.onclick = theAction;
theButton.setAttribute("href","javascript:;");
}
if(theTooltip)
theButton.setAttribute("title",theTooltip);
if(theText)
theButton.appendChild(document.createTextNode(theText));
if(theClass)
theButton.className = theClass;
else
theButton.className = "button";
if(theId)
theButton.id = theId;
if(attribs){
for(var n in attribs){
e.setAttribute(n,attribs[n]);
}
}
if(theParent)
theParent.appendChild(theButton);
if(theAccessKey)
theButton.setAttribute("accessKey",theAccessKey);
return theButton;
}
config.macros.notes={
cancelWarning: "Are you sure you want to abandon changes to your notes for '%0'?",
addLabel: "add notes",
editLabel: "edit notes",
editTitle: "double click to edit",
saveLabel: "save notes",
saveTitle: "double click to save",
cancelLabel: "cancel",
heading: "Notes",
suffix: "Notes",
tags: "Notes",
saveNotes: function(ev){
e = ev? ev : window.event;
var theTarget = resolveTarget(e);
if (theTarget.nodeName.toLowerCase() == "textarea")
return false;
var title = story.findContainingTiddler(theTarget).getAttribute("tiddler");
story.setDirty(title,false);
var box = document.getElementById("notesContainer"+title);
// if 'save notes' is clicked on, notesBox is this.parentNode
var notesBox = this.parentNode;
if (this.getAttribute("noteCount")) {
// if notesBox has been double-clicked, notesBox is this
notesBox = this;
}
var noteCount = notesBox.getAttribute("noteCount");
var textarea = document.getElementById("notesTextArea"+noteCount+title);
if(textarea.getAttribute("oldText")!=textarea.value){
var suffix = box.getAttribute("suffix");
var t = store.getTiddler(title+"-"+suffix+noteCount);
// this line changed to split the tags attribute
store.saveTiddler(title+"-"+suffix+noteCount,title+"-"+suffix+noteCount,textarea.value,config.options.txtUserName,new Date(),t?t.tags:box.getAttribute("tags").split(" "),t?t.fields:{});
}
story.refreshTiddler(title,1,true);
return false;
},
editNotes: function(notesBox,box,tiddler){
removeChildren(notesBox);
story.setDirty(tiddler,true);
notesBox.title = this.saveTitle;
notesBox.ondblclick = this.saveNotes;
// Q: this cancel button doesn't appear to work! Is this what you meant to do?
createTiddlyButton(notesBox,this.cancelLabel,this.cancelLabel,this.saveNotes,"cancelNotesButton");
createTiddlyButton(notesBox,this.saveLabel,this.saveLabel,this.saveNotes,"saveNotesButton");
wikify("!!"+box.getAttribute("heading")+"\n",notesBox);
addClass(notesBox,"editor");
var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
var wrapper2 = createTiddlyElement(wrapper1,"div");
var e = createTiddlyElement(wrapper2,"textarea","notesTextArea"+notesBox.getAttribute("noteCount")+tiddler);
var v = store.getValue(tiddler+"-"+box.getAttribute("suffix")+notesBox.getAttribute("noteCount"),"text");
if(!v)
v = "";
e.value = v;
e.setAttribute("oldText",v);
var rows = 10;
var lines = v.match(/\n/mg);
var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
if(lines != null && lines.length > rows)
rows = lines.length + 5;
rows = Math.min(rows,maxLines);
e.setAttribute("rows",rows);
notesBox.appendChild(wrapper1);
},
editNotesButtonOnclick: function(e){
var title = story.findContainingTiddler(this).getAttribute("tiddler");
var box = document.getElementById("notesContainer"+title);
var notesBox = this.parentNode;
config.macros.notes.editNotes(notesBox,box,title);
return false;
},
addNotesButtonOnclick: function(e){
var title = story.findContainingTiddler(this).getAttribute("tiddler");
var box = document.getElementById("notesContainer"+title);
var oldNoteCount = box.getAttribute("notesCount");
var notesBox = createTiddlyElement(box,"div",null,"TiddlerNotes",null,{noteCount:oldNoteCount++});
notesBox.ondblclick = config.macros.notes.ondblclick;
removeNode(this);
config.macros.notes.editNotes(notesBox,box,title);
return false;
},
ondblclick : function(ev){
e = ev? ev : window.event;
var theTarget = resolveTarget(e);
var title = story.findContainingTiddler(theTarget).getAttribute("tiddler");
var box = document.getElementById("notesContainer"+title);
var notesBox = this;
config.macros.notes.editNotes(notesBox,box,title);
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return false;
},
handler : function(place,macroName,params,wikifier,paramString,tiddler){
params = paramString.parseParams("anon",null,true,false,false);
var heading = getParam(params,"heading",this.heading);
// tags can be a space-separated string of tags
// this parameter allows you to specify an identiying tag for the note
// then we inherit the parent's tags
// when we create the Notes box, we use tags as its tags attribute
var tags_string = getParam(params,"tags",this.tags);
var tags = tags_string.split(" ");
for (var i=0;i<tiddler.tags.length;i++) {
if (!tags.contains(tiddler.tags[i])) {
tags_string += " " + tiddler.tags[i].toString();
}
}
var suffix = getParam(params,"suffix",this.suffix);
// Get the notes tiddlers for this tiddler, count them, make the count an attribute on the box
var notes_tiddlers = store.getTaggedTiddlers("notes");
var notes = [];
var notesCount = 0;
for (var i=0;i<notes_tiddlers.length;i++) {
if (notes_tiddlers[i].title != tiddler.title && notes_tiddlers[i].title.indexOf(tiddler.title) != -1) {
notes.push(notes_tiddlers[i]);
notesCount++;
}
}
var box = createTiddlyElement(place,"div","notesContainer"+tiddler.title,"TiddlerNotes",null,{"source":tiddler.title,params:paramString,heading:heading,tags:tags_string,suffix:suffix,notesCount:notesCount});
// if there aren't any notes, show "No notes"
// if there are notes, show "Notes by xxx"
// if you added the notes, show "Notes by you"
// REMOVED: var notes_tiddler = store.fetchTiddler(tiddler.title+"-"+suffix);
var heading_extension = "";
if (notes[0] && notes[0].modifier) {
wikify("!!"+heading+"\n",box);
} else {
wikify("//No notes//\n",box);
}
box.title=this.editTitle;
// These lines unnecessary with createTiddlyElement that takes an object of attributes
//box.setAttribute("source",tiddler.title);
//box.setAttribute("params",paramString);
//box.setAttribute("heading",heading);
//box.setAttribute("tags",tags_string);
//box.setAttribute("suffix",suffix);
// REMOVED: box.ondblclick = this.ondblclick;
for (var i=0;i<notes.length;i++) {
var notesBox = createTiddlyElement(box,"div",null,"TiddlerNotes",null,{noteCount:i});
notesBox.ondblclick = this.ondblclick;
wikify("<<tiddler [["+notes[i].title+"]]>>\n",notesBox);
createTiddlyElement(notesBox,"span",null,"subtitle","at "+notes[i].modified+" by "+notes[i].modifier);
createTiddlyButton(notesBox,this.editLabel,this.editLabel,this.editNotesButtonOnclick,"editNotesButton");
}
// add 'add notes' button
createTiddlyButton(box,this.addLabel,this.addLabel,this.addNotesButtonOnclick,"editNotesButton");
}
};
/* CHANGE: 09/10/07 - not sure why this is needed
Story.prototype.old_notes_closeTiddler = Story.prototype.closeTiddler;
Story.prototype.closeTiddler = function(title,animate,unused){
if(story.isDirty(title)) {
if(!confirm(config.macros.notes.cancelWarning.format([title])))
return false;
}
return this.old_notes_closeTiddler.apply(this,arguments);
}
*/
setStylesheet(".TiddlerNotes {\n"+ " background:#eee;\n"+ " border:1px solid #ccc;\n"+ " padding:10px;\n"+ " margin:15px;\n"+ "}\n"+ "\n"+ ".cancelNotesButton,.editNotesButton, .saveNotesButton {\n"+ " float:right;\n"+ " border:1px solid #ccc;\n"+ " padding:2px 5px;\n"+ "}\n"+ "\n"+ ".saveNotesButton{\n"+ " margin-right:0.5em;\n"+ "}\n"+ "\n"+ ".TiddlerNotes.editor textarea{\n"+ " border:1px solid #ccc;\n"+ "}","NotesPluginStyles");
//sliders
//keyboard shortcuts
// ids..
// ids..
//!END-PLUGIN-CODE
// %/
/***
|''Name:''|PasswordOptionPlugin|
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Version:''|1.0.2|
|''Date:''|Apr 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#PasswordOptionPlugin|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (Beta 5)|
***/
//{{{
version.extensions.PasswordOptionPlugin = {
major: 1, minor: 0, revision: 2,
date: new Date("Apr 19, 2007"),
source: 'http://tiddlywiki.bidix.info/#PasswordOptionPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',
coreVersion: '2.2.0 (Beta 5)'
};
config.macros.option.passwordCheckboxLabel = "Save this password on this computer";
config.macros.option.passwordInputType = "password"; // password | text
setStylesheet(".pasOptionInput {width: 11em;}\n","passwordInputTypeStyle");
merge(config.macros.option.types, {
'pas': {
elementType: "input",
valueField: "value",
eventName: "onkeyup",
className: "pasOptionInput",
typeValue: config.macros.option.passwordInputType,
create: function(place,type,opt,className,desc) {
// password field
config.macros.option.genericCreate(place,'pas',opt,className,desc);
// checkbox linked with this password "save this password on this computer"
config.macros.option.genericCreate(place,'chk','chk'+opt,className,desc);
// text savePasswordCheckboxLabel
place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));
},
onChange: config.macros.option.genericOnChange
}
});
merge(config.optionHandlers['chk'], {
get: function(name) {
// is there an option linked with this chk ?
var opt = name.substr(3);
if (config.options[opt])
saveOptionCookie(opt);
return config.options[name] ? "true" : "false";
}
});
merge(config.optionHandlers, {
'pas': {
get: function(name) {
if (config.options["chk"+name]) {
return encodeCookie(config.options[name].toString());
} else {
return "";
}
},
set: function(name,value) {config.options[name] = decodeCookie(value);}
}
});
// need to reload options to load passwordOptions
loadOptionsCookie();
/*
if (!config.options['pasPassword'])
config.options['pasPassword'] = '';
merge(config.optionsDesc,{
pasPassword: "Test password"
});
*/
//}}}
/***
Server adaptor for talking to RSS 2.0 files
based on FileAdaptor
***/
//{{{
function RSSAdaptor()
{
this.host = null;
this.store = null;
this.context = null;
return this;
}
RSSAdaptor.NotLoadedError = "RSS file has not been loaded";
RSSAdaptor.serverType = 'RSS';
// Use the line below instead of the line above if you want to override the local file adaptor
// RSSAdaptor.serverType = 'file';
// Open the specified host/server
// Return true if successful, error string if not
RSSAdaptor.prototype.openHost = function(host,context,userParams,callback)
{
this.host = host;
if(!context)
context = {};
context.adaptor = this;
context.callback = callback;
context.userParams = userParams;
var ret = loadRemoteFile(host,RSSAdaptor.openHostCallback,context);
return typeof(ret) == "string" ? ret : true;
};
RSSAdaptor.openHostCallback = function(status,context,responseText,url,xhr)
{
var adaptor = context.adaptor;
context.status = status;
if(!status) {
context.statusText = "Error reading file: " + xhr.statusText;
} else {
// CHANGE this bit to store RSS file appropriately (as part of the adaptor?) - DONE
// We're just storing the plain text rather than bring XML into it
adaptor.store = responseText;
// OLD CODE
// Load the content into a TiddlyWiki() object
// adaptor.store = new TiddlyWiki();
// if(!adaptor.store.importTiddlyWiki(responseText))
// context.statusText = config.messages.invalidFileError.format([url]);
}
context.callback(context,context.userParams);
};
// Gets the list of workspaces on a given server
// Sets context.workspaces, which is a list of workspaces
// Returns true if successful, error string if not (or it should)
// Default for RSS file as we don't have a workspace
RSSAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
{
if(!context)
context = {};
context.workspaces = [{title:"(default)"}];
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Open the specified workspace
// Returns true if successful, error string if not (or it should)
// Trivial in the case of RSS file where we don't have a workspace
RSSAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
{
if(!context)
context = {};
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Gets the list of tiddlers within a given workspace
// Returns true if successful, error string if not (or it should)
// Sets context.tiddlers, which is an array of tiddlers
// Set these variables if possible:
// title: tiddler.title, modified: tiddler.modified, modifier: tiddler.modifier, text: tiddler.text, tags: tiddler.tags, size: tiddler.text
// For RSS each item is a tiddler
RSSAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
{
if(!this.store)
return RSSAdaptor.NotLoadedError;
if(!context)
context = {};
context.tiddlers = [];
// First thing to do is strip out any \r characters in the file, as TiddlyWiki doesn't deal with them
this.store = this.store.replace(/\r+/mg,"");
// regex_item matches on the items
var regex_item = /<item>(.|\n)*?<\/item>/mg;
// regex_title matches for the titles
var regex_title = /<title>(.|\n)*?<\/title>/mg;
var regex_guid = /<guid>(.|\n)*?<\/guid>/mg;
var regex_desc = /<description>(.|\n)*?<\/description>/mg;
var regex_category = /<category>(.|\n)*?<\/category>/mg;
var regex_link = /<link>(\S|\n)*?<\/link>/mg;
var regex_pubDate = /<pubDate>(.|\n)*?<\/pubDate>/mg;
var regex_author = /<author>(.|\n)*?<\/author>/mg;
var item_match = this.store.match(regex_item);
// check for filter and then implement tag filter if of the form [tag[public stuff]]
// filter syntax: [tag[tag1 tag2 ...]]
// tags in the same set of brackets are all compulsory
// TO-DO: support bracketed list, where multi-word tags are of the form [two words]
if (filter) {
var filter_regex = /\[(\w+)\[([ \w]+)\]\]/;
var filter_match = filter_regex.exec(filter);
if (filter_match) {
// filter_match[2] is a space-seperated string of the tags to match on
var tags_to_match = filter_match[1]=="tag" ? filter_match[2].split(" ") : null;
} else {
displayMessage("no match: check regex in filter");
}
}
for (var i=0;i<item_match.length;i++) {
// create a new Tiddler in context.tiddlers with the finished item object
// grab a title
item = {};
var title = item_match[i].match(regex_title);
if (title)
item.title = title[0].replace(/^<title>|<\/title>$/mg,"");
else {
// something went wrong grabbing the title, grab the guid instead
title = item_match[i].match(regex_guid);
displayMessage("problem with getting title: " + item_match[i])
if (title)
item.title = title[0].replace(/^<guid>|<\/guid>$/mg,"");
else {
item.title = new Date();
displayMessage("problem with getting title AND guid: " + item_match[i]);
}
}
// This line makes sure any html-encoding in the title is reversed
item.title = item.title.htmlDecode();
// There is a problem with the title, which is that it is not formatted, so characters like ' are not converted at render time
// renderHtmlText() extends String and sorts out the problem
item.title = item.title.renderHtmlText();
// grab a description
desc = item_match[i].match(regex_desc);
if (desc) item.text = desc[0].replace(/^<description>|<\/description>$/mg,"");
else {
item.text = "empty, something seriously wrong with this item";
displayMessage("description empty for item: " + item.title);
}
var t = new Tiddler(item.title);
t.text = "<html>" + item.text.htmlDecode().renderHtmlText() + "</html>";
// grab the categories
category = item_match[i].match(regex_category);
if (category) {
item.categories = [];
for (var j=0;j<category.length;j++) {
item.categories[j] = category[j].replace(/^<category>|<\/category>$/mg,"");
}
t.tags = item.categories;
} else {
displayMessage("no tags for item: " + item.title);
}
// grab the link and put it in a custom field (assumes this is sensible)
// regex_link assumes you can never have whitespace in a link
link = item_match[i].match(regex_link);
if (link) item.link = link[0].replace(/^<link>|<\/link>$/mg,"");
else {
item.link = "#";
displayMessage("link empty for item: " + item.title);
}
t.fields["link to original"] = item.link;
// grab date created
pubDate = item_match[i].match(regex_pubDate);
if (pubDate) {
pubDate = pubDate[0].replace(/^<pubDate>|<\/pubDate>$/mg,"");
// TO-DO: does this work on Windows FF and IE??
item.pubDate = new Date(pubDate);
} else {
item.pubDate = new Date();
displayMessage("pubDate empty for item: " + item.title);
}
t.created = item.pubDate;
// grab item author
author = item_match[i].match(regex_author);
if (author) {
author = author[0].replace(/^<author>|<\/author>$/mg,"");
// TO-DO: does this work on Windows FF and IE??
item.author = author;
} else {
item.author = "unknown";
displayMessage("author empty for item: " + item.title);
}
t.modifier = item.author;
// check to see that we have a filter to use
if (filter_match) {
if(t.isTaggedAllOf(tags_to_match)) {
context.tiddlers.push(t);
}
} else {
// with no filter, we just add all the tiddlers
context.tiddlers.push(t);
}
}
context.status = true;
// Set this.context so that we can refer to the tiddler list even if it is not passed on to us
this.context = context;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// QUERY: what actually calls this and does it always pass in a real tiddler?
RSSAdaptor.prototype.generateTiddlerInfo = function(tiddler)
{
var info = {};
info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
return info;
};
// Retrieves a tiddler from a given workspace on a given server
// Sets context.tiddler to the requested tiddler
// Context object passed in from importTiddlers is empty so we use this.context
// Returns true if successful, error string if not (or it should)
RSSAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
{
if(!this.store)
return RSSAdaptor.NotLoadedError;
if(!context)
context = {};
// Retrieve the tiddler from the this.context.tiddlers array
for (var i=0; i<this.context.tiddlers.length; i++) {
if (this.context.tiddlers[i].title == title) {
context.tiddler = this.context.tiddlers[i];
}
}
// NOTE: this doesn't add the filter field - is that ok? Probably not...
if(context.tiddler) {
context.tiddler.fields['server.type'] = RSSAdaptor.serverType;
context.tiddler.fields['server.host'] = this.host;
context.tiddler.fields['server.page.revision'] = context.tiddler.modified.convertToYYYYMMDDHHMM();
context.status = true;
} else {
context.status = false;
context.statusText = "error retrieving tiddler: " + title;
return context.statusText;
}
if(context.allowSynchronous) {
context.isSynchronous = true;
callback(context,userParams);
} else {
window.setTimeout(function() {callback(context,userParams);},10);
}
return true;
};
RSSAdaptor.prototype.close = function()
{
delete this.store;
this.store = null;
};
config.adaptors[RSSAdaptor.serverType] = RSSAdaptor;
// Hack to override the importTiddlers local file behaviour
config.macros.importTiddlers.onBrowseChange = function(e)
{
var wizard = new Wizard(this);
var fileInput = wizard.getElement("txtPath");
fileInput.value = "file://" + this.value;
var serverType = wizard.getElement("selTypes");
if(serverType.value != "RSS") {
serverType.value = "file";
}
return false;
};
// renderHtmlText puts a string through the browser render process and then extracts the text
// useful to turn HTML entities into literals such as ' to '
String.prototype.renderHtmlText = function() {
var e = createTiddlyElement(document.body,"div");
e.innerHTML = this;
var text = getPlainText(e);
removeNode(e);
return text;
};
// Test if a tiddler carries any of an array of tags
// Takes an array of tags
// Returns true if there is a match, false if not
Tiddler.prototype.isTaggedAnyOf = function(tag_array)
{
if (tag_array) {
// get a string of this tiddler's tags
var this_tag_list = this.getTags();
// spilt that into an array
var this_tag_array = this_tag_list.split(" ");
// check that all the members of tag_array are contained in this_tag_array
for (var i=0; i<this_tag_array.length; i++) {
for (var j=0; j<tag_array.length; j++) {
if (this_tag_array[i] == tag_array[j]) {
return true;
}
}
}
// if we get to this point, we've not had any matches
return false;
} else {
return false;
}
};
// Test if a tiddler carries all of an array of tags
// Takes an array of tags
// Returns true if all match, false if not
Tiddler.prototype.isTaggedAllOf = function(tag_array)
{
if (tag_array) {
// get a string of this tiddler's tags
var this_tag_list = this.getTags();
// spilt that into an array
var this_tag_array = this_tag_list.split(" ");
// check whether any of the members of tag_array are not contained in this_tag_array
for (var i=0; i<tag_array.length; i++) {
// tag_match is a flag to signal whether we've had a match for a compulsory tag
var tag_match = false;
for (var j=0; j<this_tag_array.length; j++) {
if (tag_array[i] == this_tag_array[j]) {
tag_match = true;
break;
}
}
// if tag_match is still false after we've looked through the tiddler's tags,
// there is a failed match in the compulsory list so we can return false
if (tag_match == false) {
return false;
}
}
// now we've looked through the compulsory tags, return true
// this is valid because we would have returned false by this point anyway if
// there had been no match
return true;
} else {
return false;
}
};
//}}}
/***
|Title|RSSAdaptor|
|Summary|Server adaptor for talking to RSS 2.0 files|
|Description|Based on FileAdaptor|
|Version of product it works with|2.2.5|
|Version of component|1.0|
|Explanation of how it can be used and modified|This supports the server adaptor interface, a description of which can be found at: http://tiddlywiki.com/#ServerAdaptorMechanism|
|Examples where it can be seen working|The RSSAdator is used in this RSS Reader: (link to come)|
***/
//{{{
function RSSAdaptor()
{
this.host = null;
this.store = null;
this.context = null;
return this;
}
RSSAdaptor.NotLoadedError = "RSS file has not been loaded";
RSSAdaptor.serverType = 'RSS';
// Use the line below instead of the line above if you want to override the local file adaptor
// RSSAdaptor.serverType = 'file';
// Open the specified host/server
// Return true if successful, error string if not
RSSAdaptor.prototype.openHost = function(host,context,userParams,callback)
{
this.host = host;
if(!context)
context = {};
context.adaptor = this;
context.callback = callback;
context.userParams = userParams;
var ret = loadRemoteFile(host,RSSAdaptor.openHostCallback,context);
return typeof(ret) == "string" ? ret : true;
};
RSSAdaptor.openHostCallback = function(status,context,responseText,url,xhr)
{
var adaptor = context.adaptor;
context.status = status;
if(!status) {
context.statusText = "Error reading file: " + xhr.statusText;
} else {
// CHANGE this bit to store RSS file appropriately (as part of the adaptor?) - DONE
// We're just storing the plain text rather than bring XML into it
adaptor.store = responseText;
// OLD CODE
// Load the content into a TiddlyWiki() object
// adaptor.store = new TiddlyWiki();
// if(!adaptor.store.importTiddlyWiki(responseText))
// context.statusText = config.messages.invalidFileError.format([url]);
}
context.callback(context,context.userParams);
};
// Gets the list of workspaces on a given server
// Sets context.workspaces, which is a list of workspaces
// Returns true if successful, error string if not (or it should)
// Default for RSS file as we don't have a workspace
RSSAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
{
if(!context)
context = {};
context.workspaces = [{title:"(default)"}];
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Open the specified workspace
// Returns true if successful, error string if not (or it should)
// Trivial in the case of RSS file where we don't have a workspace
RSSAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
{
if(!context)
context = {};
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Gets the list of tiddlers within a given workspace
// Returns true if successful, error string if not (or it should)
// Sets context.tiddlers, which is an array of tiddlers
// Set these variables if possible:
// title: tiddler.title, modified: tiddler.modified, modifier: tiddler.modifier, text: tiddler.text, tags: tiddler.tags, size: tiddler.text
// For RSS each item is a tiddler
RSSAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
{
if(!this.store)
return RSSAdaptor.NotLoadedError;
if(!context)
context = {};
context.tiddlers = [];
// First thing to do is strip out any \r characters in the file, as TiddlyWiki doesn't deal with them
this.store = this.store.replace(/\r+/mg,"");
// regex_item matches on the items
var regex_item = /<item>(.|\n)*?<\/item>/mg;
// regex_title matches for the titles
var regex_title = /<title>(.|\n)*?<\/title>/mg;
var regex_guid = /<guid>(.|\n)*?<\/guid>/mg;
var regex_desc = /<description>(.|\n)*?<\/description>/mg;
var regex_category = /<category>(.|\n)*?<\/category>/mg;
var regex_link = /<link>(\S|\n)*?<\/link>/mg;
var regex_pubDate = /<pubDate>(.|\n)*?<\/pubDate>/mg;
var item_match = this.store.match(regex_item);
// check for filter and then implement tag filter if of the form [tag[public stuff]]
// filter syntax: [tag[tag1 tag2 ...]]
// tags in the same set of brackets are all compulsory
// TO-DO: support bracketed list, where multi-word tags are of the form [two words]
if (filter) {
var filter_regex = /\[(\w+)\[([ \w]+)\]\]/;
var filter_match = filter_regex.exec(filter);
if (filter_match) {
// filter_match[2] is a space-seperated string of the tags to match on
var tags_to_match = filter_match[1]=="tag" ? filter_match[2].split(" ") : null;
} else {
displayMessage("no match: check regex in filter");
}
}
for (var i=0;i<item_match.length;i++) {
// create a new Tiddler in context.tiddlers with the finished item object
// grab a title
var item = {};
var title = item_match[i].match(regex_title);
if (title)
item.title = title[0].replace(/^<title>|<\/title>$/mg,"");
else {
// something went wrong grabbing the title, grab the guid instead
title = item_match[i].match(regex_guid);
displayMessage("problem with getting title: " + item_match[i])
if (title)
item.title = title[0].replace(/^<guid>|<\/guid>$/mg,"");
else {
item.title = new Date();
displayMessage("problem with getting title AND guid: " + item_match[i]);
}
}
// This line makes sure any html-encoding in the title is reversed
item.title = item.title.htmlDecode();
// There is a problem with the title, which is that it is not formatted, so characters like ' are not converted at render time
// renderHtmlText() extends String and sorts out the problem
item.title = item.title.renderHtmlText();
// grab a description
desc = item_match[i].match(regex_desc);
if (desc) item.text = desc[0].replace(/^<description>|<\/description>$/mg,"");
else {
item.text = "empty, something seriously wrong with this item";
// displayMessage("description empty for item: " + item.title);
}
var t = new Tiddler(item.title);
t.text = "<html>" + item.text.htmlDecode().renderHtmlText() + "</html>";
// grab the categories
category = item_match[i].match(regex_category);
if (category) {
item.categories = [];
for (var j=0;j<category.length;j++) {
item.categories[j] = category[j].replace(/^<category>|<\/category>$/mg,"");
}
t.tags = item.categories;
} else {
// displayMessage("no tags for item: " + item.title);
}
// grab the link and put it in a custom field (assumes this is sensible)
// regex_link assumes you can never have whitespace in a link
link = item_match[i].match(regex_link);
if (link) item.link = link[0].replace(/^<link>|<\/link>$/mg,"");
else {
item.link = "#";
// displayMessage("link empty for item: " + item.title);
}
t.fields["link to original"] = item.link;
// grab date created
pubDate = item_match[i].match(regex_pubDate);
if (pubDate) {
pubDate = pubDate[0].replace(/^<pubDate>|<\/pubDate>$/mg,"");
// TO-DO: does this work on Windows FF and IE??
item.pubDate = new Date(pubDate);
} else {
item.pubDate = new Date();
// displayMessage("pubDate empty for item: " + item.title);
}
t.created = item.pubDate;
// check to see that we have a filter to use
if (filter_match) {
if(t.isTaggedAllOf(tags_to_match)) {
context.tiddlers.push(t);
}
} else {
// with no filter, we just add all the tiddlers
context.tiddlers.push(t);
}
}
context.status = true;
// Set this.context so that we can refer to the tiddler list even if it is not passed on to us
this.context = context;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// QUERY: what actually calls this and does it always pass in a real tiddler?
RSSAdaptor.prototype.generateTiddlerInfo = function(tiddler)
{
var info = {};
info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
return info;
};
// Retrieves a tiddler from a given workspace on a given server
// Sets context.tiddler to the requested tiddler
// Context object passed in from importTiddlers is empty so we use this.context
// Returns true if successful, error string if not (or it should)
RSSAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
{
if(!this.store)
return RSSAdaptor.NotLoadedError;
if(!context)
context = {};
// Retrieve the tiddler from the this.context.tiddlers array
for (var i=0; i<this.context.tiddlers.length; i++) {
if (this.context.tiddlers[i].title == title) {
context.tiddler = this.context.tiddlers[i];
}
}
// NOTE: this doesn't add the filter field - is that ok? Probably not...
if(context.tiddler) {
context.tiddler.fields['server.type'] = RSSAdaptor.serverType;
context.tiddler.fields['server.host'] = this.host;
context.tiddler.fields['server.page.revision'] = context.tiddler.modified.convertToYYYYMMDDHHMM();
context.status = true;
} else {
context.status = false;
context.statusText = "error retrieving tiddler: " + title;
return context.statusText;
}
if(context.allowSynchronous) {
context.isSynchronous = true;
callback(context,userParams);
} else {
window.setTimeout(function() {callback(context,userParams);},10);
}
return true;
};
RSSAdaptor.prototype.close = function()
{
delete this.store;
this.store = null;
};
config.adaptors[RSSAdaptor.serverType] = RSSAdaptor;
// Hack to override the importTiddlers local file behaviour
config.macros.importTiddlers.onBrowseChange = function(e)
{
var wizard = new Wizard(this);
var fileInput = wizard.getElement("txtPath");
fileInput.value = "file://" + this.value;
var serverType = wizard.getElement("selTypes");
if(serverType.value != "RSS") {
serverType.value = "file";
}
return false;
};
// renderHtmlText puts a string through the browser render process and then extracts the text
// useful to turn HTML entities into literals such as ' to '
String.prototype.renderHtmlText = function() {
var e = createTiddlyElement(document.body,"div");
e.innerHTML = this;
var text = getPlainText(e);
removeNode(e);
return text;
};
// Test if a tiddler carries any of an array of tags
// Takes an array of tags
// Returns true if there is a match, false if not
Tiddler.prototype.isTaggedAnyOf = function(tag_array)
{
if (tag_array) {
// get a string of this tiddler's tags
var this_tag_list = this.getTags();
// spilt that into an array
var this_tag_array = this_tag_list.split(" ");
// check that all the members of tag_array are contained in this_tag_array
for (var i=0; i<this_tag_array.length; i++) {
for (var j=0; j<tag_array.length; j++) {
if (this_tag_array[i] == tag_array[j]) {
return true;
}
}
}
// if we get to this point, we've not had any matches
return false;
} else {
return false;
}
};
// Test if a tiddler carries all of an array of tags
// Takes an array of tags
// Returns true if all match, false if not
Tiddler.prototype.isTaggedAllOf = function(tag_array)
{
if (tag_array) {
// get a string of this tiddler's tags
var this_tag_list = this.getTags();
// spilt that into an array
var this_tag_array = this_tag_list.split(" ");
// check whether any of the members of tag_array are not contained in this_tag_array
for (var i=0; i<tag_array.length; i++) {
// tag_match is a flag to signal whether we've had a match for a compulsory tag
var tag_match = false;
for (var j=0; j<this_tag_array.length; j++) {
if (tag_array[i] == this_tag_array[j]) {
tag_match = true;
break;
}
}
// if tag_match is still false after we've looked through the tiddler's tags,
// there is a failed match in the compulsory list so we can return false
if (tag_match == false) {
return false;
}
}
// now we've looked through the compulsory tags, return true
// this is valid because we would have returned false by this point anyway if
// there had been no match
return true;
} else {
return false;
}
};
//}}}
//{{{
// Override built-in saveToRss to include modifier field
Tiddler.prototype.saveToRss = function(url)
{
var s = [];
s.push("<item>");
s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
for(var t=0; t<this.tags.length; t++)
s.push("<category>" + this.tags[t] + "</category>");
s.push("<link>" + url + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
s.push("<author>" + this.modifier + "</author>");
s.push("</item>");
return s.join("\n");
};
//}}}
The key to ~TiddlyChatter is your subscription to someone else's "~ChatterStream", which you can manage from [[TiddlyChatter control panel]]. Click "new subscription" and put in the URL of a ~TiddlyWiki which is publishing a stream. You have been set up with one subscription called ChatterFeed to some test content on the web.
''You need to make sure that the "Tag for import" field in ImportWorkspacePlugin is set to "publish", or the menu button to get your updates won't apear.'' At 09/10/07, it is still set as a cookie...
Currently, ~TiddlyChatter supports subscribing to one person's stream.
Hitting "Go chatter!" in the MainMenu gets your updates. When you save your ~TiddlyWiki, your own updates are published into your stream.
<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">> [[UploadOptions]]
Test workbook - to work with other TiddlyChatter-enabled workbooks
/*{{{*/
#channelBox {background:#BBBB33;}
#newChannelBox {background:#33BBBB;}
#subscriptionBox {background:#CCCC22;}
#newSubscriptionBox {background:#22CCCC;}
.publishing {
background-color: #EEEEEE;
border: 1px solid #EEEEEE;
float: right;
margin: 0.5em;
font-size: 0.9em;
padding: 0.25em;
}
.selected .publishing {
background-color: #CCCCCC;
border: 1px solid #999999;
}
.publishing .button {
border: medium none;
}
.publishing ul {
list-style-image: none;
list-style-position: outside;
list-style-type: none;
margin: 0.25em;
padding: 0pt;
}
tr.tiddlyChatterIncomingRowUnread {
background-color: #C44;
}
/*}}}*/
[[StyleSheetTiddlersBar]]
/*{{{*/
h1,h2,h3,h4,h5 {
color: [[ColorPalette::Background]];
background: [[ColorPalette::SecondaryDark]];
border-bottom: #000 4px solid;
border-right: #000 4px solid;
border-top: #000 1px solid;
border-left: #000 1px solid;
padding-bottom: 3px;
}
.date-prog {
background: [[ColorPalette::SecondaryMid]];
color: [[ColorPalette::Foreground]];
border: 1px solid #800;
padding: 5px}
.ctsent {
background: [[ColorPalette::PrimaryPale]];
color: [[ColorPalette::Foreground]];
border: 1px solid #800;
padding: 5px}
.sliderPanel a:active {
background: #900;
}
body {
background: [[ColorPalette::SecondaryPale]];
color: [[ColorPalette::Foreground]];
}
.header {border-bottom: 1px solid #800;}
.headerShadow {
position: relative;
padding: 1.5em 0em 1em 1em;
left: -1px;
top: -1px;
}
.headerForeground {
position: absolute;
padding: 1.5em 0em 1em 1em;
left: 0px;
top: 0px;
}
.searchBar {float: right; font-size: 0.9em;}
.searchBar .button {border: none; color: #ccc;}
.searchBar .button:hover {border: none; color: #eee;}
.searchBar input{
border: 1px inset #1d65bc; background: #dbdee3;
}
.searchBar input:focus {
border: 1px inset #3371a3; background: #fff;
}
.tiddler {
background: [[ColorPalette::Background]];
border: solid 1px #000;
margin-bottom: .75em;
margin-right: 1.25 em;
}
.tiddler .toolbar {
visibility: visible;
margin-top: 10px;
}
.tiddler .toggletags {
font-size: .8em;
}
.tiddler .toolbar .button {
background: #aaa;
color: [[ColorPalette::Background]];
}
.tagging {
margin: 0.25em 0.25em 0.25em 0;
float: none;
display: block;
position: relative;
}
.isTag .tagging {
display: block;
}
.tagging{
font-size: 0.9em;
padding: 0.25em;
}
.tagging ul, .tagged ul {
list-style: none; margin: 0.25em;
padding: 0;
}
.viewer hr {border:0; border-top: solid 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
*/}}}/*
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, .headerShadow, .headerForeground, #hoverMenu, {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*{{{*/
#tiddlerDisplay {border: 1px solid #CCC; -moz-border-radius: 0.5em}
.tabCloseButton {padding : 0 2px 0 2px ; margin : 0 0 0 4px; font-size: 0.75em; vertical-align: top;}
.tabUnselected .tabCloseButton {border : 1px solid #BBB; background-color : #DDD; color:#888;}
.tabUnselected .tabCloseButton:hover {border : 1px solid #600; background-color : #D87443; color:#FFF;}
.tabSelected .tabCloseButton {border : 1px solid #600; background-color : #D87443; color:#FFF}
.tabCloseButton:hover {color:#A00; background-color:#F0F0F0; border : 1px solid #000;}
#tiddlersBar {padding : 1em 0.5em 0 0.5em}
#tiddlersBar .button {border: 0; color:#444}
#tiddlerDisplay .tiddler {padding-top : 0.1em}
#tiddlersBar .tab {padding : 0.4em 0.5em 0.2em 0.5em; -moz-border-radius-topleft:0.5em;-moz-border-radius-topright:0.5em}
#tiddlersBar .tabSelected {background-color :#FFF; color:#CC6633; border-top: 1px solid #000; border-right: 1px solid #000; border-bottom: 1px solid #FFF; border-left:1px solid #000;}
#tiddlersBar .tabUnselected {background-color :#EEE; border-bottom: 1px solid #000; padding : 0.4em 0.5em 0.1em 0.5em;}
#tiddlersBar .tabUnselected .button {color: #AAA; font-weight: bold; font-style: italic; padding : 0.4em 0.5em 0.3em 0.5em;}
#tiddlersBar .tabSelected .button {color:#CC6633;font-weight:bold;}
/*}}}*/
/*{{{*/
#channelBox {background:#BBBB33;}
#newChannelBox {background:#33BBBB;}
#subscriptionBox {background:#CCCC22;}
#newSubscriptionBox {background:#22CCCC;}
.publishing, .unread {
background-color: #EEEEEE;
border: 1px solid #EEEEEE;
float: right;
margin: 0.5em;
font-size: 0.9em;
padding: 0.25em;
}
.selected .publishing, .selected .unread {
background-color: #CCCCCC;
border: 1px solid #999999;
}
.publishing .button, .unread .button {
border: medium none;
}
.publishing ul {
list-style-image: none;
list-style-position: outside;
list-style-type: none;
margin: 0.25em;
padding: 0pt;
}
tr.tiddlyChatterIncomingRowUnread {
background-color: #C44;
}
/*}}}*/
<html>Well done for setting up TiddlyChatter. This is some test content on http://tiddlychatter.tiddlyspot.com - feel free to add some notes...</html>
/***
|Name|TaggedTemplateTweak|
|Source|http://www.TiddlyTools.com/#TaggedTemplateTweak|
|Version|1.1.0|
|Author|Eric Shulman - ELS Design Studios|
|License|http://www.TiddlyTools.com/#LegalStatements <<br>>and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|~CoreVersion|2.1|
|Type|plugin|
|Requires||
|Overrides|Story.prototype.chooseTemplateForTiddler()|
|Description|use alternative ViewTemplate/EditTemplate for tiddler's tagged with specific tag values|
The core function, "story.chooseTemplateForTiddler(title,template)" is essentially a "pass-thru" that returns the same template it was given, and is provided by the core so that plugins can customize the template selection logic to select alternative templates, based on whatever programmatic criteria is appropriate. This tweak extends story.chooseTemplateForTiddler() so that ''whenever a tiddler is marked with a specific tag value, it can be viewed and/or edited using alternatives to the standard tiddler templates.''
!!!!!Usage
<<<
Each alternative template is associated with a specific tiddler tag value by using that tag value as a prefix added to the standard TiddlyWiki template titles, [[ViewTemplate]] and [[EditTemplate]].
For example, any tiddlers that are tagged with ''<<tag media>>'' will look for alternative templates named [[mediaViewTemplate]] and [[mediaEditTemplate]]. Additionally, in order to find templates that have proper WikiWord tiddler titles (e.g., [[MediaViewTemplate]] and [[MediaEditTemplate]]), the plugin will also attempt to use a capitalized form of the tag value (e.g., ''Media'') as a prefix. //This capitalization is for comparison purposes only and will not alter the actual tag values that are stored in the tiddler.//
If no matching alternative template can be found by using //any// of the tiddler's tags (either "as-is" or capitalized), the tiddler defaults to using the appropriate standard [[ViewTemplate]] or [[EditTemplate]] definition.
''To add your own custom templates:''
>First, decide upon a suitable tag keyword to uniquely identify your custom templates and create custom view and/or edit templates using that keyword as a prefix (e.g., "KeywordViewTemplate" and "KeywordEditTemplate"). Then, simply create a tiddler and tag it with your chosen keyword... that's it! As long as the tiddler is tagged with your keyword, it will be displayed using the corresponding alternative templates. If you remove the tag or rename/delete the alternative templates, the tiddler will revert to using the standard viewing and editing templates.
<<<
!!!!!Examples
<<<
|Sample tiddler| tag | view template | edit template |
|[[MediaSample - QuickTime]]| <<tag media>> | [[MediaViewTemplate]] | [[MediaEditTemplate]] |
|[[MediaSample - Windows]]| <<tag media>> | [[MediaViewTemplate]] | [[MediaEditTemplate]] |
|[[CDSample]]| <<tag CD>> | [[CDViewTemplate]] | [[CDEditTemplate]] |
|<<newTiddler label:"create new task..." title:SampleTask tag:task text:"Type some text and then press DONE to view the task controls">> | <<tag task>> | [[TaskViewTemplate]] | [[EditTemplate]] |
//(note: if these samples are not present in your document, please visit// http://www.TiddlyTools.com/ //to view these sample tiddlers on-line)//
<<<
!!!!!Installation
<<<
import (or copy/paste) the following tiddlers into your document:
[[TaggedTemplateTweak]]
<<<
!!!!!Revision History
<<<
''2007.06.23 [1.1.0]'' re-written to use automatic 'tag prefix' search instead of hard coded check for each tag. Allows new custom tags to be used without requiring code changes to this plugin.
''2007.06.11 [1.0.0]'' initial release
<<<
!!!!!Credits
<<<
This feature was developed by Eric L Shulman / ELS Design Studios
<<<
!!!!!Code
***/
//{{{
version.extensions.taggedTemplate= {major: 1, minor: 1, revision: 0, date: new Date(2007,6,18)};
Story.prototype.taggedTemplate_chooseTemplateForTiddler = Story.prototype.chooseTemplateForTiddler
Story.prototype.chooseTemplateForTiddler = function(title,template)
{
// get default template from core
var template=this.taggedTemplate_chooseTemplateForTiddler.apply(this,arguments);
// if the tiddler to be rendered doesn't exist yet, just return core result
var tiddler=store.getTiddler(title); if (!tiddler) return template;
// look for template whose prefix matches a tag on this tiddler
for (t=0; t<tiddler.tags.length; t++) {
var tag=tiddler.tags[t];
if (store.tiddlerExists(tag+template)) { template=tag+template; break; }
// try capitalized tag (to match WikiWord template titles)
var cap=tag.substr(0,1).toUpperCase()+tag.substr(1);
if (store.tiddlerExists(cap+template)) { template=cap+template; break; }
}
return template;
}
//}}}
|Title|TiddlyChatter|
|Summary|Opt-in, decentralized collaboration|
|Description|This component spans several javascript files and depends on a number of other plugins|
|Version of product it works with|2.2.5|
|Version of component|0.5|
|Image illustrating it working||
|Explanation of how it can be used and modified|see below for necessary plugins|
|Examples where it can be seen working|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/verticals/TiddlyChatter/examples/tiddlychatter0-5.html|
|Links to reviews and discussion|http://jayfresh.wordpress.com/category/tiddlychatter/ http://groups.google.com/group/TiddlyWikiDev/browse_thread/thread/ac0532d241e2cb1a http://groups.google.com/group/TiddlyWikiDev/browse_thread/thread/f6e11d6c56d26817/5c204eaed61855f2|
!!The following plugins are built for TiddlyChatter:
|[[lv]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/verticals/TiddlyChatter/js/lv.js]] |
|[[tiddlyChatterSetup code]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/TiddlyChatter/js/tiddlyChatterSetup_code.js]] |
|[[tiddlyChatterPublishing]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/TiddlyChatter/js/tiddlyChatterPublishing.js]] |
|[[applyTiddlyChatterStyles]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/verticals/TiddlyChatter/js/applyTiddlyChatterStyles.js]] |
!!The following plugins are modified and patch discussions are ongoing with plugin authors:
|NotesPlugin |by SaqImtiaz |[[original source|http://tw.lewcid.org/#NotesPlugin]] |[[modified source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/TiddlyChatter/NotesPlugin_JRL.js]] |
|ImportWorkspacePlugin |by Martin Budden |[[original source|http://svn.tiddlywiki.org/Trunk/contributors/MartinBudden/adaptors/ImportWorkspacePlugin.js]] | [[modified source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/TiddlyChatter/ImportWorkspacePlugin_JRL.js]] |
|[[Core patches]] |by various |[[original source dir|http://svn.tiddlywiki.org/Trunk/core/js]] | |
!!The following plugins are used without modification:
|[[RSSAdaptor|RSSAdaptor]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/RSSAdaptor/js/rssadaptor.js]] |
|[[RSSRender|RSSrender plugin]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/RSSAdaptor/js/RSSRender.js]] |
| [[File adaptor filter plugin]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/verticals/TiddlyChatter/js/File_adaptor_filter_plugin.js]] |
|TaggedTemplateTweak |by EricShulman |[[source|http://www.TiddlyTools.com/#TaggedTemplateTweak]] |
|[[stickyOptionsPlugin]] |by SaqImtiaz |[[source|http://tw.lewcid.org/#StickyOptionsPlugin]] |
|[[Unread]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/Unread/js/Unread.js]] |
!!The following tiddlers are not plugins, but are part of the config:
[[publicViewTemplate]]
StyleSheetTiddlyChatter
ChatterFeed
!!The following tiddlers provide additional information or run plugins:
[[TiddlyChatter]]
[[Welcome to TiddlyChatter]]
[[Setting up TiddlyChatter]]
[[How TiddlyChatter works - an example scenario]]
[[TiddlyChatterDocumentation]]
|''Type:''|file|
|''URL:''|http://tiddlychatter.tiddlyspot.com/index.html|
|''Workspace:''||
|''TiddlerFilter:''|[tag[TiddlyChatterPackage]]|
/***
Contains the stuff you need to use Tiddlyspot
Note you must also have UploadPlugin installed
***/
//{{{
// edit this if you are migrating sites or retrofitting an existing TW
config.tiddlyspotSiteId = 'tiddlychatter';
// make it so you can by default see edit controls via http
config.options.chkHttpReadOnly = false;
window.readOnly = false; // make sure of it (for tw 2.2)
// disable autosave in d3
if (window.location.protocol != "file:")
config.options.chkGTDLazyAutoSave = false;
// tweak shadow tiddlers to add upload button, password entry box etc
with (config.shadowTiddlers) {
SiteUrl = 'http://'+config.tiddlyspotSiteId+'.tiddlyspot.com';
SideBarOptions = SideBarOptions.replace(/(<<saveChanges>>)/,"$1<<tiddler TspotSidebar>>");
OptionsPanel = OptionsPanel.replace(/^/,"<<tiddler TspotOptions>>");
DefaultTiddlers = DefaultTiddlers.replace(/^/,"[[Welcome to Tiddlyspot]] ");
MainMenu = MainMenu.replace(/^/,"[[Welcome to Tiddlyspot]] ");
}
// create some shadow tiddler content
merge(config.shadowTiddlers,{
'Welcome to Tiddlyspot':[
"This document is a ~TiddlyWiki from tiddlyspot.com. A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //What now?// @@ Before you can save any changes, you need to enter your password in the form below. Then configure privacy and other site settings at your [[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]] (your control panel username is //" + config.tiddlyspotSiteId + "//).",
"<<tiddler TspotControls>>",
"See also GettingStarted.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working online// @@ You can edit this ~TiddlyWiki right now, and save your changes using the \"save to web\" button in the column on the right.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working offline// @@ A fully functioning copy of this ~TiddlyWiki can be saved onto your hard drive or USB stick. You can make changes and save them locally without being connected to the Internet. When you're ready to sync up again, just click \"upload\" and your ~TiddlyWiki will be saved back to tiddlyspot.com.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Help!// @@ Find out more about ~TiddlyWiki at [[TiddlyWiki.com|http://tiddlywiki.com]]. Also visit [[TiddlyWiki Guides|http://tiddlywikiguides.org]] for documentation on learning and using ~TiddlyWiki. New users are especially welcome on the [[TiddlyWiki mailing list|http://groups.google.com/group/TiddlyWiki]], which is an excellent place to ask questions and get help. If you have a tiddlyspot related problem email [[tiddlyspot support|mailto:support@tiddlyspot.com]].",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Enjoy :)// @@ We hope you like using your tiddlyspot.com site. Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions."
].join("\n"),
'TspotControls':[
"| tiddlyspot password:|<<option pasUploadPassword>>|",
"| site management:|<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">>//(requires tiddlyspot password)//<<br>>[[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]], [[download (go offline)|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download]]|",
"| links:|[[tiddlyspot.com|http://tiddlyspot.com/]], [[FAQs|http://faq.tiddlyspot.com/]], [[announcements|http://announce.tiddlyspot.com/]], [[blog|http://tiddlyspot.com/blog/]], email [[support|mailto:support@tiddlyspot.com]] & [[feedback|mailto:feedback@tiddlyspot.com]], [[donate|http://tiddlyspot.com/?page=donate]]|"
].join("\n"),
'TspotSidebar':[
"<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">><html><a href='http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download' class='button'>download</a></html>"
].join("\n"),
'TspotOptions':[
"tiddlyspot password:",
"<<option pasUploadPassword>>",
""
].join("\n")
});
//}}}
config.macros.unread = {};
config.macros.unread.handler = function(place,macroName,param,wikifier,paramString,tiddler) {
// check to see if tiddler has extended field "unread"
// if so, add button to click to change status to read
if (tiddler.fields["unread"]) {
var unread = tiddler.fields["unread"];
var label = unread == "true" ? "Mark as read" : "Mark as unread";
var caption = unread == "false" ? "Click to mark as read" : "Click to mark as unread";
var theUnreadBox = createTiddlyButton(place,label,caption);
theUnreadBox.onclick = config.macros.unread.markAsRead;
}
};
config.macros.unread.markAsRead = function() {
var DOMTiddler = story.findContainingTiddler(this);
var t = store.fetchTiddler(DOMTiddler.getAttribute("tiddler"));
t.fields["unread"] = t.fields["unread"] == "true" ? "false" : "true";
// store.saveTiddler(t.title,t.title,t.text,t.modifier,t.modified,t.tags,t.fields,false,t.created);
// story.refreshTiddler(DOMTiddler.getAttribute("tiddler"),DOMTiddler.getAttribute("template"),true);
story.refreshAllTiddlers();
// the line above seems rather heavy-handed... what's an efficient way to make another tiddler respond to a change in this one's fields?
// also, if I don't save t back to the store, does this have any consequences? Or is it happening automatically?
};
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 16/10/2007 01:20:08 | KenGirard | [[tiddlychatter0-5_two.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter0-5_two.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | | ok |
| 16/10/2007 07:22:50 | KenGirard | [[tiddlychatter0-5_two.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter0-5_two.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | | ok |
| 16/10/2007 07:24:43 | KenGirard | [[tiddlychatter0-5_two.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter0-5_two.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | | ok |
| 19/10/2007 18:43:23 | KenGirard | [[tiddlychatter0-5_two.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter0-5_two.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | | ok |
| 19/10/2007 18:49:10 | KenGirard | [[tiddlychatter0-5_two.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter0-5_two.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | | ok |
| 19/10/2007 20:08:19 | KenGirard | [[tiddlychatter.html|file:///C:/Documents%20and%20Settings/Dark%20Lady/My%20Documents/My%20Webs/No-Sin/wiki/tiddlychatter.html]] | [[store.php|http://no-sin.com/wiki/store.php]] | . | [[tiddlychatter.html | http://no-sin.com/wiki/tiddlychatter.html]] | |
UploadPlugin uses the following sequence for finding parameters :
#''macro'' parameters
#''Options'' saved in cookies
#''Plugin'' default values
!Options used by UploadPlugin
| Option | Value | Default |
|Upload Username: |<<option txtUploadUserName>>| |
|Upload Password: |<<option pasUploadPassword>>| |
|Url of the UploadService script: |<<option txtUploadStoreUrl urlInput>>| store.php |
|Relative Directory where to store the file: |<<option txtUploadDir urlInput>>| . (the script directory) |
|Filename of the uploaded file: |<<option txtUploadFilename urlInput>>| index.html |
|Directory to backup file on webserver^^(1)^^: |<<option txtUploadBackupDir urlInput>>| //null// (none/no backup) |
|Log in UploadLog |<<option chkUploadLog>> Trace Upload| true |
|Maximum of lines in UploadLog |<<option txtUploadLogMaxLine>>| 10 |
^^(1)^^No backup if Backup Directory is empty, the previous file will be overwritten. Use a '.' to backup in the script directory.
<<upload>> with these options.
!Upload Macro parameters
{{{
<<upload [storeUrl [toFilename [backupDir [uploadDir [username]]]]]>>
Optional positional parameters can be passed to overwrite UploadOptions.
}}}
!UploadToFile Macro Macro parameters
{{{
<<uploadTofile [filename [tiddlerTitle]]>>
tiddlerTitle, filename: if omitted the title of the current tiddler
<<uploadToFile allowedsites.txt allowedsites.txt>>
}}}
/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.1|
|''Date:''|Sep 15, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#UploadPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (#3125)|
|''Requires:''|PasswordOptionPlugin|
***/
//{{{
version.extensions.UploadPlugin = {
major: 4, minor: 1, revision: 1,
date: new Date("Sep 15, 2007"),
source: 'http://tiddlywiki.bidix.info/#UploadPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
coreVersion: '2.2.0 (#3125)'
};
//
// Environment
//
if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false; // true to activate both in Plugin and UploadService
//
// Upload Macro
//
config.macros.upload = {
// default values
defaultBackupDir: '', //no backup
defaultStoreScript: "store.php",
defaultToFilename: "index.html",
defaultUploadDir: ".",
authenticateUser: true // UploadService Authenticate User
};
config.macros.upload.label = {
promptOption: "Save and Upload this TiddlyWiki with UploadOptions",
promptParamMacro: "Save and Upload this TiddlyWiki in %0",
saveLabel: "save to web",
saveToDisk: "save to disk",
uploadLabel: "upload"
};
config.macros.upload.messages = {
noStoreUrl: "No store URL in parmeters or options",
usernameOrPasswordMissing: "Username or password missing"
};
config.macros.upload.handler = function(place,macroName,params) {
if (readOnly)
return;
var label;
if (document.location.toString().substr(0,4) == "http")
label = this.label.saveLabel;
else
label = this.label.uploadLabel;
var prompt;
if (params[0]) {
prompt = this.label.promptParamMacro.toString().format([this.destFile(params[0],
(params[1] ? params[1]:bidix.basename(window.location.toString())), params[3])]);
} else {
prompt = this.label.promptOption;
}
createTiddlyButton(place, label, prompt, function() {config.macros.upload.action(params);}, null, null, this.accessKey);
};
config.macros.upload.action = function(params)
{
// for missing macro parameter set value from options
var storeUrl = params[0] ? params[0] : config.options.txtUploadStoreUrl;
var toFilename = params[1] ? params[1] : config.options.txtUploadFilename;
var backupDir = params[2] ? params[2] : config.options.txtUploadBackupDir;
var uploadDir = params[3] ? params[3] : config.options.txtUploadDir;
var username = params[4] ? params[4] : config.options.txtUploadUserName;
var password = config.options.pasUploadPassword; // for security reason no password as macro parameter
// for still missing parameter set default value
if ((!storeUrl) && (document.location.toString().substr(0,4) == "http"))
storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
if (storeUrl.substr(0,4) != "http")
storeUrl = bidix.dirname(document.location.toString()) +'/'+ storeUrl;
if (!toFilename)
toFilename = bidix.basename(window.location.toString());
if (!toFilename)
toFilename = config.macros.upload.defaultToFilename;
if (!uploadDir)
uploadDir = config.macros.upload.defaultUploadDir;
if (!backupDir)
backupDir = config.macros.upload.defaultBackupDir;
// report error if still missing
if (!storeUrl) {
alert(config.macros.upload.messages.noStoreUrl);
clearMessage();
return false;
}
if (config.macros.upload.authenticateUser && (!username || !password)) {
alert(config.macros.upload.messages.usernameOrPasswordMissing);
clearMessage();
return false;
}
bidix.upload.uploadChanges(false,null,storeUrl, toFilename, uploadDir, backupDir, username, password);
return false;
};
config.macros.upload.destFile = function(storeUrl, toFilename, uploadDir)
{
if (!storeUrl)
return null;
var dest = bidix.dirname(storeUrl);
if (uploadDir && uploadDir != '.')
dest = dest + '/' + uploadDir;
dest = dest + '/' + toFilename;
return dest;
};
//
// uploadOptions Macro
//
config.macros.uploadOptions = {
handler: function(place,macroName,params) {
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
wizard.setValue("listWrapper",listWrapper);
this.refreshOptions(listWrapper,false);
var uploadCaption;
if (document.location.toString().substr(0,4) == "http")
uploadCaption = config.macros.upload.label.saveLabel;
else
uploadCaption = config.macros.upload.label.uploadLabel;
wizard.setButtons([
{caption: uploadCaption, tooltip: config.macros.upload.label.promptOption,
onClick: config.macros.upload.action},
{caption: this.cancelButton, tooltip: this.cancelButtonPrompt, onClick: this.onCancel}
]);
},
refreshOptions: function(listWrapper) {
var uploadOpts = [
"txtUploadUserName",
"pasUploadPassword",
"txtUploadStoreUrl",
"txtUploadDir",
"txtUploadFilename",
"txtUploadBackupDir",
"chkUploadLog",
"txtUploadLogMaxLine",
]
var opts = [];
for(i=0; i<uploadOpts.length; i++) {
var opt = {};
opts.push()
opt.option = "";
n = uploadOpts[i];
opt.name = n;
opt.lowlight = !config.optionsDesc[n];
opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
opts.push(opt);
}
var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
for(n=0; n<opts.length; n++) {
var type = opts[n].name.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create) {
h.create(opts[n].colElements['option'],type,opts[n].name,opts[n].name,"no");
}
}
},
onCancel: function(e)
{
backstage.switchTab(null);
return false;
},
wizardTitle: "Upload with options",
step1Title: "These options are saved in cookies in your browser",
step1Html: "<input type='hidden' name='markList'></input><br>",
cancelButton: "Cancel",
cancelButtonPrompt: "Cancel prompt",
listViewTemplate: {
columns: [
{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
{name: 'Option', field: 'option', title: "Option", type: 'String'},
{name: 'Name', field: 'name', title: "Name", type: 'String'}
],
rowClasses: [
{className: 'lowlight', field: 'lowlight'}
]}
}
//
// upload functions
//
if (!bidix.upload) bidix.upload = {};
if (!bidix.upload.messages) bidix.upload.messages = {
//from saving
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
backupSaved: "Backup saved",
backupFailed: "Failed to upload backup file",
rssSaved: "RSS feed uploaded",
rssFailed: "Failed to upload RSS feed file",
emptySaved: "Empty template uploaded",
emptyFailed: "Failed to upload empty template file",
mainSaved: "Main TiddlyWiki file uploaded",
mainFailed: "Failed to upload main TiddlyWiki file. Your changes have not been saved",
//specific upload
loadOriginalHttpPostError: "Can't get original file",
aboutToSaveOnHttpPost: 'About to upload on %0 ...',
storePhpNotFound: "The store script '%0' was not found."
};
bidix.upload.uploadChanges = function(onlyIfDirty,tiddlers,storeUrl,toFilename,uploadDir,backupDir,username,password)
{
var callback = function(status,uploadParams,original,url,xhr) {
if (!status) {
displayMessage(bidix.upload.messages.loadOriginalHttpPostError);
return;
}
if (bidix.debugMode)
alert(original.substr(0,500)+"\n...");
// Locate the storeArea div's
var posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
bidix.upload.uploadRss(uploadParams,original,posDiv);
};
if(onlyIfDirty && !store.isDirty())
return;
clearMessage();
// save on localdisk ?
if (document.location.toString().substr(0,4) == "file") {
var path = document.location.toString();
var localPath = getLocalPath(path);
saveChanges();
}
// get original
var uploadParams = Array(storeUrl,toFilename,uploadDir,backupDir,username,password);
var originalPath = document.location.toString();
// If url is a directory : add index.html
if (originalPath.charAt(originalPath.length-1) == "/")
originalPath = originalPath + "index.html";
var dest = config.macros.upload.destFile(storeUrl,toFilename,uploadDir);
var log = new bidix.UploadLog();
log.startUpload(storeUrl, dest, uploadDir, backupDir);
displayMessage(bidix.upload.messages.aboutToSaveOnHttpPost.format([dest]));
if (bidix.debugMode)
alert("about to execute Http - GET on "+originalPath);
var r = doHttp("GET",originalPath,null,null,null,null,callback,uploadParams,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
bidix.upload.uploadRss = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
if(status) {
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.rssSaved,bidix.dirname(url)+'/'+destfile);
bidix.upload.uploadMain(params[0],params[1],params[2]);
} else {
displayMessage(bidix.upload.messages.rssFailed);
}
};
// do uploadRss
if(config.options.chkGenerateAnRssFeed) {
var rssPath = uploadParams[1].substr(0,uploadParams[1].lastIndexOf(".")) + ".xml";
var rssUploadParams = Array(uploadParams[0],rssPath,uploadParams[2],'',uploadParams[4],uploadParams[5]);
var rssString = generateRss();
// no UnicodeToUTF8 conversion needed when location is "file" !!!
if (document.location.toString().substr(0,4) != "file")
rssString = convertUnicodeToUTF8(rssString);
bidix.upload.httpUpload(rssUploadParams,rssString,callback,Array(uploadParams,original,posDiv));
} else {
bidix.upload.uploadMain(uploadParams,original,posDiv);
}
};
bidix.upload.uploadMain = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
var log = new bidix.UploadLog();
if(status) {
// if backupDir specified
if ((params[3]) && (responseText.indexOf("backupfile:") > -1)) {
var backupfile = responseText.substring(responseText.indexOf("backupfile:")+11,responseText.indexOf("\n", responseText.indexOf("backupfile:")));
displayMessage(bidix.upload.messages.backupSaved,bidix.dirname(url)+'/'+backupfile);
}
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.mainSaved,bidix.dirname(url)+'/'+destfile);
store.setDirty(false);
log.endUpload("ok");
} else {
alert(bidix.upload.messages.mainFailed);
displayMessage(bidix.upload.messages.mainFailed);
log.endUpload("failed");
}
};
// do uploadMain
var revised = bidix.upload.updateOriginal(original,posDiv);
bidix.upload.httpUpload(uploadParams,revised,callback,uploadParams);
};
bidix.upload.httpUpload = function(uploadParams,data,callback,params)
{
var localCallback = function(status,params,responseText,url,xhr) {
url = (url.indexOf("nocache=") < 0 ? url : url.substring(0,url.indexOf("nocache=")-1));
if (xhr.status == httpStatus.NotFound)
alert(bidix.upload.messages.storePhpNotFound.format([url]));
if ((bidix.debugMode) || (responseText.indexOf("Debug mode") >= 0 )) {
alert(responseText);
if (responseText.indexOf("Debug mode") >= 0 )
responseText = responseText.substring(responseText.indexOf("\n\n")+2);
} else if (responseText.charAt(0) != '0')
alert(responseText);
if (responseText.charAt(0) != '0')
status = null;
callback(status,params,responseText,url,xhr);
};
// do httpUpload
var boundary = "---------------------------"+"AaB03x";
var uploadFormName = "UploadPlugin";
// compose headers data
var sheader = "";
sheader += "--" + boundary + "\r\nContent-disposition: form-data; name=\"";
sheader += uploadFormName +"\"\r\n\r\n";
sheader += "backupDir="+uploadParams[3] +
";user=" + uploadParams[4] +
";password=" + uploadParams[5] +
";uploaddir=" + uploadParams[2];
if (bidix.debugMode)
sheader += ";debug=1";
sheader += ";;\r\n";
sheader += "\r\n" + "--" + boundary + "\r\n";
sheader += "Content-disposition: form-data; name=\"userfile\"; filename=\""+uploadParams[1]+"\"\r\n";
sheader += "Content-Type: text/html;charset=UTF-8" + "\r\n";
sheader += "Content-Length: " + data.length + "\r\n\r\n";
// compose trailer data
var strailer = new String();
strailer = "\r\n--" + boundary + "--\r\n";
data = sheader + data + strailer;
if (bidix.debugMode) alert("about to execute Http - POST on "+uploadParams[0]+"\n with \n"+data.substr(0,500)+ " ... ");
var r = doHttp("POST",uploadParams[0],data,"multipart/form-data; boundary="+boundary,uploadParams[4],uploadParams[5],localCallback,params,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
// same as Saving's updateOriginal but without convertUnicodeToUTF8 calls
bidix.upload.updateOriginal = function(original, posDiv)
{
if (!posDiv)
posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
store.allTiddlersAsHtml() + "\n" +
original.substr(posDiv[1]);
var newSiteTitle = getPageTitle().htmlEncode();
revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
return revised;
};
//
// UploadLog
//
// config.options.chkUploadLog :
// false : no logging
// true : logging
// config.options.txtUploadLogMaxLine :
// -1 : no limit
// 0 : no Log lines but UploadLog is still in place
// n : the last n lines are only kept
// NaN : no limit (-1)
bidix.UploadLog = function() {
if (!config.options.chkUploadLog)
return; // this.tiddler = null
this.tiddler = store.getTiddler("UploadLog");
if (!this.tiddler) {
this.tiddler = new Tiddler();
this.tiddler.title = "UploadLog";
this.tiddler.text = "| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |";
this.tiddler.created = new Date();
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
}
return this;
};
bidix.UploadLog.prototype.addText = function(text) {
if (!this.tiddler)
return;
// retrieve maxLine when we need it
var maxLine = parseInt(config.options.txtUploadLogMaxLine,10);
if (isNaN(maxLine))
maxLine = -1;
// add text
if (maxLine != 0)
this.tiddler.text = this.tiddler.text + text;
// Trunck to maxLine
if (maxLine >= 0) {
var textArray = this.tiddler.text.split('\n');
if (textArray.length > maxLine + 1)
textArray.splice(1,textArray.length-1-maxLine);
this.tiddler.text = textArray.join('\n');
}
// update tiddler fields
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
// refresh and notifiy for immediate update
story.refreshTiddler(this.tiddler.title);
store.notify(this.tiddler.title, true);
};
bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir, backupDir) {
if (!this.tiddler)
return;
var now = new Date();
var text = "\n| ";
var filename = bidix.basename(document.location.toString());
if (!filename) filename = '/';
text += now.formatString("0DD/0MM/YYYY 0hh:0mm:0ss") +" | ";
text += config.options.txtUserName + " | ";
text += "[["+filename+"|"+location + "]] |";
text += " [[" + bidix.basename(storeUrl) + "|" + storeUrl + "]] | ";
text += uploadDir + " | ";
text += "[[" + bidix.basename(toFilename) + " | " +toFilename + "]] | ";
text += backupDir + " |";
this.addText(text);
};
bidix.UploadLog.prototype.endUpload = function(status) {
if (!this.tiddler)
return;
this.addText(" "+status+" |");
};
//
// Utilities
//
bidix.checkPlugin = function(plugin, major, minor, revision) {
var ext = version.extensions[plugin];
if (!
(ext &&
((ext.major > major) ||
((ext.major == major) && (ext.minor > minor)) ||
((ext.major == major) && (ext.minor == minor) && (ext.revision >= revision))))) {
// write error in PluginManager
if (pluginInfo)
pluginInfo.log.push("Requires " + plugin + " " + major + "." + minor + "." + revision);
eval(plugin); // generate an error : "Error: ReferenceError: xxxx is not defined"
}
};
bidix.dirname = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(0, lastpos);
} else {
return filePath.substring(0, filePath.lastIndexOf("\\"));
}
};
bidix.basename = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("#")) != -1)
filePath = filePath.substring(0, lastpos);
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(lastpos + 1);
} else
return filePath.substring(filePath.lastIndexOf("\\")+1);
};
bidix.initOption = function(name,value) {
if (!config.options[name])
config.options[name] = value;
};
//
// Initializations
//
// require PasswordOptionPlugin 1.0.1 or better
bidix.checkPlugin("PasswordOptionPlugin", 1, 0, 1);
// styleSheet
setStylesheet('.txtUploadStoreUrl, .txtUploadBackupDir, .txtUploadDir {width: 22em;}',"uploadPluginStyles");
//optionsDesc
merge(config.optionsDesc,{
txtUploadStoreUrl: "Url of the UploadService script (default: store.php)",
txtUploadFilename: "Filename of the uploaded file (default: in index.html)",
txtUploadDir: "Relative Directory where to store the file (default: . (downloadService directory))",
txtUploadBackupDir: "Relative Directory where to backup the file. If empty no backup. (default: ''(empty))",
txtUploadUserName: "Upload Username",
pasUploadPassword: "Upload Password",
chkUploadLog: "do Logging in UploadLog (default: true)",
txtUploadLogMaxLine: "Maximum of lines in UploadLog (default: 10)"
});
// Options Initializations
bidix.initOption('txtUploadStoreUrl','');
bidix.initOption('txtUploadFilename','');
bidix.initOption('txtUploadDir','');
bidix.initOption('txtUploadBackupDir','');
bidix.initOption('txtUploadUserName','');
bidix.initOption('pasUploadPassword','');
bidix.initOption('chkUploadLog',true);
bidix.initOption('txtUploadLogMaxLine','10');
// Backstage
merge(config.tasks,{
uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}
});
config.backstageTasks.push("uploadOptions");
//}}}
<!--{{{-->
<div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler > fields syncing permalink references jump'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
This ~TiddlyWiki has the required plugins installs to support "~TiddlyChatter", a peer-to-peer collaboration feature.
You can read an example of [[how TiddlyChatter works|What is TiddlyChatter?]].
''You need to set one cookie-stored variable before you can get updates - [[find out more here|Setting up TiddlyChatter]]''
~TiddlyChatter is still in [[development|TiddlyChatterDevelopment]]; click for information about the plugins ~TiddlyChatter is based on and what changes have been made. All the tiddlers necessary to run ~TiddlyChatter are tagged <<tag TiddlyChatter>> for ease of import.
Click for the [[set-up instructions|Setting up TiddlyChatter]].
'New Chatter'...check
'Control panel'...check
'blank space'...check
'Incoming!'...check
Wait, what is a blank space?
refreshStyles("StyleSheetTiddlyChatter");
config.macros.lv = {};
config.macros.lv.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
var table, this_tiddler;
var subManagement, subManagementSlider;
var listTemplate = {};
listTemplate.columns = [];
listTemplate.rowClasses = [];
listTemplate.buttons = [];
// listTemplate.actions = [];
listTemplate.columns.push({
type:"String",
title:"Latest update",
field:"latest_update"
});
listTemplate.columns.push({
type:"TiddlerLink",
title:"Title (no. of updates)",
field:"link_title",
tiddlerLink:"link"
});
listTemplate.columns.push({
type:"String",
title:"Last updated by",
field:"last_updated_by"
});
/* listTemplate.columns.push({
type:"Selector",
field:"checked",
rowName:"row_name"
}); */
listTemplate.rowClasses.push({
field:"unread",
className:"tiddlyChatterIncomingRowUnread"
});
listTemplate.rowClasses.push({
field:"read",
className:"tiddlyChatterIncomingRow"
});
listTemplate.buttons.push({
name:"Get updates",
caption:"Get",
allowEmptySelection:"true"
});
listTemplate.buttons.push({
name:"Create chatter",
caption:"Create",
allowEmptySelection:"true"
});
listTemplate.buttons.push({
name:"Manage subscriptions",
caption:"Manage",
allowEmptySelection:"true"
});
/* listTemplate.actions.push({
name:"bark",
caption:"woof woof"
});
listTemplate.actions.push({
name:"laugh",
caption:"ha ha"
}); */
// callback has to deal with all the different functions, so select them by 'name'
var callback = function(view,name,tiddlers) {
switch(name) {
case "Get updates":
config.macros.importWorkspace.getTiddlers.call(config.macros.importWorkspace,config.defaultCustomFields);
break;
case "Manage subscriptions":
subManagement.style.display = subManagement.style.display == "none" ? "block" : "none";
break;
case "Create chatter":
// mimicing the newTiddler button
this.setAttribute("newTitle","NewChatter");
this.setAttribute("isJournal","false");
this.setAttribute("params","public");
this.setAttribute("newFocus","title");
this.setAttribute("newTemplate","2");
this.setAttribute("customFields","unread:true");
this.setAttribute("newText","Type some text and then press DONE");
config.macros.newTiddler.onClickNewTiddler.call(this,null);
break;
default:
// don't do anything
break;
}
};
/* START: routine to fill content into our listObject */
var tagFilter;
if (params) {
tagFilter = params[0];
}
// content_and_notes holds our content and associated notes in the form:
// [{content:content1,notes:[note1a,note1b]},{content:content2,notes[note2a,note2b]},...]
var content_and_notes = [];
var notes = [];
var filteredContent = store.filterTiddlers(tagFilter);
// process: collect the content first and set up the content_and_notes array
// collect the notes at the same time and store them
// sort notes by modify date
// run through cotent, adding its notes to content_and_notes
// sort content_and_notes by modify date of most recent note belonging to content
for (var i=0;i<filteredContent.length;i++) {
var t = filteredContent[i];
if (t.isTagged("public")) {
if (!t.isTagged("notes")) {
// it's parent content
content_and_notes.push({content:t,notes:[]});
} else {
// it's a note
notes.push(t);
}
}
}
notes.sort(function(a,b){
return a.modified > b.modified ? -1 : (a.modified == b.modified ? 0 : 1);
});
for (var i=0;i<content_and_notes.length;i++) {
var content_object = content_and_notes[i];
for (var j=0;j<notes.length;j++) {
if (notes[j].title.indexOf(content_object["content"].title) != -1) {
// matched a note with a title containing a piece of content's title
content_object["notes"].push(notes[j]);
}
}
}
content_and_notes.sort(function(a,b){
var a_most_recent, b_most_recent;
// a["notes"][0] is the most recent note for a piece of content, if it exists
// if it doesn't exist, use the content itself
if (a["notes"][0]) {
a_most_recent = a["notes"][0];
} else {
a_most_recent = a["content"];
}
if (b["notes"][0]) {
b_most_recent = b["notes"][0];
} else {
b_most_recent = b["content"];
}
return a_most_recent.modified > b_most_recent.modified ? -1 : (a_most_recent.modified == b_most_recent.modified ? 0 : 1);
});
// map content_and_notes onto listObject
var listObject = [];
for (var i=0;i<content_and_notes.length;i++) {
var content_object = content_and_notes[i];
// noteCount is the number of notes in content_object["notes"];
var noteCount = content_object["notes"] ? content_object["notes"].length : 0;
// newestNote is the first note in content_object["notes"] or the content itself if there are no notes
var newestNote = noteCount !== 0 ? content_object["notes"][0] : content_object["content"];
// display "new" next to the title if noteCount is 0
var newContent = noteCount === 0 ? true : false;
// we want to know how many days since the last update
var daysSince = newestNote.modified.relativeDays();
// display today or yesterday if daysSince is 0 or 1, respectively
if (daysSince === 0) {
daysSince = "today";
} else if (daysSince == 1) {
daysSince = "yesterday";
} else {
daysSince = daysSince + " days ago";
}
var contentTitle = content_object["content"].title;
var contentTitleSuffix = newContent ? " (new)" : " (" + noteCount + ")";
listObject[i] = {
latest_update:daysSince,
link_title:contentTitle + contentTitleSuffix,
link:contentTitle,
last_updated_by:newestNote.modifier,
checked:newestNote.fields["unread"] ? true : false,
row_name:contentTitle
};
if (newestNote.fields["unread"] == "true") {
listObject[i]["unread"] = "yes";
} else {
listObject[i]["read"] = "yes";
}
}
/* END */
// Create a listview
table = ListView.create(place,listObject,listTemplate,callback);
this_tiddler = story.findContainingTiddler(table);
this_tiddler.setAttribute("refresh","tiddler");
this_tiddler.setAttribute("force","true");
subManagement = config.macros.tiddlyChatterSetup.handler(place,"tiddlyChatterSetup",params,wikifier,paramString,tiddler);
subManagement.style.display = "none";
};
<!--{{{-->
<div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler > fields syncing permalink references jump'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='publishing' macro='publishing'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='viewer' macro='notes tags:"notes public"'></div>
<div class='tagClear'></div>
<!--}}}-->
/***
|''Name:''|StickyOptionsPlugin|
|''Description:''||
|''Author:''|Saq Imtiaz ( lewcid@gmail.com )|
|''Source:''|http://tw.lewcid.org/#StickyOptionsPlugin|
|''Code Repository:''|http://tw.lewcid.org/svn/plugins|
|''Version:''|2.0 pre-release|
|''Date:''||
|''License:''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
|''~CoreVersion:''|2.2|
!!Usage:
* Sticky options are saved in the file rather than in cookies.
* To make an option sticky, check the 'sticky' checkbox for it in AdvancedOptions
* To force all options to be sticky, check the 'Options always sticky' checkbox at the bottom of the AdvancedOptions screen.
* Please remember that the file needs to be saved after each option is saved, for the setting to be remembered.
* Enable the 'autosave' option below, to trigger an autosave when an option is saved
Trigger autosave when an option is saved: <<option chkStickyOptionsAutoSave>>
***/
// /%
//!BEGIN-PLUGIN-CODE
if(config.options.chkStickyOptionsAutoSave == undefined)
config.options.chkStickyOptionsAutoSave = false;
config.optionsDesc.chkStickyOptionsAutoSave = "Trigger autosave when an option is saved";
StickyOptions = {
container : tiddler,
alwaysSticky : !! store.getValue(this.container,"stickyoptions.alwayssticky"),
toggleAlwaysSticky : function(stat){
this.alwaysSticky = stat;
store.setValue(this.container,"stickyoptions.alwayssticky",stat? stat : undefined);
if(stat)
this.saveAllOptions();
},
toggleOption : function(optName,toggle,stat,nosave){
if(this.isOption(optName)){
var optVal = toggle ? (stat? config.options[optName]:undefined):config.options[optName];
if(toggle)
this.updateDir(optName,stat);
store.setValue(this.container,"sticky."+optName.toLowerCase(),optVal);
if(!nosave && config.options.chkStickyOptionsAutoSave)
autoSaveChanges();
}
},
saveAllOptions : function(){
store.suspendNotifications();
for(var n in config.options){
this.toggleOption(n,true,true,true);
}
store.resumeNotifications;
store.notify(this.container.title,true);
if (config.options.chkStickyOptionsAutoSave)
autoSaveChanges();
},
updateDir : function(optName,stat){
this.options.setItem(optName,stat? +1 : -1);
store.setValue(this.container,"stickyoptions.dir",this.options);
},
getOption : function(optName){
return store.getValue(this.container,"sticky."+optName.toLowerCase());
},
isOption : function(optName){
var optType = optName.substr(0,3);
return (config.optionHandlers[optType] && config.optionHandlers[optType].get);
},
isSticky : function(optName){
return this.options.contains(optName);
},
loadAllOptions : function(){
if(safeMode)
return;
var savedOpts = store.getValue(this.container,"stickyoptions.dir");
this.options = savedOpts? savedOpts.split(",") : [];
for (var i=0; i<this.options.length; i++){
var optType = this.options[i].substr(0,3);
if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
config.optionHandlers[optType].set(this.options[i],this.getOption(this.options[i]));
}
},
oldSaveOptionCookie : window.saveOptionCookie
};
StickyOptions.loadAllOptions();
saveOptionCookie = function(name){
if (StickyOptions.alwaysSticky || StickyOptions.isSticky(name)){
StickyOptions.toggleOption(name,StickyOptions.alwaysSticky ? true:false ,true);
}
else{
StickyOptions.oldSaveOptionCookie(name);
}
};
config.macros.options.step1Title += " unless they are sticky. Sticky options are saved in this file.";
config.macros.options.old_step1Html = config.macros.options.step1Html;
config.macros.options.updateStep1Html = function(){
this.step1Html = this.old_step1Html + "<br><input type='checkbox' " + (StickyOptions.alwaysSticky? "checked":"") + " onclick='config.macros.options.toggleAlwaysSticky(this);'>Options always sticky</input>";
};
config.macros.options.listViewTemplate.columns.splice(1,0,{name: 'Sticky', field: 'name', title: "Sticky", type: 'StickyOption'});
config.macros.options.old_handler = config.macros.options.handler;
config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler){
this.updateStep1Html();
this.old_handler.apply(this,arguments);
};
config.macros.options.toggleAlwaysSticky = function(e)
{
StickyOptions.toggleAlwaysSticky(e.checked);
var wizard = new Wizard(e);
var listWrapper = wizard.getValue("listWrapper");
var chkUnknown = wizard.getElement("chkUnknown").checked;
removeChildren(listWrapper);
config.macros.options.refreshOptions(listWrapper,chkUnknown);
return false;
};
ListView.columnTypes.StickyOption = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var opt = listObject[field];
var e = createTiddlyCheckbox(place,null,StickyOptions.isSticky(opt),this.onChange);
e.disabled = StickyOptions.alwaysSticky;
e.name = opt;
},
onChange : function(e){
StickyOptions.toggleOption(this.name,true,this.checked);
}
};
//!END-PLUGIN-CODE
// %/
/* Update on 09/10/07: SIMPLIFICATION
* No longer offering a choice of streams by default
* ID for stream tiddlers now supplied as a parameter */
config.macros.publishing = {};
config.macros.publishing.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
// publishing puts a box on a tiddler that shows:
// if the tiddler is not 'published', a 'publish' button -
// - hitting 'publish' drops a menu of available streams to choose from;
// if the tiddler is published, a 'published' label -
// - hitting 'published' drops a menu of streams the tiddler is published in
// - plus the list of streams that you can also publish it in
// being published is defined as being tagged with a channel name
var published = false;
var publications = {};
// collect a list of streams and label them as being streams this tiddler is published in or not
params = paramString.parseParams("anon",null,true,false,false);
// stream_id defines what a stream tiddler is tagged with
// if we haven't supplied that parameter, we just look at whether this tiddler is tagged published or not
var stream_id = getParam(params,"stream_id",null);
if (stream_id) {
var streams = store.getTaggedTiddlers(stream_id);
publications = {'yes':[], 'no':[]};
for (var i=0;i<streams.length;i++) {
if (tiddler.isTagged(streams[i].title)) {
publications.yes.push(streams[i]);
} else {
publications.no.push(streams[i]);
}
}
if (publications.yes.length !== 0) {
published = true;
}
} else {
if (tiddler.isTagged("published")) {
published = true;
}
}
if (published) {
// add a published box
var thePublishedBox = createTiddlyButton(place,"Published","Click to unpublish");
// is there is no stream_id, we are not working with streams, so clicking Published unpublishes the content, which means updates will no longer appear in the stream
if (stream_id) {
thePublishedBox.onclick = this.reveal;
// create the published list
var thePublishedList = createTiddlyElement(place,"ul");
thePublishedList.style.display = "none";
for (var i=0;i<publications.yes.length;i++) {
// for the published list just present a simple list
var streamItem = createTiddlyElement(thePublishedList,"li",null,null,publications.yes[i].title);
}
} else {
thePublishedBox.onclick = function() {
config.macros.publishing.unsubscribe.call(this,"published");
};
}
} else {
// add a publish box
var thePublishBox = createTiddlyButton(place,"Publish","Click to publish");
// if there is no stream_id, we are not working with streams, so clicking publish just adds "published" to the tiddler's tags
if (stream_id) {
thePublishBox.onclick = this.reveal;
// create the publish list
var thePublishListBox = createTiddlyElement(place,"div");
thePublishListBox.style.display = "none";
var thePublishList = createTiddlyElement(thePublishListBox,"ul");
for (var i=0;i<publications.no.length;i++) {
// for the publish list present a list of buttons
var streamItem = createTiddlyElement(thePublishList,"li");
createTiddlyButton(streamItem,publications.no[i].title,publications.no[i].title,this.subscribe);
}
// CROSS-PLUGIN DEPENDENCY!
if (config.macros.tiddlyChatterSetup) {
var newStream = createTiddlyButton(thePublishListBox,"new stream...","Create a new stream",this.reveal);
var newStreamBox = createTiddlyElement(thePublishListBox,"div");
newStreamBox.style.display = "none";
// next line to give input box and go button same depth as list items above
// so the subscribe function points to the parent tiddler properly in both cases
var newStreamList = createTiddlyElement(newStreamBox,"div");
var newStreamInput = createTiddlyElement(newStreamList,"input",null,null);
newStreamInput.setAttribute("size","5");
createTiddlyButton(newStreamList,"go","go",config.macros.publishing.onClickNewChannel);
}
} else {
thePublishBox.onclick = function() {
config.macros.publishing.subscribe.call(this,"published");
};
}
}
};
config.macros.publishing.onClickNewChannel = function() {
// call the onclick for the stream creator, setting 'this' to the current value of 'this'
config.macros.tiddlyChatterSetup.onClickNewChannel.call(this);
// now subscribe to this channel
var created = false;
if (store.fetchTiddler(this.previousSibling.value)) {
created = true;
}
// subscribe, setting 'this' to be the input with the new stream name in
this.previousSibling.textContent = this.previousSibling.value;
config.macros.publishing.subscribe.call(this.previousSibling);
};
// onclick for channel names; 'this' refers to the link
config.macros.publishing.subscribe = function(tag) {
var DOMTiddler = story.findContainingTiddler(this);
var tiddler = store.fetchTiddler(DOMTiddler.attributes.tiddler.textContent);
if (!tag) {
tag = this.textContent;
}
tiddler.tags.push(tag);
tiddler.set(tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.created,tiddler.fields);
story.refreshTiddler(DOMTiddler.getAttribute("tiddler"),DOMTiddler.getAttribute("template"),true);
};
// onclick for channel names when published; 'this' refers to the link
config.macros.publishing.unsubscribe = function(tag) {
var DOMTiddler = story.findContainingTiddler(this);
var tiddler = store.fetchTiddler(DOMTiddler.attributes.tiddler.textContent);
if (!tag) {
tag = this.textContent;
}
tiddler.tags.splice(tiddler.tags.indexOf(tag),1);
tiddler.set(tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.created,tiddler.fields);
story.refreshTiddler(DOMTiddler.getAttribute("tiddler"),DOMTiddler.getAttribute("template"),true);
};
// onclick for "publish" buttons; 'this' refers to the span
config.macros.publishing.reveal = function() {
// create an interface to give your channel an id
var slideBox = this.nextSibling;
var isOpen = slideBox.style.display != "none";
if(anim && typeof Slider == "function")
anim.startAnimating(new Slider(slideBox,!isOpen,null,"none"));
else
slideBox.style.display = isOpen ? "none" : "block";
};
//{{{
/* Update on 09/10/07: SIMPLIFICATION
* No longer offering a choice of streams by default
* Boolean parameter provided to specify whether we are working with streams */
config.macros.tiddlyChatterSetup = {};
config.macros.tiddlyChatterSetup.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
params = paramString.parseParams("anon",null,true,false,false);
// using_streams defines whether we are working with multiple streams
var using_streams = getParam(params,"using_streams",null);
var channelBox;
if(using_streams) {
// create an interface for handling streams
// a streams is a feed you publish
// add a button to create new streams
var channelWrapper = createTiddlyElement(place,"div");
channelBox = createTiddlyElement(channelWrapper,"div","channelBox");
var newChannelButton = createTiddlyButton(channelBox,"New stream","New stream",this.reveal);
createTiddlyElement(channelBox,"br");
// set up the new channel UI to reveal when 'new channel' is clicked
var newChannelBox = createTiddlyElement(channelWrapper,"div","newChannelBox");
newChannelBox.style.display = "none";
createTiddlyElement(newChannelBox,"span",null,null,"Please provide a name for your stream");
createTiddlyElement(newChannelBox,"br");
createTiddlyElement(newChannelBox,"span",null,null,"Stream:");
var channelName = createTiddlyElement(newChannelBox,"input","newChannelName");
createTiddlyButton(newChannelBox,"create","Create stream",this.onClickNewChannel);
createTiddlyElement(newChannelBox,"br");
}
// create an interface for handling subscriptions
// we do this whether or not using_streams is true
// a subscription is to someone else's stream
// add a button to create new subscriptions
var subscriptionWrapper = createTiddlyElement(place,"div");
var subscriptionBox = createTiddlyElement(subscriptionWrapper,"div","subscriptionBox");
var newSubscriptionButton = createTiddlyButton(subscriptionBox,"New subscription","New subscription",this.reveal);
createTiddlyElement(subscriptionBox,"br");
// set up the new subscription UI to reveal when 'new subscription' is clicked
var newSubscriptionBox = createTiddlyElement(subscriptionWrapper,"div","newSubscriptionBox");
newSubscriptionBox.style.display = "none";
createTiddlyElement(newSubscriptionBox,"span",null,null,"Please point to the stream list you want to subscribe to");
createTiddlyElement(newSubscriptionBox,"br");
createTiddlyElement(newSubscriptionBox,"span",null,null,"URL:");
var subscriptionURL = createTiddlyElement(newSubscriptionBox,"input","newSubscriptionURL");
createTiddlyButton(newSubscriptionBox,"go","View stream list",this.onClickNewSubscription);
createTiddlyElement(newSubscriptionBox,"br");
// a stream is defined as a tiddler tagged with systemServer, channel and the id of the channel
// the id is what you tag your tiddler with to put it in that channel
// a subscription is defined as a tiddler tagged with systemServer, channel, subscription and the id of the channel
// a subscription is also a channel, in that you can subscribe to it
var channels = [];
var subscriptions = [];
if (using_streams) {
store.forEachTiddler(function(title,tiddler) {
if (tiddler.isTagged("systemServer") && tiddler.isTagged("channel")) {
channels.push(tiddler);
if (tiddler.isTagged("subscription")) {
subscriptions.push(tiddler);
}
}
});
// the channels array now has all the channel tiddlers in it, so we add them to the channelBox
for (var i=0;i<channels.length;i++) {
createTiddlyLink(channelBox,channels[i].title,true);
// if the channel is a subscription too, flag this to the user
if (channels[i].isTagged("subscription")) {
wikify("// - one of your own subscriptions//",channelBox);
}
createTiddlyElement(channelBox,"br");
}
} else {
store.forEachTiddler(function(title,tiddler) {
if (tiddler.isTagged("systemServer") && tiddler.isTagged("published")) {
subscriptions.push(tiddler);
}
});
}
// the subscriptions array now has all the subscriptions tiddlers in it, so we add them to the subscriptionBox
// we do this whether or not using_streams is true
for (var i=0;i<subscriptions.length;i++) {
createTiddlyLink(subscriptionBox,subscriptions[i].title,true);
createTiddlyElement(subscriptionBox,"br");
}
};
// onclick for creating a new channel; 'this' refers to the button
config.macros.tiddlyChatterSetup.onClickNewChannel = function() {
var channelName = this.previousSibling.value;
// create a new tiddler tagged with channel, systemServer and whatever id the user specified
// a channel's filter is of the form [tag[public id]], where id is the same as above
// we leave the URL field blank and let that be created by the subscription mechanism
var tags = "channel systemServer";
tags += " " +channelName;
var tiddlerBody = "|''Type:''|RSS|\n|''URL:''||\n|''Workspace:''||\n|''TiddlerFilter:''|[tag[public "+channelName+"]]|";
store.saveTiddler(channelName,channelName,tiddlerBody,config.options.txtUserName,null,tags);
var this_tiddler = story.findContainingTiddler(this);
story.refreshTiddler(this_tiddler.getAttribute("tiddler"),this_tiddler.getAttribute("template"),true);
};
// onclick after clicking the new subscription button; 'this' refers to the button
config.macros.tiddlyChatterSetup.onClickNewSubscription = function() {
var subscriptionURL = document.getElementById("newSubscriptionURL").value;
var place = document.getElementById("newSubscriptionBox");
// load up the url provided and show a list of channels to subscribe to
// assume we are pointing at a TiddlyWiki
var adaptor = new FileAdaptor();
var context = {};
context.place = place;
adaptor.openHost(subscriptionURL,context,null,config.macros.tiddlyChatterSetup.onOpenHost);
};
config.macros.tiddlyChatterSetup.onOpenHost = function(context,userParams) {
if(context.status !== true) {
displayMessage("error opening host: " + context.statusText);
} else {
var filter = "[tag[channel systemServer]]";
context.adaptor.getTiddlerList(context,userParams,config.macros.tiddlyChatterSetup.onGetTiddlerList,filter);
}
};
config.macros.tiddlyChatterSetup.onGetTiddlerList = function(context,userParams) {
// collect a list of existing channels to check against
var channels = [];
store.forEachTiddler(function(title,tiddler) {
if (tiddler.isTagged("systemServer") && tiddler.isTagged("channel")) {
channels.push(tiddler);
}
});
// offer a list of channels to subscribe to
for (var i=0; i<context.tiddlers.length; i++) {
createTiddlyElement(context.place,"span",null,null,context.tiddlers[i].title);
var box = createTiddlyCheckbox(context.place,"tick me",false,function(){
var subscribeButton = document.getElementById("subscribeButton");
if(this.checked==true) {
subscribeButton.tiddler_title = this.previousSibling.textContent;
} else {
subscribeButton.tiddler_title = "";
}
});
// if the name of a potential subscription is the same as one of your own channels,
// flag that to the user
for (var t in channels) {
if (channels[t].title == context.tiddlers[i].title) {
wikify("// - this could be your own content - learn more [[here|ReciprocalSubscriptions]]//",context.place);
}
}
createTiddlyElement(context.place,"br");
}
var subscribeButton = createTiddlyButton(context.place,"subscribe","Subscribe",config.macros.tiddlyChatterSetup.onClickSubscribe,null,"subscribeButton");
subscribeButton.context = context;
};
// onclick for clicking the subscribe button; 'this' refers to the button
config.macros.tiddlyChatterSetup.onClickSubscribe = function() {
var tiddler = {};
var tiddler_title = this.tiddler_title;
for (var t in this.context.tiddlers) {
if (this.context.tiddlers[t].title == this.tiddler_title) {
tiddler = this.context.tiddlers[t];
}
}
if (tiddler) {
// now copy the tiddler across, adding in the 'subscription' tag and rebuilding the body with the URL
var adaptor_store = this.context.adaptor.store;
var type_field = adaptor_store.getTiddlerSlice(tiddler.title,"Type");
var workspace_field = adaptor_store.getTiddlerSlice(tiddler.title,"Workspace");
var filter_field = adaptor_store.getTiddlerSlice(tiddler.title,"TiddlerFilter");
var subscriptionURL = this.context.adaptor.host;
subscriptionURL = subscriptionURL.replace(/\.html$/g,".xml");
var subscriptionTemplate = "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n|''TiddlerFilter:''|%3|";
var text = subscriptionTemplate.format([type_field,subscriptionURL,workspace_field,filter_field]);
tiddler.tags.push("subscription");
store.saveTiddler(tiddler.title, tiddler.title, text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
var this_tiddler = story.findContainingTiddler(this);
story.refreshTiddler(this_tiddler.getAttribute("tiddler"),this_tiddler.getAttribute("template"),true);
} else {
displayMessage("problem with matching: " + tiddler_title);
}
};
// onclick for "new" buttons; 'this' refers to the button
config.macros.tiddlyChatterSetup.reveal = function() {
var slideBox = this.parentNode.nextSibling;
var isOpen = slideBox.style.display != "none";
if(anim && typeof Slider == "function")
anim.startAnimating(new Slider(slideBox,!isOpen,null,"none"));
else
slideBox.style.display = isOpen ? "none" : "block";
};
// Extension to TiddlyWiki.js
// Filter a list of tiddlers
TiddlyWiki.prototype.filterTiddlers = function(filter)
{
var results = [];
if(filter) {
var re = /(\w+)|(?:\[([ \w]+)\[([ \w]+)\]\])|(?:\[\[([ \w]+)\]\])/mg;
var match = re.exec(filter);
while(match) {
if(match[1]) {
var tiddler = this.fetchTiddler(match[1])
if(tiddler)
results.push(tiddler);
} else if(match[2]) {
if(match[2]=="tag") {
this.forEachTiddler(function(title,tiddler) {
if(tiddler.isTaggedAllOf(match[3].split(" "))) {
results.push(tiddler);
}
});
}
} else if(match[4]) {
var tiddler = this.fetchTiddler(match[4])
if(tiddler)
results.push(tiddler);
}
match = re.exec(filter);
}
} else {
this.forEachTiddler(function(title,tiddler) {results.push(tiddler);});
}
return results;
};
//}}}