<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
<!--}}}-->
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, #backstageArea {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
<<importTiddlers>>
The user journey and the fixes to get people who wander back on to the main line have been created with OmniGraffle for the Mac. You can get the files in these formats:

User journey
* [[Graffle file|http://jonny.jonathan.googlepages.com/first_impressions_user_journey.graffle]]
* [[PNG file|http://jonny.jonathan.googlepages.com/first_impressions_user_journey.png]]

Fixes
* [[Graffle file|http://jonny.jonathan.googlepages.com/first_impressions_uj_fixes.graffle]]
* [[PNG file|http://jonny.jonathan.googlepages.com/first_impressions_uj_fixes.png]]
# 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.
!Current
* 1/11/07 - generation of ChatterFeed pointing to self fails when viewing root host rather than full URL e.g. http://domain.com rather than http://domain.com/index.html - Update 1/11/07, FND suggested using localPath not window.location
* 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
** 30/10/07 - Update: behaviour arises due to use of Regex.exec, which maintains state; should change to Regex.match
* 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
* 29/10/07 - File_adaptor_filter_plugin broke with no filter provided - Fixed 30/10/07, removed plugin as core FileAdaptor updated to support filter
* 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.
!!!boss
[img[http://jonny.jonathan.googlepages.com/boss.png]]
* wants to know what team are doing
* is responsible for delivery
* interested in development of team

!!!team
[img[http://jonny.jonathan.googlepages.com/team.png]]
* want to impress their boss
* can work together
* work to deadlines
Caroline: is a knowledge worker
* is 25 years old
* is addicted to Facebook
* works in external affairs for a construction trade federation
* is part of a team of 12
* lives in North London, Zone 2
* enjoys spending time with her friends
* plays squash and swims regularly
* lives with her boyfriend
* writes a lot of research-based reports
* coaches a netball team
* is Dan's client
* not to waste time with [[Dan]] explaining what she wants from his team
* for her netball team to do better this season
* for the conference she is organising to go really smoothly
* for it to be easier to work with [[Naomi]] on the research she does for [[Caroline]]
* more effectively organise information from varied sources
Caroline: as a knowledge worker...
* I want direct access to Naomi's research so I can access it at my convenience
* I want to be able to communicate with Naomi about her research as she is writing it so she can respond to my changing needs and we can discuss areas of confusion
* I want to easily combine information from Naomi with information from others so it takes me less time to pull together a report
|''Type:''|RSS|
|''URL:''|http://phil1.tiddlyspot.com/index.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
|''Type:''|RSS|
|''URL:''|file:///Users/jonathanlister/Documents/osmosoft/twsource/contributors/JonathanLister/verticals/TiddlyChatter/examples/tiddlychatter0-6.xml|
|''Workspace:''||
|''TiddlerFilter:''|[tag[public]]|
Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #000
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
{{{
/*** 16/10 - Patch not accepted, being revised ***/

ListView.getCommandHandler = function(callback,name,allowEmptySelection)
{	
	return function(e) {
		var that = this;
		var view = findRelated(this,"TABLE",null,"previousSibling");
		var tiddlers = [];
		ListView.forEachSelector(view,function(e,rowName) {
					if(e.checked)
						tiddlers.push(rowName);
					});
		if(tiddlers.length == 0 && !allowEmptySelection) {
			alert(config.messages.nothingSelected);
		} else {
			if(this.nodeName.toLowerCase() == "select") {
				callback.call(that,view,this.value,tiddlers);
				this.selectedIndex = 0;
			} else {
				callback.call(that,view,name,tiddlers);
			}
		}
		return false;
	};
};

/*** 16/10 - Patch not submitted yet ***/

// Returns the number of days since the Date
Date.prototype.relativeDays = function() {
	var now = new Date();
	var interval = now.getTime() - this.getTime();
	interval = Math.floor(interval / (1000 * 60 * 60 * 24));
	return interval;
};
}}}
Dan: is a boss
* is 31 years old
* runs a small team at a Public Affairs company
* lives just outside Central London, on the South side
* enjoys having lunch out
* rides to work on his bike
* feels pressure from his boss to make his team more productive (who doesn't?)
* uses computers on a daily basis, mainly for email
* has a Blackberry he is addicted to
* is single
* impress his boss
* keep his clients happy
* get more clients
* have more time for lunching with clients
* lose weight
* perform well next time his cycling club enters a race
* know his team are on track
* spend less time on the BlackBerry
Dan: as a boss...
* I want to spend less time answering email so I have more time to be productive (and can get rid of the Crackberry!)
* I want to keep an eye on my team's work in a less intrusive way so they can be more productive
* I want easy access to my team's work as and when I need it, so I can respond to my clients and my boss in a rapid manner
[[Welcome to TiddlyChatter]]
The development of TiddlyChatter (TC) has reached v0.7 and is a working prototype. At this point, we have taken a step back and are focussing on making TC as user-friendly as possible, and designing the user interface driven by the future users' needs.

!Basis for TiddlyChatter
The underlying need we are assuming for uses of TiddlyChatter is:

@@"I want to share information with people!"@@

We perceive two problems with current web-based technologies used for collaboration (e.g. wikis, Sharepoint):
* @@It's noisy (spam!)@@
* @@Why do I have to go somewhere new to do it?!@@

!Design
[[Thinking about users]]
[[Designing the interface]]
!User journeys
[[User journeys]] are the first step in constructing the user interface. They help to show what your interface will need to support, where the users could go astray and how we deal with that.
!!!teacher
[img[http://jonny.jonathan.googlepages.com/teacher.png]]
* has information to get to the pupils
* has work to mark
* offers feedback and help

!!!pupils
[img[http://jonny.jonathan.googlepages.com/pupils.png]]
* have work to do
* are learning
* can work with each other
* have a lot of questions
[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'
//}}}
# 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.
Joshua: is a pupil
* is 10 years old
* has a laptop at home
* has a Bebo page
* lives with his Mum & Dad
* lives in the inner suburbs of North-West London
* does a lot of sport
* plays outside with his neighbours
* uses the Internet every day at school and at home
* has a PSP and an XBox 360
* does quite well at school
* wants to go to Loughborough University
* spend less time doing homework
* get better marks
* get school colours for sporting achievement
* finish Halo 3
* beat [[Nicholas]] in the Maths exam at the end of term
Joshua: as a pupil...
* I want to find the solution to problems I am stuck on so it doesn't take me ages to get my work done and I can get the right answer
/***
|''Name:''|LoadRemoteFileThroughProxy (previous LoadRemoteFileHijack)|
|''Description:''|When the TiddlyWiki file is located on the web (view over http) the content of [[SiteProxy]] tiddler is added in front of the file url. If [[SiteProxy]] does not exist "/proxy/" is added. |
|''Version:''|1.1.0|
|''Date:''|mar 17, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#LoadRemoteFileHijack|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
***/
//{{{
version.extensions.LoadRemoteFileThroughProxy = {
 major: 1, minor: 1, revision: 0, 
 date: new Date("mar 17, 2007"), 
 source: "http://tiddlywiki.bidix.info/#LoadRemoteFileThroughProxy"};

if (!window.bidix) window.bidix = {}; // bidix namespace
if (!bidix.core) bidix.core = {};

bidix.core.loadRemoteFile = loadRemoteFile;
loadRemoteFile = function(url,callback,params)
{
 if ((document.location.toString().substr(0,4) == "http") && (url.substr(0,4) == "http")){ 
 url = store.getTiddlerText("SiteProxy", "/proxy/") + url;
 }
 return bidix.core.loadRemoteFile(url,callback,params);
}
//}}}
[[Welcome|Welcome to TiddlyChatter]]
[[BUGS]]

~TiddlyChatter controls
----
[[TC Control panel|TiddlyChatter]]
<<importWorkspace>>
<script language="javascript" type="text/javascript" src="../../../firebuglite/firebug/firebug.js"></script>
Naomi: is a researcher
* is 22 years old
* works in Dan's team
* has just moved house to a small flat in Central London
* works from home two days a week
* spends a lot of time in cafes, working or otherwise
* uses the Web all the time for research purposes
* plays in Caroline's netball team
* does a lot of the research Dan provides to Caroline's company
* be as effective in the cafe as she is in the office
* research to take less time
* report-writing made easier
* work directly with [[Caroline]], not through [[Dan]]
* impress [[Dan]] enough to get a promotion
* make more time to get fitter
* play better netball
Naomi: as a researcher...
* I want my research to go down really well with our clients so I impress Dan and get a promotion
* I want to have a one-to-one relationship with a client so I can be more certain I am doing the work they want
Nathan: is a teacher
* is 24 years old
* is reasonably good with technology (understands the web!)
* does his lesson plans in the evenings and at weekends
* lives in London, Zone 2
* enjoys reading classic literature
* goes out in London with old friends from University
* aspires to change classroom education for the better
* teaches Modern Languages (French, German)
* teaches at a good school just outside Central London
* teaches [[Joshua]] and [[Nicholas]]
* Bring the best out in the kids
* Use technology in the classroom to enhance the learning experience
* Spend less time marking work
* Have lesson-planning invade his spare time less
* Foster a spirit of working together amongst the kids
* Persuade the school to spend more to provide laptops for the pupils
* Inspire and learn from like-minded teachers
Nathan: as a teacher...
* I want to see what my students are doing do I can identify opportunities to give feedback, intervene or just check progress
* I want to give feedback on my students' work so it helps them get round a problem or stops them running into one
* I want to share what I do with like-minded teachers so I can inspire them or learn from their work and feedback
Nicholas: is a pupil
* is 11 years old
* is in [[Joshua's|Joshua]] class
* plays rugby for the school
* is really strong in geography
* has a maths tutor
* doesn't have the Internet at home
* has read the entire Harry Potter series twice and has all the films that are out
* has just given up the recorder
* lives in an estate in North-West London
* persuade his parents to get the Internet at home
* see the last Harry Potter film the day it comes out
* not to have a maths tutor
* stay at the top of the year in geography
Nicholas: as a pupil...
* I want to find a better way of getting better at Maths, so that I can get rid of my tutor
* I want to show that having the Internet at home will improve my studies, so that my parents will get the Internet at home!
/***
|''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
// %/
With personas, we are describing human beings that we think would use TiddlyChatter. Creating characters we can empathise with helps to unlock people's motivations and brings things up you wouldn't uncover when just thinking about the "generic user".

Education
* [[Nathan]] (is a teacher) - [[Nathan's goals]]
* [[Joshua]] (is a pupil) - [[Joshua's goals]]
* [[Nicholas]] (is a pupil) - [[Nicholas' goals]]

Business
* [[Dan]] (is a boss) - [[Dan's goals]]
* [[Naomi]] (is a researcher) - [[Naomi's goals]]
* [[Caroline]] (is a knowledge worker) - [[Caroline's goals]]

!!!An observation about motivations towards collaboration:

In the //educational// context, it is only really the teacher with the vested interest in their pupils working together - the pupils are motivated by their own performance. In the //business// context, while the boss clearly wants the team to be successful and team members can see this benefit, again people are selfishly motivated to improve their personal performance. Therefore, most user requirements are going to be of the form @@"I want something to happen that makes my life easier"@@.
/***

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

//}}}
Test workbook - to work with other TiddlyChatter-enabled TiddlyWikis
TiddlyChatter
/*{{{*/

#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;
}
/*}}}*/
/*{{{*/

#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;
}
/*}}}*/
/***
|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;
}
//}}}
! Scenarios
Early indications are that TiddlyChatter would be useful in two generic areas:
* [[Education|Education Scenario]]
* [[Business|Business Scenario]]
! Personas
With [[Personas]], we create some characters to think about as model users, and explore their goals and needs. These can then be used as a lens through which to look at the design of the application.
! User stories
[[User stories]] are a way of looking at what the application should do from the point of view of what a user wants the application to do. A user story states //who// a user is, //what// they want and //why// they want it.
<<lv>>
How to get going with TiddlyChatter, graphically...

|!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>&rarr; 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)|

Thanks to FND for this.
|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]] |
|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]]|
To create a piece of Chatter, you have a couple of options:
* Hit "Create" on the TiddlyChatter control panel, edit and save
* Create a new tiddler and tag it with "public"
This creates a Chatter ready for publishing into your ChatterFeed after you've finished drafting it - click "Publish" to get it into your feed.

At this point, you might be wondering how to get your content updates up onto the web and into the hands of your subscribers:
* Using the "Save to web" feature of TiddlySpot is the simplest at the moment, especially because other TiddlySpot TiddlyWikis will be able to download your Chatter directly, instead of them having to download and run a local version.
* If you have BidiX's [[UploadPlugin|http://tiddlywiki.bidix.info/#UploadPlugin]] and a php server, you can set up a store that will accept uploads directly from TiddlyWiki
* If you have *any form* of static file hosting available to you, posting your ChatterFeed (and your TiddlyWiki) on there is totally fine

We recognise that this is a frustrating area, but we're not alone in trying to solve it.

[Addition:] 1/11/07 - I just found [[yoyko|http://www.chaosindex.com/yoyko/index.html]], who provide a hosted store for TiddlyWikis
!Subscribing to other people's Chatter

To read someone's else Chatter, you have to be subscribed to their ChatterFeed:
* From the TiddlyChatter control panel, click Manage > New Subscription
* Enter the URL of the TiddlyWiki publishing the ChatterFeed you want to subscribe to
* When the list of available feeds appears, put a check in the box next to the one you want and click "subscribe"

When it comes to downloading other people's Chatter, you generally need to be viewing your TiddlyWiki as a local file for the remote calls to work. This isn't true if you're only working with TiddlyWikis running on TiddlySpot, as their proxy sorts everything out, which is grand.

Hitting "Get" from the control panel downloads your updates. New content is displayed in red and the number of notes on a tiddler is shown in brackets next to the tiddler's title.

!Adding and publishing comments

After you've read an imported tiddler and want to add a comment, clicking on "add notes" opens a box to type in your note. Clicking "save notes" saves the note and publishes it into your ChatterFeed. Various ways to get your feed out onto the web where people can see it are discussed in [[this tiddler|TiddlyChatterPublishing]].
/***
Contains the stuff you need to use Tiddlyspot
Note you must also have UploadPlugin installed
***/
//{{{

// edit this if you are migrating sites or retrofitting an existing TW
config.tiddlyspotSiteId = 'tiddlychatter';

// make it so you can by default see edit controls via http
config.options.chkHttpReadOnly = false;
window.readOnly = false; // make sure of it (for tw 2.2)

// disable autosave in d3
if (window.location.protocol != "file:")
	config.options.chkGTDLazyAutoSave = false;

// tweak shadow tiddlers to add upload button, password entry box etc
with (config.shadowTiddlers) {
	SiteUrl = 'http://'+config.tiddlyspotSiteId+'.tiddlyspot.com';
	SideBarOptions = SideBarOptions.replace(/(<<saveChanges>>)/,"$1<<tiddler TspotSidebar>>");
	OptionsPanel = OptionsPanel.replace(/^/,"<<tiddler TspotOptions>>");
	DefaultTiddlers = DefaultTiddlers.replace(/^/,"[[Welcome to Tiddlyspot]] ");
	MainMenu = MainMenu.replace(/^/,"[[Welcome to Tiddlyspot]] ");
}

// create some shadow tiddler content
merge(config.shadowTiddlers,{

'Welcome to Tiddlyspot':[
 "This document is a ~TiddlyWiki from tiddlyspot.com.  A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.",
 "",
 "@@font-weight:bold;font-size:1.3em;color:#444; //What now?// &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")

});
//}}}
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 |
| 16/10/2007 19:33:41 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . |
| 16/10/2007 19:45:47 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 16/10/2007 19:47:32 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 16/10/2007 19:48:45 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . |
| 16/10/2007 19:53:07 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 16/10/2007 19:55:23 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 16/10/2007 20:01:37 | JayFresh | [[/|http://tiddlychatter.tiddlyspot.com/]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . |
| 22/10/2007 17:08:45 | JayFresh | [[tiddlychatter.html|file:///Users/jonathanlister/Documents/osmosoft/tiddlywikis/tiddlyspot/tiddlychatter.html]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 22/10/2007 17:18:21 | JayFresh | [[tiddlychatter.html|file:///Users/jonathanlister/Documents/osmosoft/tiddlywikis/tiddlyspot/tiddlychatter.html]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.tiddlyspot.com/index.html]] | . | ok |
| 01/11/2007 23:31:15 | Jon Lister | [[tiddlychatter0-6.html|file:///Users/jonathanlister/Documents/osmosoft/twsource/contributors/JonathanLister/verticals/TiddlyChatter/examples/tiddlychatter0-6.html]] | [[store.cgi|http://tiddlychatter.tiddlyspot.com/store.cgi]] | . | [[index.html | http://tiddlychatter.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");

*/


//}}}
User journeys describe the path a user takes through an application interface in order to satisfy a need. At every possible point where a user can fall off a journey and end up with a bad experience, we attempt to catch this and figure out how to redirect them back on to the main line.

[[1st impressions]] - getting the software, downloading, installing, configuring and running for the first time
User stories expresses who a user is, what they want to do with an application and why they want to do it. With a prioritized set of user stories, these essentially define what your user interface has to do. A user story is of the form //"As a... I want to... so that..."//.

Education
* [[Nathan's stories]]
* [[Joshua's stories]]
* [[Nicholas' stories]]

Business
* [[Dan's stories]]
* [[Naomi's stories]]
* [[Caroline's stories]]
<!--{{{-->
<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>
<!--}}}-->
@@color(red):1/11/07 Updated!@@ Introduction and set-up instructions re-written for clarity
@@color(red):1/11/07 New!@@ [[Diagram|TiddlyChatter flow]] of how to get two people Chattering
@@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.

!!~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.

!!Starting out
To get into the ~TiddlyChatter mood, you need to [[install|InstallTiddlyChatter]] the <<tag TiddlyChatterPackage>> tiddlers in your own ~TiddlyWiki. When you're done, take a look at these [[quick tips|After installing]] or read these guides to [[publishing|TiddlyChatterPublishing]] content and [[getting hold of|TiddlyChatterReading]] other people's content. There is also a [[diagram|TiddlyChatter flow]] of the flow two people would go through to Chatter with each other.

You are actively encouraged to help design and build 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.

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.
refreshStyles("StyleSheetTiddlyChatter");
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;
};

//}}}