Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background::[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which use a logographic writing system and need larger font sizes.
***/

/*{{{*/
body {font-size:0.8em;}

#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}

.subtitle {font-size:0.8em;}

.viewer table.listView {font-size:0.95em;}

.htmlarea .toolbarHA table {border:1px solid ButtonFace; margin:0em 0em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton {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;}
}
/*}}}*/
<!--{{{-->
<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
<!--{{{-->
<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>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
<!--}}}-->
To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
These InterfaceOptions for customising TiddlyWiki are saved in your browser

Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

<<option txtUserName>>
<<option chkSaveBackups>> SaveBackups
<<option chkAutoSave>> AutoSave
<<option chkRegExpSearch>> RegExpSearch
<<option chkCaseSensitiveSearch>> CaseSensitiveSearch
<<option chkAnimate>> EnableAnimations

----
Also see AdvancedOptions
* Digg this
* Blog reactions badge
* Comments
* Trackback (can this be done with blog reactions? Or Blogger-style google links?)
!Current
* 24/10/07 - Parent content tiddler's unread status doesn't get updated to unread when a new note is added, which it should - or the displayed status should be unread if at least one of the notes' statuses' is unread
** 23/10/07 - Update: partial fix: problem is that the process is not repeatable, as the match looking for feeds to import from returns an empty array
* 23/10/07 - ordering of content in ListView is not right - for content on same day, latest doesn't appear at the top
* 09/10/07 - refresh mechanism implemented in a way I don't fully understand - could be unstable?
** 23/10/07 - adding new content doesn't update the TiddlyChatter content list, although hitting "mark as read" or "mark as unread" does
* 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 (update: actually it is "channel")
! Fixed
* 23/10/07 - Get updates doesn't get all your subscriptions - Fixed 24/10/07
* 16/10/07 - Creating a new tiddler doesn't add the unread field necessary to make the unread macro work properly - Fixed 17/10/07
* 16/10/07 - author name not working for imported content - Fixed 23/10/07
* 16/10/07 - Tiddler says "No notes" if you import other people's notes - Fixed 23/10/07
* 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. Add username to end of title, as in tiddler-NotesJayFresh0 ? - Fixed 23/10/07, design decision: notes are unique by user ID, content not supposed to be updated by people other than the content owner - in future, put in notification if new content updates come and a user has changed their copy
* 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) - Fixed 23/10/07, design decision: "mark as read" button now reflects the status of the newest update
* 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]]|}}} - Fixed 23/10/07, design decision: the first time TiddlyChatter runs, it creates a new feed called ChatterFeed with the username appended and points the url at the user's own xml feed; this means that ChatterFeed remains what the user uses as their default subscription
** Tag it: "systemServer TiddlyChatter public channel" and lets see where it goes.
//{{{
// Blog macro
// v0.1
// 09th October 2007
// 
// Author: Craig Cook, craig [dot] cook [at] bt [dot] com
//
// Not quite sure how to do this at the moment

//Need to add the following into the main TiddlyWiki:
//config.commands.publishBlog: {}, - FIXED
//and to the div 'ViewTemplate' in the 'toolbar' class add publishBlog - CHANGED:
// blogViewTemplate created, active when TaggedTemplateTweak plugin is installed
//Add the publishBlog option into TiddlyWiki

// set some default values for blog variables
if(!config.options.txtBlogID) config.options.txtBlogID = "your blog id";
if(!config.options.txtBlogUserName) config.options.txtBlogUserName = "your username";
if(!config.options.pasBlogPassWord) config.options.pasBlogPassWord = "password";

config.commands.publishBlog = {};

merge(config.commands.publishBlog,{
	text: "publish as blog",
	tooltip: "This will publish to you online blog"});

config.commands.publishBlog.handler = function(event,src,title) {
    var pubTiddler = store.getTiddler(title);
	var blog = new Blog();
	
	blog.setContent(pubTiddler.text);
	blog.setSubject(pubTiddler.title);
	blog.setTags(pubTiddler.tags);
    blog.publish();	
};

function Blog() {
    this.appkey = "0123456789ABCDEF";
    this.username = config.options.txtBlogUserName;
    this.password = config.options.pasBlogPassWord;
    this.blogid = config.options.txtBlogID;
    this.content = "";
    this.subject = "";
    this.tags = "";
    this.method = "metaWeblog.newPost";
    this.blogURL = "http://"+config.options.txtBlogID+".wordpress.com/xmlrpc.php";   //This is for wordpress, will need to be changed for others
}

Blog.prototype.publish = function(){
	var xmlMsg = new XMLRPCMessage();
	xmlMsg.setMethod(this.method);
    xmlMsg.addStringXML(this.blogid);
    xmlMsg.addStringXML(this.username);
    xmlMsg.addStringXML(this.password);
    xmlMsg.addItemXML("<struct><member><name>description</name><value>" + this.content + "</value></member><member><name>title</name><value>" + this.subject + "</value></member><member><name>categories</name><value><array><data>" + this.tags + "</data></array></value></member></struct>");
    xmlMsg.addBoolXML(true);
    
    var xmlText = xmlMsg.xml();
    this.makeAjaxCall(this.blogURL, xmlText);
};

Blog.prototype.makeAjaxCall = function(url, postdata) {

	// with callback
	var loginCallback = function(status,params,responseText,url,xhr) {

		if (status) {
			displayMessage("completed...");
		} else {
			displayMessage("status is false");
			displayMessage(xhr.statusText);
		}
	};

	var headers = {
		"Content-type":"text/xml",
		"Content-length":postdata.length,
		"Connection":"close"
	};

	var ret = doHttp("POST",url,postdata,null,null,null,loginCallback,null,headers);
   	if(typeof ret == "string") {
   		displayMessage("Failed HTTP request - is BlogPlugin set up?","Error: " + ret);
   	} else {
   		displayMessage("posting...");
   	}

	/* old version without callback
    var xmlhttp = getXMLHttpRequest();

    if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
        window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");

    xmlhttp.open("POST", url, false);
    xmlhttp.setRequestHeader("Content-type", "text/xml");
    xmlhttp.setRequestHeader("Content-length", postdata.length);
    xmlhttp.setRequestHeader("Connection", "close");        
    xmlhttp.send(postdata);
    alert("You blog has been sent");
    //TODO: add response to blog submission
    
	END: old version without callback */
};

Blog.prototype.setSubject = function(txt){
    if (!txt) return;
    this.subject = txt;
};

Blog.prototype.setContent = function(txt){
    if (!txt) return;
    this.content = txt;
};

Blog.prototype.setTags = function(tags){
	for (var i=0;i<tags.length;i++) {
		if (tags[i] != "blog") {
			this.tags += "<value>" + tags[i] + "</value>";
		}
	}
	if (!this.tags) {
		this.tags = "<value>Uncategorized</value>";
		return;
	}
};

function XMLRPCMessage() {
    this.method = "";
    this.msgParams = [];
}

XMLRPCMessage.prototype.setMethod = function(methodName){
    if (!methodName) return;
    this.method = methodName;
};

XMLRPCMessage.prototype.xml = function(){

  var method = this.method;
  
  // assemble the XML message header
  var xml = "";
  
  xml += "<?xml version=\"1.0\"?>\n";
  xml += "<methodCall>\n";
  xml += "<methodName>" + method+ "</methodName>\n";
  xml += "<params>\n";
  
  // do individual parameters
  for (var i = 0; i < this.msgParams.length; i++){
    xml += this.msgParams[i];
  }
  
  xml += "</params>\n";
  xml += "</methodCall>";
  
  return xml; // for now
};

XMLRPCMessage.prototype.addBoolXML = function(data){
    var value = (data==true)?1:0;  
    this.msgParams[this.msgParams.length] = "<param>\n<value><boolean>" + value + "</boolean></value>\n</param>\n";
};

XMLRPCMessage.prototype.addStringXML = function(data){
    this.msgParams[this.msgParams.length] = "<param>\n<value><string>" + data + "</string></value>\n</param>\n";
};

XMLRPCMessage.prototype.addItemXML = function(data){
    this.msgParams[this.msgParams.length] = "<param>\n" + data + "\n</param>\n";
};

//}}}
http://24ways.org/2008/easing-the-path-from-design-to-development
This is about photoshop design to developer. I have a  workflow that goes from photoshop design, to HTML mockup with sample data, through to whatever templating the system uses to inject content (and presumably logic). Working on this for Geekmap, probably using TiddlyTemplating at some point - the browser is the place to write things for the browser.
-------
geekmap.co.uk - the thing - example of doing business in public - outside of the scope of a formal business arrangement, tracking contributions
-------
Thoughts about proportional reward
Peer-voting to accrue "points"; thumb-in-the-air equivalence between different identifiable contributions e.g. setting up a server, designing a page, adding a user interface widget, something server-sidey
-------
OpenSource CSS
Gallery of CSS fragments that style HTML structures (inc. microformats) in a cross-browser way
-------
User stories and development
Techniques for tying any development to user stories
Use of tickets, sub-stories that capture functional elements (are these acceptance criteria?)
Setup your blog!
| URL: | http:// <<option txtBlogID>>.wordpress.com/ |
| Username: | <<option txtBlogUserName>> |

| Password: | <<option pasBlogPassWord>> |
|''Type:''|file|
|''URL:''|http://bob.mcelrath.org/plugins.html|
|''Workspace:''||

This tiddler was automatically created to record the details of this server
|''Type:''|RSS|
|''URL:''|http://fnd.tiddlyspot.com/index.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
|''Type:''|RSS|
|''URL:''|file:///Users/jonathanlister/Desktop/jayfresh(2).xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
|''Type:''|RSS|
|''URL:''|http://jayfresh.tiddlyspot.com/index.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
|''Type:''|RSS|
|''URL:''|http://fnd.tiddlyspot.com/|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
|''Type:''|RSS|
|''URL:''|http://jayfresh.tiddlyspot.com/index.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
Background: #fcfcfc
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #06d
PrimaryMid: #909
PrimaryDark: #04b
SecondaryPale: #fff
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
<<fullscreen>><<search>><<newTiddler label:"new blog post" title:NewBlogPost tag:blog text:"When you're finished drafting your blog post, hit publish">><<newTiddler label:"new task" title:NewTask tag:task text:"Type some text and then press DONE to view the task controls" >><<newTiddler>><<closeAll>><<permaview>><<tiddler TspotSidebar>><<slider chkSliderOptionsPanel Listings "Listings »" "View the full tiddler listings">>
{{{
/*** 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;
};
}}}
<<dailytasks>>
/***
|''Name:''|DailyTasksMacro|
|''Description:''|For habit forming. Tick off tasks on a daily basis and don't break the chain.|
|''Author''|Saq Imtiaz (modifications by Jon Lister)|
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components |
|''Version:''|0.2|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License''|[[BSD License|http://www.opensource.org/licenses/bsd-license.php]] |
|''~CoreVersion:''|2.2|
***/

function getOffsetDate (offset) {
	var date = new Date();
	dt=date.getDate();
	date.setDate(dt-offset);
	return date;
};

config.macros.deletedailytask = {
	
	label : ' X ',
	
	tooltip : 'delete this daily task',
	
	handler : function(place,macroName,params,wikifier,paramString,tiddler){
		var b = createTiddlyButton(place,this.label,this.tooltip,this.onclick,'deletedailytask dailytaskbutton button');
		b.setAttribute('tiddler',params[0]);
	},
	
	onclick : function(e) {
		if (!e) var e = window.event;
		var tiddler = this.getAttribute('tiddler');
		var doIt = confirm('are you sure you wish to delete the task: ' + tiddler);
		if (doIt) {
			story.closeTiddler(tiddler);
			store.removeTiddler(tiddler);
			store.notify(tiddler,true);
			story.refreshTiddler(story.findContainingTiddler(this).getAttribute('tiddler'),null,true);
		}
		return false;
	}
	

};

config.macros.newdailytask = {
	
	label : ' + ',
	
	tooltip : 'add a new daily task',
	
	handler : function(place,macroName,params,wikifier,paramString,tiddler){
		var b = createTiddlyButton(place,this.label,this.tooltip,this.onclick,'button dailytaskbutton');
		b.place = place;
	},
	
	onclick : function(e) {
		if (!e) var e = window.event;
		var t = resolveTarget(e);
		if(! t.div) {
			var d = createTiddlyElement(null,'div',null,'newdailytaskform');
			t.parentNode.insertBefore(d,t.nextSibling);
			var input = createTiddlyElement(d,'input',null,'dailytaskinput');
			t.div = d;
			input.value = 'enter new task';
			input.select();
			input.focus();
			createTiddlyElement(d,'br');
			createTiddlyButton(d,'cancel','cancel',function(){removeNode(d); t.div = null;return false;});
			createTiddlyButton(d,'save','save this new daily task',function(e){if(!e) var e = window.event; removeNode(d); t.div = null; config.macros.newdailytask.save(input.value); story.refreshTiddler(story.findContainingTiddler(t).getAttribute('tiddler'),null,true); return false;});
		}
		return false;
	},
	
	removeNode : function (m) {
		removeChildren(m);
		m.parentNode.removeChild(m);
	},
	
	save : function (task) {
		if (store.getTiddler(task)) {
			alert('a tiddler with this name already exists');
		}
		var tiddler = new Tiddler(task);
		tiddler.text = '<<dailytask>>';
		tiddler.tags.push('dailytask');
		tiddler.modifier = config.options.txtUserName;
		store.addTiddler(tiddler);
		store.notify(tiddler.title,true);
	}
	
	
};

config.macros.dailytasks = {
	handler : function(place,macroName,params,wikifier,paramString,tiddler){
		store.addNotification("dailyTaskStyles",refreshStyles);
		store.notify("dailyTaskStyles");
		var dailytasks = store.getTaggedTiddlers('dailytask');
		var out = [];
		out.push("|dailytasksholder|k");
		out.push("|Daily Tasks|c");
		for (var i=0; i<dailytasks.length;i++) {
			out.push("|<<deletedailytask " + dailytasks[i].title + ">>|[[" + dailytasks[i].title + "]] |<<dailytask [[" + dailytasks[i].title + "]]>>|");
		}
		out.push("|<br><<newdailytask>>|>|>|");
		wikify(out.join('\n'),place);
	}
	
};

config.macros.dailytask = {

	handler : function(place,macroName,params,wikifier,paramString,tiddler) {
		var tiddler = params[0] ? store.getTiddler(params[0]) : tiddler;
		var theTable = createTiddlyElement(place,"table",null,"dailytasks");
		var theBody = createTiddlyElement(theTable,"tbody");
		var theRow = createTiddlyElement(theBody,"tr");
	
		var db = store.getValue(tiddler,'dailytasks');
		var days = db? db.split(',') : [];
		for (var i=9; i>-1; i--) {
			var date = getOffsetDate(i);
			var datestr = date.formatString('0DD.0MM.YY');
			var done = days.contains(datestr);
			var na = !done  && (tiddler.created > date);
			var className = done? 'taskcomplete' : (na || i==0 ? (i==0?'tasktoday' : 'tasknewer') : 'taskmissed');
	
			var box = createTiddlyElement(theRow,"td",null,"navlinkcell "+className," ");
			box.setAttribute('date',datestr);
			box.setAttribute('task',tiddler.title);
			box.title = datestr + ' (' + date.formatString('ddd') + ')';
			box.onclick = this.toggleStatus;
		}
	},
	
	toggleStatus : function(e) {
		if(!e) var e = window.event;
		var t = resolveTarget(e);
		var val = t.getAttribute('date');
		var tiddler = t.getAttribute('task');
		var db = store.getValue(tiddler,'dailytasks');
		var days = db? db.split(',') : [];
		if (days.contains(val))
			days.remove(val);
		else
			days.push(val);
		store.setValue(tiddler,'dailytasks',days.join(','));
		story.refreshTiddler(story.findContainingTiddler(t).getAttribute('tiddler'),null,true);
		return false;
	}
};

//add style as a shadow tiddler
merge(config.shadowTiddlers,{
	dailyTaskStyles: "table.dailytasksholder table.dailytasksheader td {border:1px solid #000; padding:2px;} \n\
td.taskmissed {background:red;} \n\
td.taskcomplete {background:yellow;} \n\
/* td.tasknewer {visibility:hidden;} */ \n\
table.dailytasksholder table.dailytasks td.tasknewer, table.dailytasks td.tasknewer ,td.tasknewer {border:1px solid #ccc; background:#eee;} \n\
table.dailytasks, table.dailytasksheader {border-collapse:separate; border:none;} \n\
table.dailytasksholder table.dailytasks td, table.dailytasks td {border:1px solid #000; padding:10px;} \n\
table.dailytasksholder table.dailytasks {margin:0 10px;} \n\
table.dailytasksholder caption {font-size:200%; font-weight:bold;} \n\
table.dailytasksholder {border:none;} \n\
table.dailytasksholder td, table.dailytasksholder tr {border:none; padding: 0;} \n\
.newdailytaskform { padding : 10px; margin : 10px; background: #eee; } \n\
.newdailytaskform input {padding:5px; margin:5px;} \n\
.dailytaskbutton.button {color:#999; border:1px solid #999; padding:2px 5px; margin:5px;} \n\
.deletedailytask.button {padding:2px 5px; margin:5px; color:#aaa; border:1px solid #999;} \n\
table.dailytasksholder table.dailytasks td.tasktoday, table.dailytasks td.tasktoday {border:2px solid #000;}"});
[[BlogPostIdeas]]
[[DailyTasks]]
[[Important things for March '08]]
[[Taskiness]]
//{{{

// 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;
};

//}}}
/***
|Name|FullScreenPlugin|
|Created by|SaqImtiaz|
|Location|http://tw.lewcid.org/#FullScreenPlugin|
|Version|1.1|
|Requires|~TW2.x|
!Description:
Toggle between viewing tiddlers fullscreen and normally. Very handy for when you need more viewing space.

!Demo:
Click the ↕ button in the toolbar for this tiddler. Click it again to turn off fullscreen.

!Installation:
Copy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.
Edit the ViewTemplate to add the fullscreen command to the toolbar.

!History:
*25-07-06: ver 1.1
*20-07-06: ver 1.0

!Code
***/
//{{{
var lewcidFullScreen = false;

config.commands.fullscreen =
{
            text:" ↕ ",
            tooltip:"Fullscreen mode"
};

config.commands.fullscreen.handler = function (event,src,title)
{
            if (lewcidFullScreen == false)
               {
                lewcidFullScreen = true;
                setStylesheet('#sidebar, .header, #mainMenu{display:none;} #displayArea{margin:0em 0 0 0 !important;}',"lewcidFullScreenStyle");
               }
            else
               {
                lewcidFullScreen = false;
                setStylesheet(' ',"lewcidFullScreenStyle");
               }
}

config.macros.fullscreen={};
config.macros.fullscreen.handler =  function(place,macroName,params,wikifier,paramString,tiddler)
{
        var label = params[0]||" ↕ ";
        var tooltip = params[1]||"Fullscreen mode";
        createTiddlyButton(place,label,tooltip,config.commands.fullscreen.handler);
}

var lewcid_fullscreen_closeTiddler = Story.prototype.closeTiddler;
Story.prototype.closeTiddler =function(title,animate,slowly)
{
           lewcid_fullscreen_closeTiddler.apply(this,arguments);
           if (story.isEmpty() && lewcidFullScreen == true)
              config.commands.fullscreen.handler();
}


Slider.prototype.lewcidStop = Slider.prototype.stop;
Slider.prototype.stop = function()
{
           this.lewcidStop();
           if (story.isEmpty() && lewcidFullScreen == true)
              config.commands.fullscreen.handler();
}
//}}}
[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:

* ''work offline:'' Jon’s little bit of work and Liz’s note on it appear on both computers separately, so they can both walk away with their ~TiddlyWikis and only sync-up when they get back online
* ''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.
config.macros.importWorkspaceMulti = {};

config.macros.importWorkspaceMulti.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	// this uses the existing importWorkspace mechanism to import each systemServer tiddler returned by the filter in params[0], where the filter is of the form used by Tiddlywiki.prototype.filterTiddlers
	// currently only supports tag filters i.e. filters of the form [tag[filterTag]]
	// protects against importing your own feeds
	console.log("in handler");
	if (params[0]) {
		var filter = params[0];
	}
	this.importAll(filter);
};

config.macros.importWorkspaceMulti.importAll = function(filter) {
	// 23/10/07: The next two lines should work when the core filterTiddlers function is sorted out
	// var extended_filter = filter+" [tag[systemServer]]";
	// var workspace_tiddlers = store.filterTiddlers(extended_filter);
	var sysServer_tiddlers = store.getTaggedTiddlers("systemServer");
	var workspace_tiddlers = [];
	if(!filter) {
		// if there is no filter, just import from all tiddlers with a systemServer tag
		// prompt first though...
		var sysList = "";
		for (var i=0;i<sysServer_tiddlers.length;i++) {
			sysList = sysList + "\n" + sysServer_tiddlers[i].title;
			workspace_tiddlers.push(sysServer_tiddlers[i]);
		}
		if(!confirm("are you sure you want to import from all these tiddlers?\n\n" + sysServerTiddlers)) {
			return false;
		};
	} else {
		console.log(sysServer_tiddlers);
		console.log(filter);
		var regex_tagFilter = /\[tag\[([\w ]+?)\]\]/mg;
		var match = regex_tagFilter.exec(filter);
		if (match) {
			var mini_filter = match[1];
			console.log(match);
		} else {
			console.log("no match");
		}
		for (var i=0;i<sysServer_tiddlers.length;i++) {
			// ownPath/feedPath specific to .html/.xml
			// can make feedPath general to any extension if we improve the regex
			// we assume ownPath is always a .html file
			var ownPath = document.location.href.replace(/.html$/,"");
			console.log(ownPath);
			var feedPath = store.getTiddlerSlice(sysServer_tiddlers[i].title,"URL").replace(/.xml$/,"");
			console.log(feedPath);
			if (ownPath != feedPath) {
				if (sysServer_tiddlers[i].isTagged(mini_filter)) {
					console.log("tag match with: " + sysServer_tiddlers[i].title);
					workspace_tiddlers.push(sysServer_tiddlers[i]);
				}
			}
		}
	}
	console.log(workspace_tiddlers);
	// run through the systemServer tiddlers, importing as we go
	for (var i=0;i<workspace_tiddlers.length;i++) {
		var title = workspace_tiddlers[i].title;
		var 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');
		console.log("about to call importWorkspace with: " + title);
		config.macros.importWorkspace.getTiddlers.call(config.macros.importWorkspace,fields);
	}
};
/***
|''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 = 'published';}
if(config.options.chkImportWorkspaceOnStartup == undefined)
	{config.options.chkImportWorkspaceOnStartup = false;}
if(config.options.txtImportLabel == undefined)
	{config.options.txtImportLabel = 'Check for new stuff';}


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, and 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'];
		var ret = adaptor.openHost(context.host,context,null,config.macros.importWorkspace.openHostCallback);
		if(typeof ret == "string") {
			displayMessage("error with http request: " + ret);
		}
	}
};

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);
	if (context.status !== true) {
			displayMessage("error opening feed: " + context.statusText);
	}
	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;
		if (tiddlers.length === 0) {
			displayMessage("nothing to import from " + context.adaptor.host);
		}
		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);
			// if the tiddler exists locally, don't overwrite unless the text is different
			if(!local_tiddler || local_tiddler.text != tiddler.text) {
				context.adaptor.getTiddler(tiddler.title,null,null,config.macros.importWorkspace.getTiddlerCallback);
				import_count++;
				displayMessage("writing tiddler: " + tiddler.title);
			}
		}
		if (import_count === 0) {
			displayMessage("nothing imported from " + context.adaptor.host);
		}
	}
};


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'
//}}}
* sorting out contacts into who is interested in open source and should be targeted with blog posts
* corporate open source manifesto
* tiddlytemplating (TT) SEO work with Simon (and Fred)
* TT core changes with Fred (and Simon)
* TT template gallery with Nick
* TiddlyBlogging
* changing blog into categories: cartoons, tiddlywiki, javascript, book reviews, mashups, ???
* supporting the TiddlyWiki groups more - why? to make myself more credible?
* getting into a rhythm of checking TW groups, select blogs, and commenting
* writing good blog posts and starting drafts as I think of them instead of needing to do one-offs
* finding out where the regular geek events in London and making a programme of attendance, and sharing with Osmo so they know
* suggesting visual display of Osmo commitments (in permanent pen, so you can strike-through but not erase)
# If you are planning to use ~TiddlyChatter with your ~TiddlySpot ~TiddlyWiki, make sure to remember to download a local copy so you can import tiddlers
# 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:''|ListRelatedPlugin|
|''Description:''|Displays a filtered list of tiddlers along with links to related tiddlers|
|''Author:''|JeremyRuston|
|''Source:''|http://www.osmosoft.com/#ListRelatedPlugin |
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/JeremyRuston/plugins/ListRelatedPlugin.js |
|''Version:''|0.0.4|
|''Date:''|Nov 27, 2006|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License:''|[[BSD License|http://www.opensource.org/licenses/bsd-license.php]] |
|''~CoreVersion:''|2.2|


ListRelatedPlugin displays a list of tiddlers, each of which is followed by a sublist of related tiddlers. What
constitutes a "related tiddler" is customisable, as is the template used to display the list and sublist items.

This version ships with handlers for relationships to support RippleRap's convention that a tiddler called
"<title> from <author>" is taken to be a comment by "<author>" on a tiddler called "<title>".

- "raps": returns all tiddlers that are comments on the specified one
- "rapped": if the specified tiddler is a comment, returns the tiddler that it applies to

Usage:

{{{
<<listRelated filter:"[tag[note]]" rel:"raps" template:"MyTemplate" subtemplate:"MySubTemplate">>>
}}}

The parameters are as follows:

|Parameter |Description |Default |
|filter |A tiddler filter expression that filters and sorts the tiddlers to be listed |(none) |
|rel |The relationship of the sublist |raps |
|template |A template to determine how each tiddler in the list is laid out |"<<view title>>" |
|subtemplate |A template to determine how each tiddler in the sublist is laid out |"<<view title>> by <<view modifier>>" |

The optional template parameters specify the name of a tiddler that contains the template to be used. The template is specified
in TiddlyWiki format (not HTML), and can use the <<view>> macro to extract particular fields. For example:

{{{
Item ''<<view title>>'' by <<view modifier>>
^^last saved on <<view modified date>>^^
}}}

***/

//{{{
//# Ensure that the plugin is only installed once.
if(!version.extensions.ListRelatedPlugin) {
version.extensions.ListRelatedPlugin = {installed:true};

config.relationships = {
	raps: {
		text: "raps",
		prompt: "Tiddlers that are comments on this one",
		getRelatedTiddlers: function(store,title) {
			var from = title + " from";
			var len = from.length;
			var tiddlers = [];
			store.forEachTiddler(function(title,tiddler) {
				if(title.substr(0,len)==from)
					tiddlers.push(title);
			});
			return tiddlers;
		}
	},
	rapped: {
		text: "rapped",
		prompt: "Tiddlers that are commented on by this one",
		getRelatedTiddlers: function(store,title) {
			var tiddlers = [];
			var re = "^(.+) from (.+)$";
			var regexp = new RegExp(re,"mg");
			regexp.lastIndex = 0;
			var match = regexp.exec(title);
			if(match)
				tiddlers.push(match[1]);
			return tiddlers;
		}
		
	}
};

config.macros.listRelated = {
	defaultRelationship: "raps",
	defaultTemplate: "<<view title>>",
	defaultSubTemplate: "<<view title>> by <<view modifier>>"
};

config.macros.listRelated.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var filter = getParam(params,"filter","");
	var tag = getParam(params,"tag","");
	var field = getParam(params,"sort","");
	var relationship = getParam(params,"rel",this.defaultRelationship);
	var template = getParam(params,"template",null);
	if(template)
		template = store.getTiddlerText(template,this.defaultTemplate);
	else
		template = this.defaultTemplate;
	var subTemplate = getParam(params,"subtemplate",null);
	if(subTemplate)
		subTemplate = store.getTiddlerText(subTemplate,this.defaultSubTemplate);
	else
		subTemplate = this.defaultSubTemplate;
	var tiddlers = [];
	if(tag) {
		store.forEachTiddler(function(title,tiddler) {
			if(tiddler.isTagged(tag))
				tiddlers.push(tiddler);
		});
		if(field) {
			if(TiddlyWiki.standardFieldAccess[field])
				tiddlers.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
			else
				tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -1 : (a.fields[field] == b.fields[field] ? 0 : +1);});
		}
	} else {
		tiddlers = store.filterTiddlers(filter);
	}
	for(var t=0; t<tiddlers.length; t++) {
		var tiddler = tiddlers[t];
		var wrapper = createTiddlyElement(place,"div",null,"listRelatedTiddler");
		var wikifier = new Wikifier(template,formatter,null,tiddler);
		wikifier.subWikifyUnterm(wrapper);
		var rel = config.relationships[relationship].getRelatedTiddlers(store,tiddler.title);
		for(var r=0; r<rel.length; r++) {
			var sub = createTiddlyElement(wrapper,"div",null,"listRelatedSubTiddler");
			wikifier = new Wikifier(subTemplate,formatter,null,store.fetchTiddler(rel[r]));
			wikifier.subWikifyUnterm(sub);
		}
	}
};

} //# end of 'install only once'
//}}}
<<ListTemplate filter:"[tag[docs]]" template:"RssTemplate">>
/***
|''Name:''|ListTemplatePlugin|
|''Description:''|Displays a filtered list of tiddlers along with links to related tiddlers|
|''Author:''|JeremyRuston|
|''Source:''|http://www.osmosoft.com/#ListTemplatePlugin |
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/JeremyRuston/plugins/ListTemplatePlugin.js |
|''Version:''|0.0.4|
|''Date:''|Nov 27, 2006|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License:''|[[BSD License|http://www.opensource.org/licenses/bsd-license.php]] |
|''~CoreVersion:''|2.2|


ListTemplatePlugin displays a list of tiddlers, each of which is followed by a sublist of related tiddlers. What
constitutes a "related tiddler" is customisable, as is the template used to display the list and sublist items.

This version ships with handlers for relationships to support RippleRap's convention that a tiddler called
"<title> from <author>" is taken to be a comment by "<author>" on a tiddler called "<title>".

- "raps": returns all tiddlers that are comments on the specified one
- "rapped": if the specified tiddler is a comment, returns the tiddler that it applies to

Usage:

{{{
<<ListTemplate filter:"[tag[note]]" rel:"raps" template:"MyTemplate" subtemplate:"MySubTemplate">>>
}}}

-------
What I'm aiming for here with any rewrite:

{{{
<<ListTemplate filter:"[tag[!excludeLists]]" template:"RssTemplate" params>>
}}}

It needs to be able to support recursion, so that subTemplates make sense. We can store data needed for subTemplates in params so it can be passed through to subTemplates. The content is passed in either through a filter for tiddlers, a tag to get tiddlers, or a space-separated list. We'll support more as we go along, but this is what we need for RSS.
-------

The parameters are as follows:

|Parameter |Description |Default |
|filter |A tiddler filter expression that filters and sorts the tiddlers to be listed |(none) |
|rel |The relationship of the sublist |raps |
|template |A template to determine how each tiddler in the list is laid out |"<<view title>>" |
|subtemplate |A template to determine how each tiddler in the sublist is laid out |"<<view title>> by <<view modifier>>" |

The optional template parameters specify the name of a tiddler that contains the template to be used. The template is specified
in TiddlyWiki format (not HTML), and can use the <<view>> macro to extract particular fields. For example:

{{{
Item ''<<view title>>'' by <<view modifier>>
^^last saved on <<view modified date>>^^
}}}

***/

//{{{
//# Ensure that the plugin is only installed once.
if(!version.extensions.ListTemplatePlugin) {
version.extensions.ListTemplatePlugin = {installed:true};

config.macros.ListTemplate = {
	defaultTemplate: "<rss><<view title>></rss>",
};

config.macros.ListTemplate.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	params = paramString.parseParams("anon",null,true,false,false);
	var filter = getParam(params,"filter","");
	var tag = getParam(params,"tag","");
	var field = getParam(params,"sort","");
	var template = getParam(params,"template",null);
	var list = getParams(params,"list","");
	if(template)
		template = store.getTiddlerText(template,this.defaultTemplate);
	else
		template = this.defaultTemplate;
	var tiddlers = [];
	if(tag) {
		store.forEachTiddler(function(title,tiddler) {
			if(tiddler.isTagged(tag))
				tiddlers.push(tiddler);
		});
		// Q: Why are we only sorting if we've searched by tag?
		if(field) {
			if(TiddlyWiki.standardFieldAccess[field])
				tiddlers.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
			else
				tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -1 : (a.fields[field] == b.fields[field] ? 0 : +1);});
		}
	} else if(list) {
		tiddlers = list.readBracketedList();
	} else {
		tiddlers = store.filterTiddlers(filter);
	}
	if(tiddlers[0] instanceof Tiddler) {
		for(var t=0; t<tiddlers.length; t++) {
			var tiddler = tiddlers[t];
			var wrapper = createTiddlyElement(place,"div",null,"ListTemplateTiddler");
			var wikifier = new Wikifier(template,formatter,null,tiddler);
			wikifier.subWikifyUnterm(wrapper);
		}
	} else {
		// now we assume we are working with a simple array of text
		for (var t=0; t<tiddlers.length; t++) {
			var t = tiddlers[t];
			var wrapper = createTiddlyElement(place,"div",null,"ListTemplateContent");
			// in this line, tiddler refers to the parent tiddler running the macro
			// Q: is this right?
			wikify(t,wrapper,null,tiddler);
		}
	}
};

} //# end of 'install only once'
//}}}
<<gradient vert #f9f9f9 #ffffff #ffffff #f9f9f9  #eeeeee'>><<tabs txtMainTab "Timeline" "Timeline" TabTimeline "A - Z" "All tiddlers in alphabetical order" TabAll "Tags" "All tags" TabTags "Missing" "Missing tiddlers" TabMoreMissing "Shadowed" "Shadowed tiddlers" TabMoreShadowed "TiddlySpot" "TiddlySpot options" TspotControls>>
/***
|''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 to Tiddlyspot]]
GettingStarted
TiddlyChatter

<<showUpdates>>
The thing about [[Facebook|http://www.facebook.com/]], for all its yummy and addictive gloriousness, is that it's been around a while. This is worrying. Friendster, MySpace have both experienced a huge and short-lived boom period, followed by customers migrating to the next, best and shiny thing. Given that Bebo has been the #1 social network in the UK for [[some time now|http://www.brandrepublic.com/login/News/731494/]], and with the [[recent news|http://www.news.com/8301-13577_3-9809502-36.html?tag=nefd.blgs]] that they, along with several others, are joining the Google-led "[[Open Social|http://code.google.com/apis/opensocial/]]" platform, the time has come for Facebookers to get worried.

What am I talking about? The problem is Facebook's infamous "[[walled garden|http://www.kottke.org/07/06/facebook-is-the-new-aol]]" approach to storing its customers' data. It's very easy to get your information into Facebook, but it's pretty tricky to get it out. [[Some|http://simonmcmanus.wordpress.com/2007/10/23/facebook-screen-scraping-get-your-friends-back-from-facebook-and-into-tiddlywiki/]] [[people|http://www.techcrunch.com/2007/10/23/with-friendcsv-data-sneaks-out-facebooks-back-door/]] have [[built|http://labnol.blogspot.com/2007/10/to-download-contacts-from-facebook-to.html]] screen-scrapers that are exposing a chink in Facebook's armour, but using these violates the Terms & Conditions of Facebook, so you can be banned - it's explicit that Facebook doesn't want you to leave. When you do want to, you have to start again from scratch, building your network and your identity within it:

<<<
The Company may terminate your membership [...] for any reason, or no reason, at any time...
<<<

This cycle shouldn't continue every time taste changes or new fad pops up. The Open Social approach helps application developers by letting them build once and run their application in any social network that supports the Open Social interface, but this doesn't help the people locked into the social networks. What we need is a way for people to log-in once and be able to get involved with any social network.

Is this what people want? The Economist ran [[an article|http://www.economist.com/research/articlesBySubject/displaystory.cfm?subjectid=348963&story_id=9990635]] recently about Facebook and the phenomenon of the "social graph", postulating that social networks don't benefit from the same "network effects" as a telephone network, which becomes more useful as more phones are connected. The suggestion was that social networks fragment into small, independent groups. There is [[plenty|http://www.bbsonline.org/documents/a/00/00/05/65/bbs00000565-00/bbs.dunbar.html]] of [[research|http://www.bcg.com/strategy_institute_gallery/gorilla2.html]] that shows that the natural upper limit for social groups is around 150, which for a network like Facebook, where the participants are individuals, rather than the example of bands on MySpace that might have thousands of friends, this is quite an insight. When a social network uses its millions of members as a selling point, it is really pulling the wool over our eyes.

With this in mind, I set to thinking about whether a decentralized social network makes sense. By decentralized, I mean independent of any one company or particular implementation in software - email is a very successful decentralized system. In essence, I think social networking boils down to //people//, a //flow of information// and a //group identity//. Two small groups can happily exist independently of one another without there needing to be this great, overarching flow between them. I don't need a million people in my social network for it to be really, really good. All I need is to be able to get involved with the activities of other people, and it shouldn't matter what their choice of social networking software is.

Thinking about it, the only people who benefit from having millions of people inside a walled garden are the companies who own it:
* Advertising - in our social network, we don't want it
* Company Growth - in our social network, we don't care about it
* Monitoring Usage - in our social network, we don't like it

Open Social is going to let developers build applications that we can use inside any social network; next we need something analogous for flows of information between them. With both of these, maybe even a group's social identity can be decoupled from the personal software choice its members make.
/***
|''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"+config.options.txtUserName,
	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++;
			}
		}
		// sort the notes by modified date to get the most recent in notes[0]
		notes.sort(function(a,b){
			return a.modified > b.modified ? -1 : (a.modified == b.modified ? 0 : 1);
		});
		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 (latest by xxx)"
		// if you added the notes, show "Notes (latest by you)"
		// REMOVED: var notes_tiddler = store.fetchTiddler(tiddler.title+"-"+suffix);
		var heading_extension = "";
		if (notes[0] && notes[0].modifier) {
			if (notes[0].modifier != config.options.txtUserName) {
				heading_extension = " (latest by " + notes[0].modifier + ")";
			} else {
				heading_extension = " (latest by you)";
			}
			wikify("!!"+heading+heading_extension+"\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
// %/
<!--{{{-->
<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='controlsContainer' refresh='content' tiddler='ControlsContainer'></div>
<div id='mainMenu' refresh='content' tiddler='MainMenu'>
</div>
<div id='sidebar'>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
<div id
</div>
<!--}}}-->
PasswordOptionPlugin extends the core Options with a non encrypted password type.

Notice:
*How a style can be specified for a specific option in StyleSheet

----
Test Password: <<option pasPassword myPasOptionInput desc:no >>
/***
|''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"
	});
*/
//}}}
{{topClass{content}}}

{{customClass{hello there}}}

{{vevent{{{time{<<dtstart rr_session_starttime '0hh:0mm'>> - <<dtend rr_session_endtime '0hh:0mm'>> }}} {{title summary{<<view title link>>}}}}}}

Hmm... they don't seem to work - clashing with the code markup plugin I've got?
/***
|''Name:''|Plugin: Syntaxify|
|''Description:''|Performs syntax highlighting on CSS, JavaScript, and HTML/XML|
|''Version:''|1.2|
|''Date:''|January 27, 2007|
|''Source:''|http://bob.mcelrath.org/syntaxify.html|
|''Author:''|BobMcElrath|
|''Email:''|my first name at my last name dot org|
|''License:''|[[GPL open source license|http://www.gnu.org/licenses/gpl.html]]|
|''~CoreVersion:''|2.0.0|
!Description
This plugin will syntax highlight ("pretty-print") source code used by TiddlyWiki.  To activate CSS markup, enclose the code in the CSS code in the delimiters 
<html><code>
/&#42;{{{&#42;/<br/>
/&#42; CSS code here &#42/<br/>
/&#42;}}}&#42;/<br/>
</code></html>
To activate XML markup, enclose your HTML/XML in the delimiters
<html><code>
&lt;!--{{{--&gt;<br/>
&lt;!-- XML/HTML code here --&gt;<br/>
&lt;!--}}}--&gt;<br/>
</code></html>
And to activate JavaScript markup, enclose your code in the delimiters
<html><code>
//{{{<br/>
// JavaScript code here.<br/>
//}}}<br/>
</code></html>

In addition, all of the above languages can be syntaxified by using the custom class formatter
<html><code>
{{foo{<br/>
    code for language "foo" here<br/>
}}}<br/>
</code></html>
where {{{foo}}} is the name of the language: {{{css}}}, {{{javascript}}}, or {{{xml}}}.  This plugin can be extended with new languages by creating a data structure like those below (in {{{syntaxify.languages}}} and then calling {{{syntaxify.addLanguages}}}.
!History
* 1.2 Release
** Now syntaxifies in-line style code (thanks [[Conal Elliott|http://conal.net]]).
** Fix multi-line comments in CSS.
** Consolidate customClassesHelper and monospacedByLineHelper which had lots of duplicated code.
** Fix autoLinkWikiWords bug when using custom classes and the tag formatter.
** Fix compatability problems between 2.1 and 2.0 (termRegExp vs. terminator)
* 1.1 Release
** Rewrite things to make it easier to add new languages.
** Override customClasses to syntaxify when the class corresponds to a known language.
** TiddlyWiki 2.1 beta compatibility
* 1.0.2 Release
** Don't use {{{class}}} as a variable name, dummy.
* 1.0.1 Release
** Simplified stylesheet and removed line numbering.
** Fixed highlighting when <html><code>&#42;/</code></html> appeared at the beginning of a line.
** Fixed blank lines not being shown if {{{list-style-type: none}}} was turned on.
** Small speedups
* 1.0.0 Initial Release
!Code
***/
//{{{
version.extensions.Syntaxify = { major: 1, minor: 2, revision: 0, date: new Date("2007","01","27"),
	source: "http://bob.mcelrath.org/syntaxify.html"
};

var syntaxify = {};

syntaxify.regexpSpace = new RegExp(" ", "mg");
syntaxify.regexpTab = new RegExp("\t", "mg");
syntaxify.regexpAmp = new RegExp("&","mg");
syntaxify.regexpLessThan = new RegExp("<","mg");
syntaxify.regexpGreaterThan = new RegExp(">","mg");
syntaxify.regexpQuote = new RegExp("\"","mg");
syntaxify.regexpDoubleQuotedString = new RegExp("\"(?:\\\\.|[^\\\\\"])*?\"", "mg");
syntaxify.regexpSingleQuotedString = new RegExp("'(?:\\\\.|[^\\\\'])*?'", "mg");
syntaxify.regexpCSingleLineComment = new RegExp('//.*$', "g");
syntaxify.regexpCMultiLineComment 
    = new RegExp('/\\*(?:(?:.|(?:\\r)?\\n)(?!\\*/))*(?:.|(?:\\r)?\\n)?\\*/',"mg");
String.prototype.htmlListMono = function() {
    return(this.replace(syntaxify.regexpAmp,"&amp;")
               .replace(syntaxify.regexpLessThan,"&lt;")
               .replace(syntaxify.regexpGreaterThan,"&gt;")
               .replace(syntaxify.regexpQuote,"&quot;")
               .replace(syntaxify.regexpSpace,"&nbsp;")
               .replace(syntaxify.regexpTab,"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"));
}

syntaxify.handleSpanClass = function(w) {
    var match, lastPos=0;
    if(this.lookahead) {
        var lookaheadRegExp = new RegExp(this.lookahead,"mg");  
        lookaheadRegExp.lastIndex = w.matchStart;  
        var lookaheadMatch = lookaheadRegExp.exec(w.source);  
        if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {  
            createTiddlyText(w.output, lookaheadMatch[1]);
            var e = createTiddlyElement(w.output, "span", null, this.name);
            e.innerHTML = this.hasSpaces?lookaheadMatch[2].htmlListMono():lookaheadMatch[2];
        }
    } else {
        while((match = regexpNewLine.exec(w.matchText)) != null) {  // multi-line
            var alt = "";
            var e = createTiddlyElement(w.output, "span", null, this.name);
            e.innerHTML = this.hasSpaces?w.matchText.substr(lastPos,match.index-lastPos).htmlListMono()
                            :w.matchText.substr(lastPos,match.index-lastPos);
            if(w.output.className != "alt") alt = "alt";
            w.output = createTiddlyElement(w.output.parentNode, "li", null, alt);
            lastPos = match.index;
        } 
        var e = createTiddlyElement(w.output, "span", null, this.name);
        e.innerHTML = this.hasSpaces?w.matchText.substr(lastPos, w.matchText.length-lastPos).htmlListMono()
                        :w.matchText.substr(lastPos, w.matchText.length-lastPos)
    }
}

/* This is a shadow tiddler.  Do not edit it here.  Instead, open the tiddler StyleSheetSyntaxify 
 * and edit it instead.  (go to the toolbar on the right and select "More"->"Shadowed") */
config.shadowTiddlers.StyleSheetSyntaxify = "/*{{{*/\n"
+".viewer .syntaxify {\n"
+"         font-family: monospace;\n"
+"}\n"
+".viewer div.syntaxify {\n"
+"         background-color: #ffc;\n"
+"         border: 1px solid #fe8;\n"
+"         padding: 0.5em;\n"
+"         margin: 0 0 1em 0.5em;\n"
+"         font-size: 1.2em;\n"
+"         overflow: auto;\n"
+"}\n\n"
+".syntaxify ol {\n"
+"        margin: 0;\n"
+"        padding: 1px;\n"
+"        color: #2B91AF;\n"
+"}\n\n"
+".syntaxify ol li {\n"
+"       background-color: #ffc;\n"
+"       color: black;\n"
+"       list-style-type: none;\n"
+"/* An alternate style to enable line numbering -- remove the line above and uncomment below if desired */\n"
+"/*\n"
+"       list-style-type: 1;\n"
+"       border-left: 3px solid #fe8;\n"
+"       margin-left: 3.5em;\n"
+"*/\n"
+"}\n\n"
+"/* To disable alternating lines having a different colors, comment out the following line. */\n"
+".syntaxify ol li.alt { background-color: #ffe; }\n\n"
+".syntaxify ol li span { color: black; }\n"
+".syntaxify .singleLineComments { color: green; }\n"
+".syntaxify .multiLineComments { color: green; }\n"
+".syntaxify .multiLineComments1 { color: red; }\n"
+".syntaxify .tag { font-weight: bold; color: blue; }\n"
+".syntaxify .tagname { font-weight: bold; color: black; }\n"
+".syntaxify .attribute { color: rgb(127,0,85); }\n"
+".syntaxify .value { color: rgb(42,0,255); }\n"
+".syntaxify .keywords { color: #006699; }\n"
+".syntaxify .keywords1 { color: red; }\n"
+".syntaxify .delimiters { color: maroon; }\n"
+".syntaxify .delimiters1 { color: olive; }\n"
+".syntaxify .literals { color: maroon; }\n"
+".syntaxify .literals1 { color: blue; }\n"
+".syntaxify .literals2 { color: blue; }\n"
+".syntaxify .literals3 { color: #129; }\n"
+".syntaxify .identifiers { font-weight: bold; color: blue; }\n"
+".syntaxify .identifiers1 { font-weight: bold; color: black; }\n"
+"/*}}}*/";

store.addNotification("StyleSheetSyntaxify",refreshStyles);
config.shadowTiddlers.ViewTemplate = "<!--{{{-->\n"+config.shadowTiddlers.ViewTemplate+"\n<!--}}}-->";
config.shadowTiddlers.EditTemplate = "<!--{{{-->\n"+config.shadowTiddlers.EditTemplate+"\n<!--}}}-->";
config.shadowTiddlers.PageTemplate = "<!--{{{-->\n"+config.shadowTiddlers.PageTemplate+"\n<!--}}}-->";
config.shadowTiddlers.StyleSheetPrint = "/*{{{*/\n"+config.shadowTiddlers.StyleSheetPrint+"\n/*}}}*/";

syntaxify.commonFormatters = [
{   name: "spaces",
    match: "[ \\t]+",
    handler: function(w) {
        w.output.innerHTML += w.matchText.htmlListMono();
    }
},{ name: "newline",
    match: "\\n",
    handler: function(w) {
        var alt = ""
        if(w.output.className != "alt") alt = "alt";
        if(!w.output.hasChildNodes()) w.output.innerHTML = "&nbsp;";
        w.output = createTiddlyElement(w.output.parentNode, "li", null, alt);
    }
}];

syntaxify.xmlTagFormatters = syntaxify.commonFormatters;
syntaxify.xmlTagFormatters = syntaxify.xmlTagFormatters.concat([
{   name: "tagname",
    match: '<[/\\?]?\\s*(?:[\\w-\\.]+)',
    lookahead: '(<[/\\?]?\\s*)([\\w-\\.]+)',
    handler: syntaxify.handleSpanClass
},{
    name: "attribute-value",
    match: '[\\w-\.]+(?:\\s*=\\s*"[^"]*?"|\'[^\']*?\'|\\w+)?',
    lookahead: '([\\w-\.]+)(?:(\\s*=\\s*)("[^"]*?"|\'[^\']*?\'|\\w+))?',
    handler: function(w) {
        var lookaheadRegExp = new RegExp(this.lookahead,"mg");  
        lookaheadRegExp.lastIndex = w.matchStart;  
        var lookaheadMatch = lookaheadRegExp.exec(w.source);  
        if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {  
            var e = createTiddlyElement(w.output, "span", null, "attribute");
            e.innerHTML = lookaheadMatch[1];
            if(lookaheadMatch[2]) {
                var e = createTiddlyElement(w.output, "span");
                e.innerHTML = lookaheadMatch[2].htmlListMono();
                e = createTiddlyElement(w.output, "span", null, "value");
                e.innerHTML = lookaheadMatch[3].htmlListMono();
            }
        }
        w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;  
    }
}]);


// A rather huge data structure to store languages.  Add to it!
syntaxify.languages = {
javascript: {
    singleLineComments: [[syntaxify.regexpCSingleLineComment.source]],
    multiLineComments: [[syntaxify.regexpCMultiLineComment.source]],
    keywords: [['abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char',
        'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
        'double', 'else', 'enum', 'export', 'extends', 'false', 'final',
        'finally', 'float', 'for', 'function', 'goto', 'if', 'implements',
        'import', 'in', 'instanceof', 'int', 'interface', 'long', 'native',
        'new', 'null', 'package', 'private', 'protected', 'public', 'return',
        'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw',
        'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void',
        'volatile', 'while', 'with']
    ],
    literals: [
        [syntaxify.regexpSingleQuotedString.source],
        [syntaxify.regexpDoubleQuotedString.source],
        ["\\b\\d+(?:\\.\\d+(?:[eE][\\+-]\\d+)?)?\\b"] // Numbers
    ],
    delimiters: [["[\\{\\}]"],["[\\(\\)]"],["[\\[\\]]"]]
}, 
css: {
    multiLineComments: [[syntaxify.regexpCMultiLineComment.source]],
    keywords: [
        // Keywords appearing on the LHS of expressions
        ['ascent', 'azimuth', 'background-attachment', 'background-color',
        'background-image', 'background-position',  'background-repeat',
        'background', 'baseline', 'bbox', 'border-collapse', 'border-color',
        'border-spacing', 'border-style', 'border-top',  'border-right',
        'border-bottom', 'border-left', 'border-top-color',
        'border-right-color', 'border-bottom-color', 'border-left-color',
        'border-top-style', 'border-right-style', 'border-bottom-style',
        'border-left-style', 'border-top-width', 'border-right-width',
        'border-bottom-width', 'border-left-width', 'border-width', 'border',
        'bottom', 'cap-height', 'caption-side', 'centerline', 'clear', 'clip',
        'color',  'content', 'counter-increment', 'counter-reset', 'cue-after',
        'cue-before', 'cue', 'cursor', 'definition-src', 'descent',
        'direction', 'display', 'elevation', 'empty-cells', 'float',
        'font-size-adjust', 'font-family', 'font-size', 'font-stretch',
        'font-style', 'font-variant', 'font-weight', 'font',  'height', 'left',
        'letter-spacing', 'line-height', 'list-style-image',
        'list-style-position', 'list-style-type', 'list-style', 'margin-top',
        'margin-right', 'margin-bottom', 'margin-left', 'margin',
        'marker-offset', 'marks', 'mathline', 'max-height', 'max-width',
        'min-height', 'min-width', 'orphans',  'outline-color',
        'outline-style', 'outline-width', 'outline', 'overflow', 'padding-top',
        'padding-right', 'padding-bottom', 'padding-left', 'padding', 'page',
        'page-break-after', 'page-break-before', 'page-break-inside', 'pause',
        'pause-after', 'pause-before', 'pitch', 'pitch-range', 'play-during',
        'position', 'quotes', 'richness', 'right', 'size', 'slope', 'src',
        'speak-header', 'speak-numeral', 'speak-punctuation', 'speak',
        'speech-rate', 'stemh', 'stemv', 'stress', 'table-layout',
        'text-align', 'text-decoration', 'text-indent', 'text-shadow',
        'text-transform', 'unicode-bidi', 'unicode-range', 'units-per-em',
        'vertical-align', 'visibility', 'voice-family', 'volume',
        'white-space', 'widows', 'width', 'widths', 'word-spacing', 'x-height',
        'z-index'],
        // Treat !important as a different kind of keyword
        ["important"]
    ],
    literals: [
        // Literals appearing on the RHS of expressions
        ['above', 'absolute', 'all', 'always', 'aqua', 'armenian', 'attr',
        'aural', 'auto', 'avoid', 'baseline', 'behind', 'below',
        'bidi-override', 'black', 'blink', 'block', 'blue', 'bold', 'bolder',
        'both', 'bottom', 'braille', 'capitalize', 'caption', 'center',
        'center-left', 'center-right', 'circle', 'close-quote', 'code',
        'collapse', 'compact', 'condensed', 'continuous', 'counter',
        'counters', 'crop', 'cross', 'crosshair', 'cursive', 'dashed',
        'decimal', 'decimal-leading-zero', 'default', 'digits', 'disc',
        'dotted', 'double', 'embed', 'embossed', 'e-resize', 'expanded',
        'extra-condensed', 'extra-expanded', 'fantasy', 'far-left',
        'far-right', 'fast', 'faster', 'fixed', 'format', 'fuchsia', 'gray',
        'green', 'groove', 'handheld', 'hebrew', 'help', 'hidden', 'hide',
        'high', 'higher', 'icon', 'inline-table', 'inline', 'inset', 'inside',
        'invert', 'italic', 'justify', 'landscape', 'large', 'larger',
        'left-side', 'left', 'leftwards', 'level', 'lighter', 'lime',
        'line-through', 'list-item', 'local', 'loud', 'lower-alpha',
        'lowercase', 'lower-greek', 'lower-latin', 'lower-roman', 'lower',
        'low', 'ltr', 'marker', 'maroon', 'medium', 'message-box', 'middle',
        'mix', 'move', 'narrower', 'navy', 'ne-resize', 'no-close-quote',
        'none', 'no-open-quote', 'no-repeat', 'normal', 'nowrap', 'n-resize',
        'nw-resize', 'oblique', 'olive', 'once', 'open-quote', 'outset',
        'outside', 'overline', 'pointer', 'portrait', 'pre', 'print',
        'projection', 'purple', 'red', 'relative', 'repeat', 'repeat-x',
        'repeat-y', 'ridge', 'right', 'right-side', 'rightwards', 'rtl',
        'run-in', 'screen', 'scroll', 'semi-condensed', 'semi-expanded',
        'separate', 'se-resize', 'show', 'silent', 'silver', 'slower', 'slow',
        'small', 'small-caps', 'small-caption', 'smaller', 'soft', 'solid',
        'speech', 'spell-out', 'square', 's-resize', 'static', 'status-bar',
        'sub', 'super', 'sw-resize', 'table-caption', 'table-cell',
        'table-column', 'table-column-group', 'table-footer-group',
        'table-header-group', 'table-row', 'table-row-group', 'teal',
        'text-bottom', 'text-top', 'thick', 'thin', 'top', 'transparent',
        'tty', 'tv', 'ultra-condensed', 'ultra-expanded', 'underline',
        'upper-alpha', 'uppercase', 'upper-latin', 'upper-roman', 'url',
        'visible', 'wait', 'white', 'wider', 'w-resize', 'x-fast', 'x-high',
        'x-large', 'x-loud', 'x-low', 'x-slow', 'x-small', 'x-soft',
        'xx-large', 'xx-small', 'yellow'],
        // Font literals
        ['[mM]ono(?:space)?', '[tT]ahoma', '[vV]erdana', '[aA]rial',
        '[hH]elvetica', '[sS]ans(?:-serif)?', '[sS]erif', '[Cc]ourier'],
        // Measurement literals
        ["\\b\\d+(?:\\.\\d+)?(?:em|pt|px|cm|in|pc|mm)\\b"],
        // Color literals
        ['(?:\\#[a-fA-F0-9]{6}\\b|\\#[a-fA-F0-9]{3}\\b|rgb\\(\\s*\\d+\\s*,\\s*\\d+\\s*,\\s*\\d+\\s*\\))']
    ],
    identifiers: [["\\.[a-zA-Z_]\\w*"],["\\#[a-zA-Z_]\\w*"]],
    delimiters: [["[\\{\\}]"]]
}, 
xml: {
    multiLineComments: [
        ["<[^!>]*!--\\s*(?:(?:.|(?:\\r)?\\n)(?!--))*?(?:(?:.|(?:\\r)?\\n)(?=--))?\\s*--[^>]*?>"],
        ['<\\!\\[[\\w\\s]*?\\[(?:(?:.|(?:\\r)?\\n)(?!\\]\\]>))*?(?:(?:.|(?:\\r)?\\n)(?=\\]\\]>))?\\]\\]>']
    ],
    customFormatters: [{
        name: "tag",
        match: "<[/\\?]?[^>]*?>",
        handler: function(w) {
            var formatter = new Formatter(syntaxify.xmlTagFormatters);
            var wikifier = new Wikifier(w.matchText, formatter, w.highlightRegExp, w.tiddler);
            wikifier.subWikify(w.output, null);
        }
    }]
}};

config.formatterHelpers.customClassesHelper = function(w) {
    var lookaheadRegExp = (typeof(this.lookaheadRegExp) == "undefined")?(new RegExp(this.lookahead,"mg")):this.lookaheadRegExp;
    lookaheadRegExp.lastIndex = w.matchStart;
    var lookaheadMatch = lookaheadRegExp.exec(w.source);
    var language = (typeof(this.language) == "undefined")?lookaheadMatch[1]:this.language;
    if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
    {
        var isByLine = (typeof(this.byLine) == "undefined")?(lookaheadMatch[2] == "\n"):this.byLine;
        var p = createTiddlyElement(w.output,isByLine ? "div" : "span",null,language);
        w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
        if(typeof(syntaxify.formatters[language]) != "undefined") {
            var d = createTiddlyElement(w.output,isByLine?"div":"span",
				        null,"syntaxify "+language);
            var formatter = new Formatter(syntaxify.formatters[language]);
            if(typeof(this.termRegExp) == "undefined") {
                var text = lookaheadMatch[1];  
            } else {
                this.termRegExp.lastIndex = w.nextMatch;
                var terminatorMatch = this.termRegExp.exec(w.source);
                var text = w.source.substr(w.nextMatch, terminatorMatch.index-w.nextMatch);
            }
            if(config.browser.isIE) text = text.replace(/\n/g,"\r");  
            if (isByLine) {
                var l = createTiddlyElement(d,"ol");
                var li = createTiddlyElement(l,"li");
                var wikifier = new Wikifier(text, formatter, w.highlightRegExp, w.tiddler);
                wikifier.subWikify(li, null);
                if(!l.childNodes[l.childNodes.length-1].hasChildNodes())
                    l.removeChild(l.childNodes[l.childNodes.length-1]);
            } else {
	      var wikifier = new Wikifier(text,formatter,w.highlightRegExp,w.tiddler);
	      wikifier.subWikify(d, null);
            }
            if(typeof(this.termRegExp) != "undefined")
                w.nextMatch = terminatorMatch.index + terminatorMatch[0].length;
            else
                w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;  
        } else {
            if(isByLine)
                var e = createTiddlyElement(w.output,"code",null,null,text);  
            else
                var e = createTiddlyElement(w.output,"pre",null,null,text);  
        }
    }
}

syntaxify.formatters = {};

syntaxify.addLanguages = function(languages) {
    for(lang in languages) {
        syntaxify.formatters[lang] = new Array();
        for(var i=0;i<syntaxify.commonFormatters.length;i++)
            syntaxify.formatters[lang].push(syntaxify.commonFormatters[i]);
        var addSpanClass = function(rule, spaces, wordbreak) {
            if(typeof(languages[lang][rule]) != "undefined") {
                for(var j=0;j<languages[lang][rule].length;j++) {
                    syntaxify.formatters[lang].push({
                        name: rule+((j==0)?"":j),
                        match: wordbreak?("(?:\\b"+languages[lang][rule][j].join("\\b|\\b")+"\\b)")
                                        :("(?:"+languages[lang][rule][j].join("|")+")"),
                        hasSpaces: spaces,
                        handler: syntaxify.handleSpanClass
                    });
                }
            }
        };
        addSpanClass("singleLineComments", true, false);
        addSpanClass("multiLineComments", true, false);
        addSpanClass("keywords", false, true);
        addSpanClass("literals", true, false);
        addSpanClass("delimiters", false, false);
        addSpanClass("identifiers", false, false);
        if(typeof(languages[lang].customFormatters) != "undefined") 
            syntaxify.formatters[lang] = syntaxify.formatters[lang].concat(languages[lang].customFormatters);
    }
}

syntaxify.addLanguages(syntaxify.languages);

// Override the several built-in TiddlyWiki language-specific <pre> formatters
for(var i=0;i<config.formatters.length;i++) {  
  if(config.formatters[i].name == "monospacedByLineForPlugin") {  
    config.formatters[i].language = "javascript";
    config.formatters[i].byLine = true;
    config.formatters[i].handler = config.formatterHelpers.customClassesHelper;  
  }  
  if(config.formatters[i].name == "monospacedByLineForCSS") {  
    config.formatters[i].language = "css";
    config.formatters[i].byLine = true;
    config.formatters[i].handler = config.formatterHelpers.customClassesHelper;  
  }  
  if(config.formatters[i].name == "monospacedByLineForTemplate") {  
    config.formatters[i].language = "xml";
    config.formatters[i].byLine = true;
    config.formatters[i].handler = config.formatterHelpers.customClassesHelper;  
  }  
  if(config.formatters[i].name == "customClasses") {
    config.formatters[i].handler = config.formatterHelpers.customClassesHelper;  
    if(typeof(config.formatters[i].termRegExp) == "undefined")
        config.formatters[i].termRegExp = new RegExp(config.formatters[i].terminator, "mg");
  }
}

// make syntaxify reliably accessible from dependent plugins even under IE.
config.macros.syntaxify = syntaxify;
//}}}
/***

|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_wiki = /<tw:wikitext>(.|\n)*?<\/tw:wikitext>/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
		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 &apos; are not converted at render time
		// renderHtmlText() extends String and sorts out the problem
		item.title = item.title.renderHtmlText();
		// grab original wikitext if it is there as an extended field
		wikitext = item_match[i].match(regex_wiki);
		if (wikitext) {
			item.text = wikitext[0].replace(/^<tw:wikitext>|<\/tw:wikitext>$/mg,"");
			item.text = item.text.htmlDecode();
		} else {
			// 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);
		if (wikitext) {
			t.text = item.text;
		} else {
			t.text = "<html>" + item.text.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,"");
			item.pubDate = new Date(pubDate);
		} else {
			item.pubDate = new Date();
			// displayMessage("pubDate empty for item: " + item.title);
		}
		t.created = item.pubDate;
		// grab author
		author = item_match[i].match(regex_author);
		if (author) {
			author = author[0].replace(/^<author>|<\/author>$/mg,"");
			item.author = author;
		} else {
			item.author = "anonymous";
			// 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 &apos; to '
// this method has two passes at the string - the first to convert it to html and the second
// to selectively catch the ASCII-encoded characters without losing the rest of the html
String.prototype.renderHtmlText = function() {
	var e = createTiddlyElement(document.body,"div");
	e.innerHTML = this;
	var text = getPlainText(e);
	text = text.replace(/&#[\w]+?;/g,function(word) {
		var ee = createTiddlyElement(e,"div");
		ee.innerHTML = word;
		return getPlainText(ee);
	});
	removeNode(e);
	return text;
};

/* 24/10/07 - NOT USING THIS - extending RSS to include copy of wikitext for a tiddler instead
// RSSunwikify converts html structures into wikitext
// As at 24/10/07, only supports tables
// needed because slice mechanism does not support html table tags
RSSAdaptor.RSSunwikify = function(text) {
	console.log(text);
	var new_text = "";
	var table_regex = /<table(\w|[ "'=])*?>(.|\n)*?<\/table>/mg;
	var tr_regex = /<tr(\w|[ "'=])*?>(.|\n)*?<\/tr>/mg;
	var td_regex = /<td(\w|[ "'=])*?>(.|\n)*?<\/td>/mg;
	var table_match = text.match(table_regex);
	if (table_match) {
		console.log("found table: " + table_match[0])
		var rows = table_match[0].replace(/^<table(\w|[ "'=])*?>|<\/table>$/g,"").match(tr_regex);
		if (rows) {
			for (var i=0;i<rows.length;i++) {
				console.log("found row: " + rows[i]);
				var cells = rows[i].replace(/^<tr(\w|[ "'=])*?>|<\/tr>$/g,"").match(td_regex);
				if (cells) {
					for (var j=0;j<cells.length;j++) {
						console.log("found cell: " + cells[j]);
						// add pipe and cell content
						new_text += "| " + "<html>"+cells[j].replace(/^<td(\w|[ "'=])*?>|<\/td>$/g,"")+"</html>";
					}
				}
				// add pipe and newline at end of row
				new_text += "|\n";
			}
		}
	}
	console.log(new_text);
	return new_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 generateRss to add tw namespace (for wikitext field)
function generateRss()
{
	var s = [];
	var d = new Date();
	var u = store.getTiddlerText("SiteUrl");
	// Assemble the header
	s.push("<" + "?xml version=\"1.0\"?" + ">");
	s.push("<rss version=\"2.0\" xmlns:tw=\"http://tiddlywiki.org/wikitext/\">");
	s.push("<channel>");
	s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
	if(u)
		s.push("<link>" + u.htmlEncode() + "</link>");
	s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
	s.push("<language>en-us</language>");
	s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
	s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
	s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
	s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
	s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
	// The body
	var tiddlers = store.getTiddlers("modified","excludeLists");
	var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
	for (var t=tiddlers.length-1; t>=n; t--)
		s.push(tiddlers[t].saveToRss(u));
	// And footer
	s.push("</channel>");
	s.push("</rss>");
	// Save it all
	return s.join("\n");
}

// Override built-in saveToRss to include author and wikitext fields
Tiddler.prototype.saveToRss = function(url)
{
	var s = [];
	s.push("<item>");
	s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
	s.push("<tw:wikitext" + ">" + this.text.htmlEncode() + "</tw:wikitext" + ">");
	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");
};

//}}}
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title><<tiddler SiteTitle>></title>
<link><<tiddler SiteURL>></link>
<description><<tiddler SiteSubtitle>></description>
<language>en-us</language>
// pending fix for {this_year} <copyright>Copyright {this_year} ${username}</copyright>
<pubDate>{GMTdate}</pubDate>
<lastBuildDate>{GMTdate}</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
// pending fix for ${...} <generator>TiddlyWiki ${version.major}.${version.minor}.${version.revision}</generator>
<<ListTemplate filter:"[tag[docs]]" template:"SubTemplate">>
</channel>
</rss>
Everyone reads things that resonate with them. I try and remember to put mine here.

Don't metaphorically piss on people
Beg forgiveness rather than ask permission
Celebrate things
Show don't tell
No hassle
When the shit gets too deep, pull up your waders
|''Type:''|file|
|''URL:''|http://tw.lewcid.org/|
|''Workspace:''||

This tiddler was automatically created to record the details of this server
# Don't forget to save and reload your ~TiddlyWiki after installing the package...
# You might want to put [[Welcome to TiddlyChatter]] and/or [[TiddlyChatter]] in your MainMenu or DefaultTiddlers.
# The key to ~TiddlyChatter is your subscription to someone else's "~ChatterFeed". You have been set up with one subscription, called ChatterFeed, to some test content on http://tiddlychatter.tiddlyspot.com. You can manage your subscriptions from the main [[TiddlyChatter]] tiddler. Click "manage" and then "new subscription" and put in the URL of a ~TiddlyWiki which is publishing a feed.
#* Currently (as at 16/10/07), ~TiddlyChatter supports subscribing to only one person's feed - if you want to subscribe to someone other than the test content on tiddlychatter.tiddlyspot.com, change the URL field in ChatterFeed

Hitting "Get" downloads your updates. New content is displayed in red. When you save your ~TiddlyWiki, your own updates are published into your feed.
/***
|Name|ShowUpdatesPlugin|
|Created by|SaqImtiaz|
|Version|0.2 |
|Requires|~TW2.x|
!!!Description:
Allows you to list tiddlers that have changed since the users last visit. You can list only all changed tiddlers, or filter them to only show tiddlers that have or do not have a specific tag. By default a simple list of the titles of changed tiddlers is created. However, using an extremely versatile syntax you can provide a custom template for the generated text.

!!!Examples: 
[[ShowUpdatesDocs]]

!!!Installation:
Copy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.

!!!Syntax:
{{{<<showUpdates>>}}}
additional optional params:
{{{<showUpdates excludeTag:TagToExclude onlyTag:TagToList maxEntries:10 write:CustomWriteParameter >>}}}
excludeTag: ~TagToExclude
onlyTag: ~TagToList
maxEntries: max number of entries displayed when there are no updates. (default is 10, which can be changed in the config.macros.showUpdates.settings part of the code)
write: if a write parameter is not provided, an un-numbered list of the updates is generated. Alternatively, you can specify a custom 'template' for the text generated. The syntax for the write parameter is identical to that of the forEachTiddler macro. Additonal documentation on this syntax will be provided soon.
Some of the variables available in the write parameter are 'index', 'count' and 'lastVisit' where lastVisit is the date of the last visit in the format YYYYMMDDHHMM. Also areUpdates is a boolean that is true if there are new updates since the users last visit.

!!!To Do:
*refactor code to facilitate translations
*a streamlined version without the custom write parameter


!!!Code
***/
//{{{
window.lewcidLastVisit = '';
window.old_lewcid_whatsnew_restart = window.restart;
window.restart = function()
{
        if(config.options.txtLastVisit)
                 lewcidLastVisit= config.options.txtLastVisit;
        config.options.txtLastVisit = (new Date()).convertToYYYYMMDDHHMM();
        saveOptionCookie('txtLastVisit');
        window.old_lewcid_whatsnew_restart();
}

TiddlyWiki.prototype.lewcidGetTiddlers = function(field,excludeTag,includeTag,updatesOnly)
{
              var results = [];
              this.forEachTiddler(function(title,tiddler)
                      {
                      if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
                                    if(includeTag == undefined ||  tiddler.isTagged(includeTag))
                                            if ( updatesOnly == false || tiddler.modified.convertToYYYYMMDDHHMM()>lewcidLastVisit)
                                                  results.push(tiddler);
                      });
              if(field)
                  results.sort(function (a,b) {if(a[field] == b[field]) return(0); else return (a[field] < b[field]) ? -1 : +1; });
              return results;
}

config.macros.showUpdates={};
config.macros.showUpdates.settings =
{
         maxEntries: 10  //max items to show, if there are no updates since last visit
}

config.macros.showUpdates.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
          var args = paramString.parseParams("list",null,true);
          var write = getParam(args, "write", undefined);
          var onlyTag = getParam(args, "onlyTag", undefined);
          var excludeTag = getParam(args, "excludeTag", undefined);
          var sortBy = "modified";
          var maxEntries = getParam(args,"maxEntries",this.settings.maxEntries);

          if (lewcidLastVisit) 
                {var tiddlers = store.lewcidGetTiddlers(sortBy,excludeTag,onlyTag,true);
                 var areUpdates = tiddlers.length>0? true:false;}

          if (!lewcidLastVisit)
               {var countLine = "!!Recent Updates:";
               var tiddlers = store.lewcidGetTiddlers(sortBy,excludeTag,onlyTag,false);
               var areUpdates = false;}
          else if (tiddlers.length == 0)
               {var countLine = "!!@@color:red;No new updates@@ since your last visit. @@color:#999;font-size:70%;" + (Date.convertFromYYYYMMDDHHMM(lewcidLastVisit)).formatString(" (DD/MM/YY)") + "@@\n!!Recent Updates:";
               var tiddlers = store.lewcidGetTiddlers(sortBy,excludeTag,onlyTag,false);}
          else
               {var countLine ="!!@@color:red;"+ tiddlers.length + "@@ new " + (tiddlers.length==1?"update":"updates") + " since your last visit: @@color:#999;font-size:70%;" + (Date.convertFromYYYYMMDDHHMM(lewcidLastVisit)).formatString(" (DD/MM/YY)") + "@@";}

          tiddlers = tiddlers.reverse();
          var lastVisit = lewcidLastVisit? lewcidLastVisit:undefined;
          var count = areUpdates == true? tiddlers.length : maxEntries;
          var sp = createTiddlyElement(place,"span","showUpdates");
          if (write==undefined)
                 {
                  wikify(countLine,sp);
                  var list = createTiddlyElement(sp,"ul");
                  for (var i = 0; i < count; i++)
                          {
                           var tiddler = tiddlers[i];
                           createTiddlyLink(createTiddlyElement(list,"li"), tiddler.title, true);
                          }
                 }
          else
                {
                 var list = '';
                 for (var index = 0; index < count; index++) {
                 var tiddler = tiddlers[index];
                 list += eval(write); }
                 wikify(list, sp);
                }
}
//}}}
a web hangout for an Osmosoftonian
JayFresh
/*{{{*/

/* flag dev version */
body {border-top:solid 3px #c44; }

/* #sidebar {display:none;} */

.flaggedMandatory { background-color:#c99;}
.txtOptionInput { border:1px solid #ddd; border-top:1px solid #999; margin:4px;}
a.tiddlyLink { padding:0.1em 0.2em; }
a.tiddlyLink:hover { background-color:#ccd; color:#000; }

#mainMenu { width:12em; }
#displayArea { margin:1em 12em 1em 12em; }
#tiddlerDisplay { margin:0 5em 2em 5em; }
	
#controlsContainer { background-color:#ddd; border-top:solid 1px #fff;  border-bottom:solid 1px #aaa; }
#controlsContainer a.button { padding:0.4em 1em; border:solid 0 #000; color:#333; font-size:1em; line-height:2em; border-right:solid 1px #ccc;}
#controlsContainer a.button:hover { background-color:#ccc; color:#000; }
#controlsContainer div.sliderPanel { background-color:#333; border-top:solid 1px #aaa;}
#controlsContainer  .txtOptionInput { margin: 0; font-size:0.9em; padding:2px; border-right:solid 1px #ccc;}

#controlsContainer div.txtMainTab {  }
#controlsContainer div.txtMainTab div.tabset { background-color:#ccd; color:#ccc; float:left; padding:0; height:200px; width:150px; border-right:solid 1px #aaa; }
#controlsContainer div.txtMainTab div.tabset a.tab { display:block; padding:0.5em 1em; text-align:right; background-color:#ccd; border-bottom:solid 1px #aaa; margin:0;}
#controlsContainer div.txtMainTab div.tabset a.tabSelected {background-color:#bbc; border-top:solid 0px #aaa; color:#fff; }
#controlsContainer div.txtMainTab div.tabset a.tab:hover {background-color:#bbc; }
 
#controlsContainer div.tabContents {float:left; height:200px; width:350px; background-color: transparent; overflow:auto; border:solid 0px #aaa; border-right:solid 1px #aaa; padding:0;}

#controlsContainer div.sliderPanel div.txtMainTab ul { list-style:none; margin:0; padding:0.5em 0; }
#controlsContainer div.sliderPanel div.txtMainTab ul li { padding:0 20px;}
#controlsContainer div.sliderPanel div.txtMainTab ul li a { font-weight:normal; line-height:1.3em; margin:0.2em 0; }
#controlsContainer div.sliderPanel div.txtMainTab ul li.listTitle { border-top:solid 1px #bbc; padding:0.5em 0 0.2em 20px; font-size:1.5em; color:#bbc; }
#controlsContainer div.sliderPanel div.txtMainTab ul li.listLink { margin:0; }
#controlsContainer div.sliderPanel div.txtMainTab ul:first-child li.listTitle { border: solid 0 #ccc;  }

#popup { border:solid 1px [[ColorPalette::Subtle]]; border-bottom:solid 1px [[ColorPalette::LessSubtle]]; border-right:solid 1px [[ColorPalette::LessSubtle]]; background-color:[[ColorPalette::Background]]; padding:1px 0 0 0;}
#popup li a { background-color:[[ColorPalette::SubtleSuperLight]]; color:[[ColorPalette::MuchLessSubtle]]; text-align:left;}
#popup li a:hover { background-color:[[ColorPalette::SubtleLight]]; color:[[ColorPalette::MuchLessSubtle]];}
#popup li.listBreak div {border-bottom:solid 1px [[ColorPalette::Background]]; border-top:solid 1px [[ColorPalette::SubtleLight]]; margin:0;}

#controlsContainer div.topOptions {float:left; background-color:#bbc; width:400px; }

div.tiddler div.viewer { padding:1em 0 3em 0; border-bottom:solid 1px #ccc;}
h1,h2,h3,h4 { border-style:none; }
div.tiddler div.viewer > img {width:100%;}


div.tiddler div.task { border:solid 1px #aaa; background-color:#f2f2f2; position:relative; overflow:auto; clear:both; zoom:1;}
div.tiddler div.task div.taskBody { float:left; display:block; padding:1em 1em 1em 1em;}
div.tiddler div.task div.viewer { border-style:none; }
div.tiddler div.task div.taskControls {float:right; border-left:solid 1px #aaa; width:21em;  background-color:#ccd; padding:0; white-space:nowrap; }
div.tiddler div.task div.taskControls div.toolbar a { display:block; padding:0.5em 1em; text-align:right; background-color:#ccd; border-style:none; font-size:1.2em; color:#fff; text-align:left; border-bottom:solid 1px #aaa; margin:0;}
div.tiddler div.task div.taskControls div.toolbar a:hover {background-color:#bbc; }
div.tiddler div.task div.taskControls div.metacontrols { padding:1em; }
div.tiddler div.task div.taskControls div.metacontrols select {width: 150px; border:solid 1px #aaa; font-size:0.9em; padding:0.2em;}
div.tiddler div.task div.taskControls div.metacontrols table {border-style:none; margin:0;}
div.tiddler div.task div.taskControls div.metacontrols table tr {border-style:none;}
div.tiddler div.task div.taskControls div.metacontrols table td {border-style:none;}

#createTeamTaskUserForm { background-color:#f0f0f0; padding:1em; border:solid 1px #ccc;}
#createTeamTaskUserForm label { margin-bottom:10px; display:block; clear:left; font-size:0.9em;}
#createTeamTaskUserForm label input {float:left; border:1px solid #ddd; border-top:1px solid #999; padding:2px 4px; font-size:1em; width:30em;}


/*}}}*/
/*{{{*/

#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;
}
/*}}}*/
<item>
	<title> <<view title>></title>
	<description> <<view text>> </description>
        <<ListTemplate list:"<<view tags>>">>
	// loopy loopy {loop tiddler.tags as tag}
	//	<category>${tag}</category>
	// {endloop}
		// pending fix for ${...} <link>${tiddlerPermalink tiddler}</link>
	<pubDate><<view modified date>></pubDate>
</item>
{{{
<item>
	<title> <<view title static>></title>
	<description> <<view title html>> </description>
	{loop tiddler.tags as tag}
		<category>${tag}</category>
	{endloop}
		<link>${tiddlerPermalink tiddler}</link>
	<pubDate>{GMTdate tiddler.modified}</pubDate>
</item>
}}}

{{{
<<save RssTemplate filter:"[tag[exclude]]">>
}}}
/***
|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;
}
//}}}
!!I Want A Blog
* Talk about the Q&A recording on Twitter and blogs
* Pluginify IWAB
* Design editor
!!~IFrameHackery
* Fix it in IE again!
!!This site
* Go static
* Project pages with links to example tiddlywikis
* Make recent changes look/work "better" - Eric's plugin?
!!saving & filterTiddlers
* Get filterTiddlers into the core (pending ongoing discussion) -> change rssSaving to use limit and wildcard i.e. collect the correct tiddlers
* finish the group thread about trying to get the view macro working with format strings for ~templateViewTemplate
* Create the story about the list for template-based saving
|''Type:''|file|
|''URL:''|http://www.hawksworx.com/playground/TeamTasks/|
|''Workspace:''||

This tiddler was automatically created to record the details of this server
template: rssTemplate
tag: excludeLists
Apprehensively, I'm hitting "publish to blog" having made some changes to the boycook's TiddlyBlogger... In theory, there should be some tags attached to this post.
<<lv>>
|!Timeline|!Publisher 1 (P1)|!|!Publisher 2 (P2)|
|!initial setup|download or import [[TiddlyChatter|http://tiddlychatter.tiddlyspot.com]]||download or import [[TiddlyChatter|http://tiddlychatter.tiddlyspot.com]]|
|!create content|new tiddler [[FooBar]]|||
|!mark as candidate for publishing|add tag "public" to [[FooBar]] (automated via [[TiddlyChatter Control Panel]] > {{{Create}}})|||
|!confirm publishing|add tag "published" to [[FooBar]] (automated via {{{Publish}}} button)|||
|!publishing|{{{Save to Web}}} (also creating the RSS feed)|text-align:center;&rarr;||
|!subscribe to chatter feed|||[[TiddlyChatter Control Panel]] > {{{Manage}}} > {{{New Subscription}}}<br>enter URL of P1's TiddlyWiki (not its RSS feed)<br>{{{Go}}} >  select feeds to subscribe to > {{{Subscribe}}}|
|!retrieve chatter|||[[TiddlyChatter Control Panel]] > {{{Get}}}<br>[[TiddlyChatter Control Panel]] will display recent changes as a list of tiddlers|
|!commenting on chatter contents|||open imported [[FooBar]] and select {{{Add Notes}}}|
|!publishing comments||text-align:center;&larr;|{{{Save to Web}}} (cf. steps of P1)|
Nice Fred!
|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.6|
|Version of component|0.75|
|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:

|[[ImportWorkspaceMulti]] |by JonLister |[[source|http://svn.tiddlywiki.org/Trunk/contributors/JonathanLister/components/ImportWorkspaceMulti/js/importWorkspaceMulti.js]] |
|[[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]]|
e file location will look like: http://tiddlysnip.tidd

Source: [[TiddlySnip - TSGuide|http://tiddlysnip.com/#TSGuide]]
[[TiddlySnip - User Guides|http://tiddlysnip.com/#%5B%5BUser%20Guides%5D%5D]]
ddlywiki - great example of using TW as a website on a stick


Source: [[My Notebook - Google Notebook|http://www.google.com/notebook/#b=BDQFSQwoQ5fzMpLIi]]
!Current thinking
21/1
What have I learnt? Right well, we've got the wikifier as the templating engine, which has the standard formatter matching a lot of what we need. The reason being that Jeremy's listRelated plugin does something pretty simple - takes a wikitext template and renders a bunch of tiddlers (and any related to them) through this template. The template here makes much use of the {{{<<view>>}}} macro to display relevant bits of the current tiddler. See RssTemplate.

Jeremy's thinking that we only need new support for the looping in the template, and the direct inclusion of tiddlers. Then you'd be able to say "here's some output html created using a template and some tiddlers" and that would be a TiddlyWiki plugin. The further step of linking it to the save mechanism would be a core modification.

(Just had a thought: the listRelated plugin supports subTemplates, so could that be used for rss items? The template would be the outside template and the subTemplate would be the items. Although the <html>{{customClass{{{subClass{content}}}}}}</html> syntax exists to let us nest spans. It's a bit ugly though...)

The way you'd run the plugin would be:
{{{ <<listTemplate tiddlers template params>> }}}
where tiddlers is an array but could also be a filter expression e.g. [tag[!excludeSearch]], template is the tiddler with the template in it and params ought to be the data you want to be able to refer to in the template, such as any user data.

!! We need / need to fix
Extension to {{{ <<tiddler>> }}} so it supports encoded output
To take the listRelated code and make it work for this case
Extension to filter syntax in e.g. filterTiddlers() to allow for negation - !
To make sure the template plugin works when the templates are wrapped to make them display nicely
Can't currently nest macros e.g. {{{ <<macro1 list:"<<macro2>>">> }}}

!!Supported by {{{<<view>>}}}

GMTdate : {{{<<view modified date>>}}} (for the tiddler)
Title : {{{<<view title>>}}}
Description: {{{<<view text wikified>>}}}
Tag: {{{<<view tag>>}}}

This is going to need looking at because we'll need to encode the text for title and description!

!!Not supported by {{{<<view>>}}}

GMTdate : for the tiddlywiki / date now
{loop array as var} : loops through array, with each item called var (note: if array is a TW macro, it will be executed and the result used as the array); {loop} is terminated with {endloop}
this_year : returns the year
TiddlerName : just loads the content of the Tiddler
tiddlerPermalink tiddler : returns the permalink for a tiddler (perhaps an extension of commands.permaLink?)
${var} : as variable passed into the template processor
{func params} : a template function

----
20/1
Ok, so the SEOTiddlyWikiPlugin generates a directory structure of files, but they link back to the TW version, and we don't want that. Uses simple syntax in the template to create the files. This doesn't look as sensible as cook's approach (that Martin showed me), which uses HTML comments. This is better because it's not inventing a new templating language. Fudge fudge fudge. The intention is for me to be able to select a bunch of tiddlers somehow (in this case, it's by not tagging them with 'excludeSearch'), then click build and for the system to build a static version of my site. I'm not sure right now whether a page per tiddler is right. It probably is. So, I need to refactor the templating, stop the redirect and then we're kind of there... at least for prototype-iness. Can post at that point.

Cook has entries like {{{<!--@@title@@-->}}} and if a line in a recipe matches that, it gets inserted. So if there are lots of entries, you just end up with lots of inserts into the end html file e.g. 'tiddler'. My feeling about doing it within tiddlywiki is to match on div id's maybe? That doesn't work for multiple entries... Tags? Aha. Just use our own recipe files. So, the constituent parts are: the builder plugin (e.g. SEOTiddlyWikiPlugin) to collect and write the files, a template, a recipe and some config stuff to say what the root is, etc?

We also want a CSS file. We need to be able to edit one tiddler, publish that and for the effect to just update what needs updating, for example it might affect the sitemaps and the index file (in the case of a blog post).

I think the areas we're adressing here are:

* General website publishing
** SEO
*** sitemaps
*** individual pages for content (not sure how much of a difference this one makes)
*** titles being picked up not locked in div attributes
* Blog publishing specific
** Sharing
*** Digg, reddit, newsvine, del.icio.us, etc.
** Posting into multiple places
*** Not good practice, but could shadow content on e.g. wordpress.com if you were unsure about the TW-powered site working properly
** Commenting
*** Not sure how this is going to work ''ask Simon''

!Templating
It's become clear that some sort of templating method other than that suggested by cook is necessary to get us going with the simple RSS case. I've been looking at JST, AjaxPages and Smarty (which is apparently a very good templating framework, but runs in php) to get an idea of how other people have gone about this. It slightly bothers me that we need to add complexity, and I am trying to discover what the simplest possible system is that we need for the RSS case to work. One idea I had this morning (21/1):
* use {{{tiddlers}}} macro (which, if it doesn't exist, is plural version of {{{<<tiddler>>}}})inside the template e.g. {{{<<tiddlers [tag[!excludeLists]] item_template>>}}}, where {{{item_template}}} is a template that would be passed as a parameter to the {{{view}}} macro (which is included after a suggestion by Jeremy that I see if it is useful in this situation).

We are fortunate enough to have certain constraints to work with, particularly that ''data is going to be tiddler objects''. Others include:
* keep any application code out of the template, as these are for designers

!Q. marks
* how does search work when it's over multiple files?
** ...maybe we do the search in the tiddlywiki and return results from there. Somehow.
* What decides the directory structure?
** ...it should look RESTful
** ...see below to see how SEOTiddlyWikiPlugin does it
* If we want to preserve some of the TW browsing experience, how much JS would need to be copied along and what would this look like in the template?

!Asks
*...would be nice to be able to say "export all tiddlers tagged with
so-and-so tag" :-) 

!Collection of notes about using TiddlyWiki to create static web pages:

!!!function
* PublishMacro - publish tiddlers as HTML files to a subfolder - http://jackparke.googlepages.com/jtw.html#PublishMacro
** ...can publish single tiddlers
** ...pretty darn snappy to do a publish of a tiddler
** ...publishes css file as well
** uses the format() style to create templates - in my opinion, pretty complicated and ties the use of this template to this specific example
* SEOTiddlyWikiPlugin - to save files per tiddler using a template - http://www.superphysique.net/#%5B%5BSEO%20TiddlyWiki%20Plugin%5D%5D
** ...this works by creating a directory for each tag
** opening a file redirects to the online version
** file contains plain text content of tiddler (cool for mobile experience perhaps?)
** also saves sitemap.xml and urllist.txt files for Google and Yahoo which includes list of generated html files
*** See http://www.google.com/webmasters/sitemaps/ to register your
sitemap.xml file
** why we'd change SEOTiddlyWikiPlugin
*** the part to focus on is putting a recipe file into the templating step - this allows us to have more than one tiddler in a page for example and separates the spec of the content from the layout a bit more
* ChangeModePlugin - to switch template and stylesheet - http://tiddlywiki.bidix.info/#ChangeModePlugin
* If just speed is your concern, you could split your data and use Udo's "IncludePlugin" - http://tiddlywiki.abego-software.de/#IncludePlugin

!!!usability thingies
* author mode - http://groups.google.com/group/TiddlyWiki/msg/def6e878fe7f12e8
* color palette viewer - http://groups.google.com/group/TiddlyWiki/msg/7ebf401cf6ab3c72

!!!related discussions
* comparison of JavaScript templating engines (and comments): http://particletree.com/notebook/templates-in-javascript/
* http://groups.google.com/group/TiddlyWiki/browse_thread/thread/92a6d2d32d2a0284
* http://groups.google.com/group/TiddlyWiki/browse_thread/thread/6fecf9a673f5f140/2e247e6876ecb1ae?#2e247e6876ecb1ae
* SEO - http://groups.google.com/group/TiddlyWikiDev/browse_thread/thread/d14ec01af14e4f8f/3d3e39c11a55e1e5?#3d3e39c11a55e1e5, http://www.tiddlywiki.org/wiki/Dev:SEO,
* TW as a blog - http://groups.google.com/group/TiddlyWiki/browse_thread/thread/ea4a2c96b8afc0dd/a293331978b6e87f?lnk=gst&q=blog+tiddlywiki , http://groups.google.com/group/TiddlyWiki/browse_thread/thread/86e7278f9f05513c/9dafdfba2a3b3d90?lnk=gst&q=blog+tiddlywiki
* Website publishing - http://groups.google.com/group/TiddlyWiki/browse_thread/thread/1192f5b94f0b2efd/75b5d150e9ac4404?lnk=gst&q=website+publishing
/***
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 = 'jayfresh';

// 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?// &nbsp;&nbsp;@@ 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// &nbsp;&nbsp;@@ 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// &nbsp;&nbsp;@@ 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!// &nbsp;&nbsp;@@ 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 :)// &nbsp;&nbsp;@@ 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")

});
//}}}
IDEAS for TWITTER HACK ARCHIVE

At [[Le Web 3|http://www.leweb3.com]], Ev Williams mentioned in [[his talk|http://vpod.tv/leweb3/392057]] that Twitter can be thought of as a command-line. This struck a chord with me. I believe there is a sub-set of Twitter's 3rd-party applications that are built to make use of Twitter's nature as a command line. I dub these "Twitter Hacks" after the impression they give of using Twitter in a way it was not designed for.

To qualify for being tagged with "twitterhack", a service will pick up on something about the syntax of a Tweet and respond accordingly. This is different from services that simply track various keywords and display and aggregation of them (e.g. [[PoliTweets|http://politweets.com/]]).

Twitter Hacks show that Twitter can be used as a device-independent, global command line that sits on top of a large user-base. This suggests several things for the future of applications that take advantage of Twitter: for example, you could create a service that takes in commands, via Twitter, from various input devices. For a business, there is a cost-saving to be had, as well as a rapid launch and the flexibility of multiple ways of interacting with customers.

People who have written about this:
http://www.horsepigcow.com/2007/04/17/twitter-as-your-command-line-interface/
http://zeroinfluence.wordpress.com/2008/01/09/clouds-using-twitter-as-a-command-line-service/
http://www.techcrunch.com/2007/03/27/twitter-becomes-mobile-dev-platform/

| Name | Description | Command-line usage | Do you need to follow a Twitter user? |
| d | direct message to a user | d @username hello | No, this is a built-in function |
| @ | "reply" to a user | @username I disagree | No, this is a built-in function |
| ~PlusPlusBot.com | Give people cred or take it away | @username++ / @username-- for your last blog post | @plusplusbot, although they are thinking about using "track" instead |
| Foamee.com | Give someone a pint of beer | @ioubeer / @ioucoffee @twitterscreenname for being an amazing human being | @ioubeer / @ioucoffee |
| Hashtags.org | Convention for adding additional context and metadata to your tweets | I can't believe anyone would design software like this! #microsoftoffice | @hashtags |
| ~TwitterMap.com | Plots locations of tweets on a map | Going to the office. L:3000 6th Ave, New York | No |
| ~TwitterVision.com | Like TwitterMap, but a nicer app | L:work=300 Alameda Parkway, San Jose, CA 92012 | No |

Further developments: del.icio.us scans for keywords? ranked hierarchy?
Post this to: Twitter Fan (twitter.pbwiki.com), run by Chris Messina
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"] == "true" ? true : false;
		// SUPPORTING: tiddlers with notes - if a note is unread, reflect that in the status
		if (config.macros.notes) {
			// Get the notes tiddlers for this tiddler and set their unread status to that of the parent tiddler
			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 != t.title && notes_tiddlers[i].title.indexOf(t.title) != -1) {
					if (notes_tiddlers[i].fields["unread"]) {
						unread = notes_tiddlers[i].fields["unread"] == "true" ? true : false;
					}
				}
			}
		}
		var label = (unread) ? "Mark as read" : "Mark as unread";
		var caption = (!unread) ? "Click to mark as read" : "Click to mark as unread";
		var theUnreadBox = createTiddlyButton(place,label,caption);
		theUnreadBox.onclick = config.macros.unread.markAsRead;
		theUnreadBox.status = unread;
	}
};

config.macros.unread.markAsRead = function() {
	var DOMTiddler = story.findContainingTiddler(this);
	var t = store.fetchTiddler(DOMTiddler.getAttribute("tiddler"));
	// switch unread status
	t.fields["unread"] = this.status ? "false" : "true";
	// SUPPORTING: tiddlers with notes, so we can mark all read at once
	if (config.macros.notes) {
		// Get the notes tiddlers for this tiddler and set their unread status to that of the parent tiddler
		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 != t.title && notes_tiddlers[i].title.indexOf(t.title) != -1) {
				if (notes_tiddlers[i].fields["unread"]) {
					notes_tiddlers[i].fields["unread"] = t.fields["unread"];
				}
			}
		}
	}
	// 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 |
| 10/04/2008 18:16:25 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 14/04/2008 12:57:00 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 14/04/2008 18:08:26 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . | ok |
| 14/04/2008 18:43:58 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 16/04/2008 12:16:36 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 16/04/2008 13:49:23 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 01/12/2008 11:04:01 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . | ok |
| 01/12/2008 11:09:55 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . | ok |
| 01/12/2008 12:58:14 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
| 12/12/2008 22:48:23 | YourName | [[/|http://jayfresh.tiddlyspot.com/]] | [[store.cgi|http://jayfresh.tiddlyspot.com/store.cgi]] | . | [[index.html | http://jayfresh.tiddlyspot.com/index.html]] | . |
/***
|''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"
	});
*/
//}}}

/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.0|
|''Date:''|May 5, 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: 0,
	date: new Date("May 5, 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]);
		bidix.upload.httpUpload(rssUploadParams,convertUnicodeToUTF8(generateRss()),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');


/* don't want this for tiddlyspot sites

// Backstage
merge(config.tasks,{
	uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}
});
config.backstageTasks.push("uploadOptions");

*/


//}}}


@@color(red):22/10/07 New!@@ [[Designing TiddlyChatter from the user's point of view]]

This ~TiddlyWiki has been installed with TiddlyChatter, a shiny new collaboration feature.

To get into the ~TiddlyChatter mood, you need to [[install|InstallTiddlyChatter]] the <<tag TiddlyChatterPackage>> tiddlers in your own ~TiddlyWiki. When you have installed these, take a look at [[these set-up instructions|Setting up TiddlyChatter]].

You are actively encouraged to get involved with designing and building TiddlyChatter. The tiddler [[Designing TiddlyChatter from the user's point of view]] leads to a lot of content that describes and documents the design process we are going through. Please contribute through the [[TiddlyWikiDev|http://groups.google.com/TiddlyWikiDev]] group or mail me at jon at osmosoft dot com.

!!~TiddlyChatter is for opt-in, decentralized collaboration. What this means:
''opt-in:'' avoid "collaboration noise" associated with standard wikis - only see the information from people you want to collaborate directly with;
''decentralized:'' everyone can keep their own personal ~TiddlyWiki and still become a part of a ~TiddlyChatter community; you don't need to rely on a central collaboration server.

You can read an example scenario of [[how TiddlyChatter works|How TiddlyChatter works - an example scenario]]. ~TiddlyChatter is still in [[development|TiddlyChatterDocumentation]]. Bugs are [[tracked|BUGS]], please post in the [[TiddlyWikiDev|http://groups.google.com/TiddlyWikiDev]] Google Group if you find more.
[extracted from original [[blog post|http://jayfresh.wordpress.com/2007/09/24/tiddlychatter-decentralized-collaboration/]] on the subject]

“~TiddlyChatter” is the idea of opt-in, decentralized collaboration. This follows Twitter’s model, where someone can follow you without you taking an interest in what they have to say. Twitter is centralized, in that it runs through a central server; ~TiddlyChatter is totally decentralized and doesn't tie you in to sharing the same software or server with other ~TiddlyChatter users. As might be obvious from the name, ~TiddlyChatter is being developed using [[TiddlyWiki|http://www.tiddlywiki.com]], but the collaborative model will work with people using other software.

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, edit or comment
* 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:

* Jon’s little bit of work and Liz’s note on it appear on both computers as separate pieces of data, they are not downloaded from a common server
* If Jon decides Liz is no good as a partner, he can stop watching her feed and he never sees any of the notes or content changes Liz makes
* If Ben comes in and subscribes to Jon’s feed, he can make entirely independent comments and changes, without Liz seeing what he is doing and without Ben seeing what Liz is doing
* Extending this slightly, Ben, Alice and Bob can all share and work on the information together, and it is only when Liz eventually gets hold of a copy that Jon will see the result of the group’s work
!Wikifier
* The {{{wikify}}} function:
//{{{
function wikify(source,output,highlightRegExp,tiddler)
{
	if(source && source != "") {
		var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
		wikifier.subWikifyUnterm(output);
	}
}
//}}}
It creates a Wikifier object and then calls wikifier.subWikifyUnterm.

{{{wikifyStatic}}} returns the html directly.

{{{wikifyPlain}}} calls wikifyPlainText on a single tiddler.

{{{wikifyPlainText}}} calls wikifier.wikifyPlain on the text.

{{{highlightify}}} calls wikifier.outputText.

!! The {{{Wikifier}}} Object
Constructor takes a formatter. getParser figures out which one. config.parsers is examined but is empty by default, which means that in the default setup, calls to getParser return the global formatter object.

{{{wikifier.wikifyPlain}}} calls subWikify on itself
{{{wikifier.subWikify}}} calls either subWikifyTerm or subWikifyUnterm depending on whether it has a terminator provided or not (see below). Example uses are in the gradient macro's handler, where it is called with a terminator, and wikifer.wikifyPlain where it is called without.

{{{subWikifyUnterm}}} is the function that gets called by wikify and wikifyStatic. wikifyPlain and wikifyPlainText calls subWikify with one argument, so that ends up calling subWikifyUnterm as well. The function finds the strings that match with the formatter, outputs whatever is before the first as ordinary text, then goes into a loop through the matches until they run out. The mechanism for figuring out which part of the formatter and therefore which handler to call depends on the result of an exec() call being an array where the elements correspond to the paranthesized parts of the regex - if there is a match, that element contains something in it, if not, it is undefined. After the matches are finished, it outputs the rest of the text. The handlers tend to wikify their contents, so the wikification process is recursive. The upshot of this is that care has to be taken to keep the regex's lastIndex property and the output pointer in check.

{{{subWikifyTerm}}} is similar to subWikifyUnTerm. It takes a terminator regex to match the end of the string in question and wikifies (again a use of recursion) whatever is in between a formatter match and a terminator match.

{{{wikifier.outputText}}} outputs text without wikifying it. Copes with highlighting text.

!Formatter Object
At startup, a formatter object is created that contains config.formatters, which is a set of rules about how to turn wiki syntax into HTML. The Formatter constructor collects these into formatter.formatters and their regex match strings into one long regex called formatter.formatterRegExp. formatter is returned by getParser as a default.

I have noticed that some functions use the global formatter object when creating their Wikifiers: wikifyPlainText, highlightify.

A formatter object has a variable called "termRegExp". The functions in the core that use subWikifyTerm are often called with this.termRegExp as the terminator: handler of heading, list, quoteByLine, wikifyCommentForPlugin, wikifyCommentForTemplate,  styleByChar, customClasses,  formatters;
In the table formatter, there are two terminators: rowTermRegExp and cellTermRegExp; these are both used in the handler;
createElementAndWikify also uses subWikifyTerm and termRegExp and is used as the handler for: quoteByBlock, boldByChar, italicByChar, underlineByChar, strikeByChar, superscriptByChar, subscriptByChar.

!listRelated
handler gathers all the tiddlers you've asked for via the filter or tag paramaters, and sorts them by a field if you've provided that. The template and subTemplate are set as well. For every tiddler in the list, a new wikifier is created with the template as the source and the standard formatter, along with a reference to the current tiddler in the loop. The related tiddlers are then found and wikified next, with the subTemplate.
Ok, so here's an idea - why don't we let the lid off and speculate about what TiddlyChatter could be used for. Here's a couple of ideas:

* core functionality in TiddlyWiki - "let your TiddlyWikis IM each other"
* discussion area for blogs - tag a blog post with "public" and you can treat the blog RSS feed as a ChatterFeed

Add your ideas as notes and I'll update this tiddler with them as we go along
* software QA; beta testers can take notes, the developer can subscribe to their feeds
* mad bad lunatic asylum entertainment
<<dailytask>>
refreshStyles("StyleSheetTiddlyChatter");
<!--{{{-->
<!--{{{-->
<div class='toolbar' macro='toolbar publishBlog 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>
<!--}}}-->
<!--}}}-->
<<dailytask>>
config.macros.lv = {};

config.macros.lv.init = function() {
	// create personal ChatterFeed with the username on the end
	var newTitle = "ChatterFeed" + config.options.txtUserName;
	if (!store.getTiddler(newTitle)) {
		var ownFeed = document.location.href.replace(/.html$/,".xml");
		var newText = "|''Type:''|RSS|\n|''URL:''|"+ownFeed+"|\n|''Workspace:''||\n|''TiddlerFilter:''|[tag[public]]|";
		var newTags = "channel public published systemServer";
		store.saveTiddler(newTitle,newTitle,newText,config.options.txtUserName,null,newTags,null,true);
	}
};

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":
					// when filterTiddlers supports excluding tiddlers, it will make sense to
					// exclude systemServer tiddlers so we don't collect other people's subscriptions
					config.macros.importWorkspaceMulti.importAll("[tag["+config.options.txtImportTag+"]]");
					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") && !t.isTagged("systemServer")) {
			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='unread' macro='unread'></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 feeds by default
 * ID for feed 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 streamManagementWrapper = createTiddlyElement(place,"div","streamManagementWrapper");
	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(streamManagementWrapper,"div","channelWrapper");
		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(streamManagementWrapper,"div","subscriptionWrapper");
	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
	var ownPath = document.location.href.replace(/.html$/,"");
	for (var i=0;i<subscriptions.length;i++) {
		createTiddlyLink(subscriptionBox,subscriptions[i].title,true);
		// if the url of a subscription is the same as the page (minus the file extension)
		// flag that to the user
		var feedPath = store.getTiddlerSlice(subscriptions[i].title,"URL").replace(/.xml$/,"");
		if (ownPath == feedPath) {
			wikify("// - this is your own ~ChatterFeed//",subscriptionBox);
		} else {
			wikify("// - <html>" + store.getTiddlerSlice(subscriptions[i].title,"URL") + "</html>//",subscriptionBox);
		}
		createTiddlyElement(subscriptionBox,"br");
	}
	return streamManagementWrapper;
};

// 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 url_field = adaptor_store.getTiddlerSlice(tiddler.title,"URL");
		var workspace_field = adaptor_store.getTiddlerSlice(tiddler.title,"Workspace");
		var filter_field = adaptor_store.getTiddlerSlice(tiddler.title,"TiddlerFilter");
		var subscriptionTemplate = "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n|''TiddlerFilter:''|%3|";
		var text = subscriptionTemplate.format([type_field,url_field,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;
};

//}}}
<<dailytask>>