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

	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 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;}
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 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 class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
<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 id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></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
Ken Girard writes:
Open up ViewTemplate and paste in {{{<div class='reminder'
macro='newReminder'></div><br />}}}
I put it right under {{{<div class='viewer' macro='view text wikified'></
div><br />}}} so that it is always at the bottom of the text (but above
my tagglyTagging when I use that).
If you have HideWhen installed you can even set it up so it only shows
up on the tiddlers you desire, or more often is hidden on the ones
that don't need it like plugins (What is there to be reminded about a
|Author|Eric Shulman|
|Original Author|SteveRumsby|
|Description|display monthly and yearly calendars|

NOTE: For enhanced date display (including popups), you must also install [[DatePlugin]]
|{{{<<calendar>>}}}|Produce a full-year calendar for the current year|
|{{{<<calendar year>>}}}|Produce a full-year calendar for the given year|
|{{{<<calendar year month>>}}}|Produce a one-month calendar for the given month and year|
|{{{<<calendar thismonth>>}}}|Produce a one-month calendar for the current month|
|{{{<<calendar lastmonth>>}}}|Produce a one-month calendar for last month|
|{{{<<calendar nextmonth>>}}}|Produce a one-month calendar for next month|
|''First day of week:''<br>{{{config.options.txtCalFirstDay}}}|<<option txtCalFirstDay>>|(Monday = 0, Sunday = 6)|
|''First day of weekend:''<br>{{{config.options.txtCalStartOfWeekend}}}|<<option txtCalStartOfWeekend>>|(Monday = 0, Sunday = 6)|

<<option chkDisplayWeekNumbers>> Display week numbers //(note: Monday will be used as the start of the week)//
|''Week number display format:''<br>{{{config.options.txtWeekNumberDisplayFormat }}}|<<option txtWeekNumberDisplayFormat >>|
|''Week number link format:''<br>{{{config.options.txtWeekNumberLinkFormat }}}|<<option txtWeekNumberLinkFormat >>|
2008.02.27: in handler(), DON'T set hard-coded default date format, so that *customized* value (pre-defined in config.macros.calendar.journalDateFmt is used.
2008.02.17: in createCalendarYear(), fix next/previous year calculation (use parseInt() to convert to numeric value).  Also, use journalDateFmt for date linking when NOT using [[DatePlugin]].
2008.02.16: in createCalendarDay(), week numbers now created as TiddlyLinks, allowing quick creation/navigation to 'weekly' journals (based on request from Kashgarinn)
2008.01.08: in createCalendarMonthHeader(), "month year" heading is now created as TiddlyLink, allowing quick creation/navigation to 'month-at-a-time' journals
2007.11.30: added "return false" to onclick handlers (prevent IE from opening blank pages)
2006.08.23: added handling for weeknumbers (code supplied by Martin Budden (see "wn**" comment marks).  Also, incorporated updated by Jeremy Sheeley to add caching for reminders (see [[ReminderMacros]], if installed)
2005.10.30: in config.macros.calendar.handler(), use "tbody" element for IE compatibility.  Also, fix year calculation for IE's getYear() function (which returns '2005' instead of '105'). Also, in createCalendarDays(), use showDate() function (see [[DatePlugin]], if installed) to render autostyled date with linked popup.  Updated calendar stylesheet definition: use .calendar class-specific selectors, add text centering and margin settings
2006.05.29: added journalDateFmt handling
!!!!!Code section:
version.extensions.calendar = { major: 0, minor: 6, revision: 0, date: new Date(2008, 2, 27)};

if(config.options.txtCalFirstDay == undefined)
  config.options.txtCalFirstDay = 0;
if(config.options.txtCalStartOfWeekend == undefined)
  config.options.txtCalStartOfWeekend = 5;
if(config.options.chkDisplayWeekNumbers == undefined)//wn**
  config.options.chkDisplayWeekNumbers = false;
  config.options.txtCalFirstDay = 0;
if(config.options.txtWeekNumberDisplayFormat == undefined)//wn**
  config.options.txtWeekNumberDisplayFormat = "w0WW";
if(config.options.txtWeekNumberLinkFormat == undefined)//wn**
  config.options.txtWeekNumberLinkFormat = "YYYY-w0WW";

config.macros.calendar = {};
config.macros.calendar.monthnames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
config.macros.calendar.daynames = ["M", "T", "W", "T", "F", "S", "S"];
config.macros.calendar.weekendbg = "#c0c0c0";
config.macros.calendar.monthbg = "#e0e0e0";
config.macros.calendar.holidaybg = "#ffc0c0";
config.macros.calendar.journalDateFmt = "DD MMM YYYY";
config.macros.calendar.monthdays = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
config.macros.calendar.holidays = [ ]; // Not sure this is required anymore - use reminders instead
function calendarIsHoliday(date) // Is the given date a holiday?
	var longHoliday = date.formatString("0DD/0MM/YYYY");
	var shortHoliday = date.formatString("0DD/0MM");
	for(var i = 0; i < config.macros.calendar.holidays.length; i++) {
		if(config.macros.calendar.holidays[i] == longHoliday || config.macros.calendar.holidays[i] == shortHoliday)
			return true;
	return false;
config.macros.calendar.handler = function(place,macroName,params) {
	var calendar = createTiddlyElement(place, "table", null, "calendar", null);
	var tbody = createTiddlyElement(calendar, "tbody", null, null, null);
	var today = new Date();
	var year = today.getYear();
	if (year<1900) year+=1900;

 	// get format for journal link by reading from SideBarOptions (ELS 5/29/06 - based on suggestion by Martin Budden)
	var text = store.getTiddlerText("SideBarOptions");
	var re = new RegExp("<<(?:newJournal)([^>]*)>>","mg"); var fm = re.exec(text);
	if (fm && fm[1]!=null) { var pa=fm[1].readMacroParams(); if (pa[0]) this.journalDateFmt = pa[0]; }

	if (params[0] == "thismonth") {
		cacheReminders(new Date(year, today.getMonth(), 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, today.getMonth());
	else if (params[0] == "lastmonth") {
		var month = today.getMonth()-1; if (month==-1) { month=11; year--; }
		cacheReminders(new Date(year, month, 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, month);
	else if (params[0] == "nextmonth") {
		var month = today.getMonth()+1; if (month>11) { month=0; year++; }
		cacheReminders(new Date(year, month, 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, month);
	} else {
		if (params[0]) year = params[0];
		if(params[1]) {
			cacheReminders(new Date(year, params[1]-1, 1, 0, 0), 31);
			createCalendarOneMonth(tbody, year, params[1]-1);
		} else {
			cacheReminders(new Date(year, 0, 1, 0, 0), 366);
			createCalendarYear(tbody, year);
	window.reminderCacheForCalendar = null;
//This global variable is used to store reminders that have been cached
//while the calendar is being rendered.  It will be renulled after the calendar is fully rendered.
window.reminderCacheForCalendar = null;
function cacheReminders(date, leadtime)
	if (window.findTiddlersWithReminders == null) return;
	window.reminderCacheForCalendar = {};
	var leadtimeHash = [];
	leadtimeHash [0] = 0;
	leadtimeHash [1] = leadtime;
	var t = findTiddlersWithReminders(date, leadtimeHash, null, 1);
	for(var i = 0; i < t.length; i++) {
		//just tag it in the cache, so that when we're drawing days, we can bold this one.
		window.reminderCacheForCalendar[t[i]["matchedDate"]] = "reminder:" + t[i]["params"]["title"]; 
function createCalendarOneMonth(calendar, year, mon)
	var row = createTiddlyElement(calendar, "tr", null, null, null);
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon] + " " + year, true, year, mon);
	row = createTiddlyElement(calendar, "tr", null, null, null);
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
function createCalendarMonth(calendar, year, mon)
	var row = createTiddlyElement(calendar, "tr", null, null, null);
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon] + " " + year, false, year, mon);
	row = createTiddlyElement(calendar, "tr", null, null, null);
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
function createCalendarYear(calendar, year)
	var row;
	row = createTiddlyElement(calendar, "tr", null, null, null);
	var back = createTiddlyElement(row, "td", null, null, null);
	var backHandler = function() {
		createCalendarYear(calendar, parseInt(year)-1);
		return false; // consume click
	createTiddlyButton(back, "<", "Previous year", backHandler);
	back.align = "center";
	var yearHeader = createTiddlyElement(row, "td", null, "calendarYear", year);
	yearHeader.align = "center";
	var fwd = createTiddlyElement(row, "td", null, null, null);
	var fwdHandler = function() {
		createCalendarYear(calendar, parseInt(year)+1);
		return false; // consume click
	createTiddlyButton(fwd, ">", "Next year", fwdHandler);
	fwd.align = "center";
	createCalendarMonthRow(calendar, year, 0);
	createCalendarMonthRow(calendar, year, 3);
	createCalendarMonthRow(calendar, year, 6);
	createCalendarMonthRow(calendar, year, 9);
function createCalendarMonthRow(cal, year, mon)
	var row = createTiddlyElement(cal, "tr", null, null, null);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon], false, year, mon);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+1], false, year, mon);
	createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+2], false, year, mon);
	row = createTiddlyElement(cal, "tr", null, null, null);
	createCalendarDayHeader(row, 3);
	createCalendarDayRows(cal, year, mon);
function createCalendarMonthHeader(cal, row, name, nav, year, mon)
	var month;
	if (nav) {
		var back = createTiddlyElement(row, "td", null, null, null);
		back.align = "center";
		back.style.background = config.macros.calendar.monthbg;

		var backMonHandler = function() {
			var newyear = year;
			var newmon = mon-1;
			if(newmon == -1) { newmon = 11; newyear = newyear-1;}
			cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
			createCalendarOneMonth(cal, newyear, newmon);
			return false; // consume click
		createTiddlyButton(back, "<", "Previous month", backMonHandler);
		month = createTiddlyElement(row, "td", null, "calendarMonthname")
		month.setAttribute("colSpan", config.options.chkDisplayWeekNumbers?6:5);//wn**
		var fwd = createTiddlyElement(row, "td", null, null, null);
		fwd.align = "center";
		fwd.style.background = config.macros.calendar.monthbg; 

		var fwdMonHandler = function() {
			var newyear = year;
			var newmon = mon+1;
			if(newmon == 12) { newmon = 0; newyear = newyear+1;}
			cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
			createCalendarOneMonth(cal, newyear, newmon);
			return false; // consume click
		createTiddlyButton(fwd, ">", "Next month", fwdMonHandler);
	} else {
		month = createTiddlyElement(row, "td", null, "calendarMonthname", name)
	month.align = "center";
	month.style.background = config.macros.calendar.monthbg;
function createCalendarDayHeader(row, num)
	var cell;
	for(var i = 0; i < num; i++) {
		if (config.options.chkDisplayWeekNumbers) createTiddlyElement(row, "td");//wn**
		for(var j = 0; j < 7; j++) {
			var d = j + (config.options.txtCalFirstDay - 0);
			if(d > 6) d = d - 7;
			cell = createTiddlyElement(row, "td", null, null, config.macros.calendar.daynames[d]);
			if(d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1))
				cell.style.background = config.macros.calendar.weekendbg;
function createCalendarDays(row, col, first, max, year, mon) {
	var i;
	if (config.options.chkDisplayWeekNumbers){
		if (first<=max) {
			var ww = new Date(year,mon,first);
			var td=createTiddlyElement(row, "td");//wn**
			var link=createTiddlyLink(td,ww.formatString(config.options.txtWeekNumberLinkFormat),false);
		else createTiddlyElement(row, "td", null, null, null);//wn**
	for(i = 0; i < col; i++)
		createTiddlyElement(row, "td", null, null, null);
	var day = first;
	for(i = col; i < 7; i++) {
		var d = i + (config.options.txtCalFirstDay - 0);
		if(d > 6) d = d - 7;
		var daycell = createTiddlyElement(row, "td", null, null, null);
		var isaWeekend = ((d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1))? true:false);
		if(day > 0 && day <= max) {
			var celldate = new Date(year, mon, day);
			// ELS 2005.10.30: use <<date>> macro's showDate() function to create popup
			// ELS 5/29/06 - use journalDateFmt 
			if (window.showDate)
				showDate(daycell,celldate,"popup","DD",config.macros.calendar.journalDateFmt,true, isaWeekend);
			else {
				if(isaWeekend) daycell.style.background = config.macros.calendar.weekendbg;
				var title = celldate.formatString(config.macros.calendar.journalDateFmt);
					daycell.style.background = config.macros.calendar.holidaybg;
				if(window.findTiddlersWithReminders == null) {
					var link = createTiddlyLink(daycell, title, false);
				} else
					var button = createTiddlyButton(daycell, day, title, onClickCalendarDate);
// We've clicked on a day in a calendar - create a suitable pop-up of options.
// The pop-up should contain:
//  * a link to create a new entry for that date
//  * a link to create a new reminder for that date
//  * an <hr>
//  * the list of reminders for that date
// NOTE: The following code is only used when [[DatePlugin]] is not present
function onClickCalendarDate(e)
	var button = this;
	var date = button.getAttribute("title");
	var dat = new Date(date.substr(6,4), date.substr(3,2)-1, date.substr(0, 2));

	date = dat.formatString(config.macros.calendar.journalDateFmt);
	var popup = createTiddlerPopup(this);
	var newReminder = function() {
		var t = store.getTiddlers(date);
		displayTiddler(null, date, 2, null, null, false, false);
		if(t) {
			document.getElementById("editorBody" + date).value += "\n<<reminder day:" + dat.getDate() +
				" month:" + (dat.getMonth()+1) + " year:" + (dat.getYear()+1900) + " title: >>";
		} else {
			document.getElementById("editorBody" + date).value = "<<reminder day:" + dat.getDate() +
				" month:" + (dat.getMonth()+1) +" year:" + (dat.getYear()+1900) + " title: >>";
		return false; // consume click
	var link = createTiddlyButton(popup, "New reminder", null, newReminder); 
	var t = findTiddlersWithReminders(dat, [0,14], null, 1);
	for(var i = 0; i < t.length; i++) {
		link = createTiddlyLink(popup, t[i].tiddler, false);
	return false; // consume click
function calendarMaxDays(year, mon)
	var max = config.macros.calendar.monthdays[mon];
	if(mon == 1 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) max++;
	return max;
function createCalendarDayRows(cal, year, mon)
	var row = createTiddlyElement(cal, "tr", null, null, null);
	var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first1 < 0) first1 = first1 + 7;
	var day1 = -first1 + 1;
	var first2 = (new Date(year, mon+1, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first2 < 0) first2 = first2 + 7;
	var day2 = -first2 + 1;
	var first3 = (new Date(year, mon+2, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first3 < 0) first3 = first3 + 7;
	var day3 = -first3 + 1;

	var max1 = calendarMaxDays(year, mon);
	var max2 = calendarMaxDays(year, mon+1);
	var max3 = calendarMaxDays(year, mon+2);

	while(day1 <= max1 || day2 <= max2 || day3 <= max3) {
		row = createTiddlyElement(cal, "tr", null, null, null);
		createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
		createCalendarDays(row, 0, day2, max2, year, mon+1); day2 += 7;
		createCalendarDays(row, 0, day3, max3, year, mon+2); day3 += 7;
function createCalendarDayRowsSingle(cal, year, mon)
	var row = createTiddlyElement(cal, "tr", null, null, null);
	var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
	if(first1 < 0) first1 = first1+ 7;
	var day1 = -first1 + 1;
	var max1 = calendarMaxDays(year, mon);
	while(day1 <= max1) {
		row = createTiddlyElement(cal, "tr", null, null, null);
		createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
setStylesheet(".calendar, .calendar table, .calendar th, .calendar tr, .calendar td { text-align:center; } .calendar, .calendar a { margin:0px !important; padding:0px !important; }", "calendarStyles");
This plugin was written by Jeremy Sheeley but is now maintained by simon.baird@gmail.com
|''Description:''|Support for cryptographic functions|
if(!version.extensions.CryptoFunctionsPlugin) {
version.extensions.CryptoFunctionsPlugin = {installed:true};

//-- Crypto functions and associated conversion routines

// Crypto "namespace"
function Crypto() {}

// Convert a string to an array of big-endian 32-bit words
Crypto.strToBe32s = function(str)
	var be = Array();
	var len = Math.floor(str.length/4);
	var i, j;
	for(i=0, j=0; i<len; i++, j+=4) {
		be[i] = ((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
	while (j<str.length) {
		be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
	return be;

// Convert an array of big-endian 32-bit words to a string
Crypto.be32sToStr = function(be)
	var str = "";
	for(var i=0;i<be.length*32;i+=8)
		str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
	return str;

// Convert an array of big-endian 32-bit words to a hex string
Crypto.be32sToHex = function(be)
	var hex = "0123456789ABCDEF";
	var str = "";
	for(var i=0;i<be.length*4;i++)
		str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
	return str;

// Return, in hex, the SHA-1 hash of a string
Crypto.hexSha1Str = function(str)
	return Crypto.be32sToHex(Crypto.sha1Str(str));

// Return the SHA-1 hash of a string
Crypto.sha1Str = function(str)
	return Crypto.sha1(Crypto.strToBe32s(str),str.length);

// Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
Crypto.sha1 = function(x,blen)
	// Add 32-bit integers, wrapping at 32 bits
	add32 = function(a,b)
		var lsw = (a&0xFFFF)+(b&0xFFFF);
		var msw = (a>>16)+(b>>16)+(lsw>>16);
		return (msw<<16)|(lsw&0xFFFF);
	// Add five 32-bit integers, wrapping at 32 bits
	add32x5 = function(a,b,c,d,e)
		var lsw = (a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
		var msw = (a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
		return (msw<<16)|(lsw&0xFFFF);
	// Bitwise rotate left a 32-bit integer by 1 bit
	rol32 = function(n)
		return (n>>>31)|(n<<1);

	var len = blen*8;
	// Append padding so length in bits is 448 mod 512
	x[len>>5] |= 0x80 << (24-len%32);
	// Append length
	x[((len+64>>9)<<4)+15] = len;
	var w = Array(80);

	var k1 = 0x5A827999;
	var k2 = 0x6ED9EBA1;
	var k3 = 0x8F1BBCDC;
	var k4 = 0xCA62C1D6;

	var h0 = 0x67452301;
	var h1 = 0xEFCDAB89;
	var h2 = 0x98BADCFE;
	var h3 = 0x10325476;
	var h4 = 0xC3D2E1F0;

	for(var i=0;i<x.length;i+=16) {
		var j,t;
		var a = h0;
		var b = h1;
		var c = h2;
		var d = h3;
		var e = h4;
		for(j = 0;j<16;j++) {
			w[j] = x[i+j];
			t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
		for(j=16;j<20;j++) {
			w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
			t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
		for(j=20;j<40;j++) {
			w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
			t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k2);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
		for(j=40;j<60;j++) {
			w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
			t = add32x5(e,(a>>>27)|(a<<5),(b&c)|(d&(b|c)),w[j],k3);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
		for(j=60;j<80;j++) {
			w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
			t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k4);
			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;

		h0 = add32(h0,a);
		h1 = add32(h1,b);
		h2 = add32(h2,c);
		h3 = add32(h3,d);
		h4 = add32(h4,e);
	return Array(h0,h1,h2,h3,h4);

|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/]]|
|Description|formatted dates plus popup menu with 'journal' link, changes and (optional) reminders|
There are quite a few calendar generators, reminders, to-do lists, 'dated tiddlers' journals, blog-makers and GTD-like schedule managers that have been built around TW.  While they all have different purposes, and vary in format, interaction, and style, in one way or another each of these plugins displays and/or uses date-based information to make finding, accessing and managing relevant tiddlers easier.  This plugin provides a general approach to embedding dates and date-based links/menus within tiddler content.
>see [[DatePluginInfo]]
>see [[DatePluginConfig]]
2008.03.08 [2.7.0] in addModifiedsToPopup(), if a tiddler was created on the specified date, don't list it in the 'changed' section of the popup.  Based on a request from Kashgarinn.
|please see [[DatePluginInfo]] for additional revision details|
2005.10.30 [0.9.0] pre-release
version.extensions.date = {major: 2, minor: 7, revision: 0, date: new Date(2008,3,8)};

config.macros.date = {
	format: "YYYY.0MM.0DD", // default date display format
	linkformat: "YYYY.0MM.0DD", // 'dated tiddler' link format
	linkedbg: "#babb1e", // "babble"
	todaybg: "#ffab1e", // "fable"
	weekendbg: "#c0c0c0", // "cocoa"
	holidaybg: "#ffaace", // "face"
	createdbg: "#bbeeff", // "beef"
	modifiedsbg: "#bbeeff", // "beef"
	remindersbg: "#c0ffee", // "coffee"
	holidays: [ "01/01", "07/04", "07/24", "11/24" ], // NewYearsDay, IndependenceDay(US), Eric's Birthday (hooray!), Thanksgiving(US)
	weekend: [ 1,0,0,0,0,0,1 ] // [ day index values: sun=0, mon=1, tue=2, wed=3, thu=4, fri=5, sat=6 ]

config.macros.date.handler = function(place,macroName,params)
	// do we want to see a link, a popup, or just a formatted date?
	var mode="display";
	if (params[0]=="display") { mode=params[0]; params.shift(); }
	if (params[0]=="popup") { mode=params[0]; params.shift(); }
	if (params[0]=="link") { mode=params[0]; params.shift(); }
	// get the date
	var now = new Date();
	var date = now;
	if (!params[0] || params[0]=="today")
		{ params.shift(); }
	else if (params[0]=="filedate")
		{ date=new Date(document.lastModified); params.shift(); }
	else if (params[0]=="tiddler")
		{ date=store.getTiddler(story.findContainingTiddler(place).id.substr(7)).modified; params.shift(); }
	else if (params[0].substr(0,8)=="tiddler:")
		{ var t; if ((t=store.getTiddler(params[0].substr(8)))) date=t.modified; params.shift(); }
	else {
		var y = eval(params.shift().replace(/Y/ig,(now.getYear()<1900)?now.getYear()+1900:now.getYear()));
		var m = eval(params.shift().replace(/M/ig,now.getMonth()+1));
		var d = eval(params.shift().replace(/D/ig,now.getDate()+0));
		date = new Date(y,m-1,d);
	// date format with optional custom override
	var format=this.format; if (params[0]) format=params.shift();
	var linkformat=this.linkformat; if (params[0]) linkformat=params.shift();

function showDate(place,date,mode,format,linkformat,autostyle,weekend)
	if (!mode) mode="display";
	if (!format) format=config.macros.date.format;
	if (!linkformat) linkformat=config.macros.date.linkformat;
	if (!autostyle) autostyle=false;

	// format the date output
	var title = date.formatString(format);
	var linkto = date.formatString(linkformat);

	// just show the formatted output
	if (mode=="display") { place.appendChild(document.createTextNode(title)); return; }

	// link to a 'dated tiddler'
	var link = createTiddlyLink(place, linkto, false);
	link.title = linkto;
	link.date = date;
	link.format = format;
	link.linkformat = linkformat;

	// if using a popup menu, replace click handler for dated tiddler link
	// with handler for popup and make link text non-italic (i.e., an 'existing link' look)
	if (mode=="popup") {
		link.onclick = onClickDatePopup;
	// format the popup link to show what kind of info it contains (for use with calendar generators)
	if (autostyle) setDateStyle(place,link,weekend);

// NOTE: This function provides default logic for setting the date style when displayed in a calendar
// To customize the date style logic, please see[[DatePluginConfig]]
function setDateStyle(place,link,weekend) {
	// alias variable names for code readability
	var date=link.date;
	var fmt=link.linkformat;
	var linkto=date.formatString(fmt);
	var cmd=config.macros.date;

	if ((weekend!==undefined?weekend:isWeekend(date))&&(cmd.weekendbg!=""))
		{ place.style.background = cmd.weekendbg; }
	if (hasModifieds(date)||hasCreateds(date)||hasTagged(date,fmt))
		{ link.style.fontStyle="normal"; link.style.fontWeight="bold"; }
	if (hasReminders(date))
		{ link.style.textDecoration="underline"; }
	if (isToday(date))
		{ link.style.border="1px solid black"; }
	if (isHoliday(date)&&(cmd.holidaybg!=""))
		{ place.style.background = cmd.holidaybg; }
	if (hasCreateds(date)&&(cmd.createdbg!=""))
		{ place.style.background = cmd.createdbg; }
	if (hasModifieds(date)&&(cmd.modifiedsbg!=""))
		{ place.style.background = cmd.modifiedsbg; }
	if ((hasTagged(date,fmt)||store.tiddlerExists(linkto))&&(cmd.linkedbg!=""))
		{ place.style.background = cmd.linkedbg; }
	if (hasReminders(date)&&(cmd.remindersbg!=""))
		{ place.style.background = cmd.remindersbg; }
	if (isToday(date)&&(cmd.todaybg!=""))
		{ place.style.background = cmd.todaybg; }

function isToday(date) // returns true if date is today
	{ var now=new Date(); return ((now-date>=0) && (now-date<86400000)); }

function isWeekend(date) // returns true if date is a weekend
	{ return (config.macros.date.weekend[date.getDay()]); }

function isHoliday(date) // returns true if date is a holiday
	var longHoliday = date.formatString("0MM/0DD/YYYY");
	var shortHoliday = date.formatString("0MM/0DD");
	for(var i = 0; i < config.macros.date.holidays.length; i++) {
		var holiday=config.macros.date.holidays[i];
		if (holiday==longHoliday||holiday==shortHoliday) return true;
	return false;

// Event handler for clicking on a day popup
function onClickDatePopup(e)
	if (!e) var e = window.event;
	var theTarget = resolveTarget(e);
	var popup = Popup.create(this);
	if(popup) {
		// always show dated tiddler link (or just date, if readOnly) at the top...
		if (!readOnly || store.tiddlerExists(this.date.formatString(this.linkformat)))
		if (!config.options.chkDatePopupHideCreated)
		if (!config.options.chkDatePopupHideChanged)
		if (!config.options.chkDatePopupHideTagged)
		if (!config.options.chkDatePopupHideReminders)
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();

function indexCreateds() // build list of tiddlers, hash indexed by creation date
	var createds= { };
	var tiddlers = store.getTiddlers("title","excludeLists");
	for (var t = 0; t < tiddlers.length; t++) {
		var date = tiddlers[t].created.formatString("YYYY0MM0DD")
		if (!createds[date])
			createds[date]=new Array();
	return createds;
function hasCreateds(date) // returns true if date has created tiddlers
	if (!config.macros.date.createds) config.macros.date.createds=indexCreateds();
	return (config.macros.date.createds[date.formatString("YYYY0MM0DD")]!=undefined);

function addCreatedsToPopup(popup,when,format)
	var force=(store.isDirty() && when.formatString("YYYY0MM0DD")==new Date().formatString("YYYY0MM0DD"));
	if (force || !config.macros.date.createds) config.macros.date.createds=indexCreateds();
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var createds = config.macros.date.createds[when.formatString("YYYY0MM0DD")];
	if (createds) {
		var e=createTiddlyElement(popup,"div",null,null,"created ("+createds.length+")");
		for(var t=0; t<createds.length; t++) {
			var link=createTiddlyLink(popup,createds[t],false);

function indexModifieds() // build list of tiddlers, hash indexed by modification date
	var modifieds= { };
	var tiddlers = store.getTiddlers("title","excludeLists");
	for (var t = 0; t < tiddlers.length; t++) {
		var date = tiddlers[t].modified.formatString("YYYY0MM0DD")
		if (!modifieds[date])
			modifieds[date]=new Array();
	return modifieds;
function hasModifieds(date) // returns true if date has modified tiddlers
	if (!config.macros.date.modifieds) config.macros.date.modifieds = indexModifieds();
	return (config.macros.date.modifieds[date.formatString("YYYY0MM0DD")]!=undefined);

function addModifiedsToPopup(popup,when,format)
	var date=when.formatString("YYYY0MM0DD");
	var force=(store.isDirty() && date==new Date().formatString("YYYY0MM0DD"));
	if (force || !config.macros.date.modifieds) config.macros.date.modifieds=indexModifieds();
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var mods = config.macros.date.modifieds[date];
	if (mods) {
		// if a tiddler was created on this date, don't list it in the 'changed' section
		if (config.macros.date.createds && config.macros.date.createds[date]) {
			var temp=[];
			for(var t=0; t<mods.length; t++)
				if (!config.macros.date.createds[date].contains(mods[t]))
		var e=createTiddlyElement(popup,"div",null,null,"changed ("+mods.length+")");
		for(var t=0; t<mods.length; t++) {
			var link=createTiddlyLink(popup,mods[t],false);

function hasTagged(date,format) // returns true if date is tagging other tiddlers
	return store.getTaggedTiddlers(date.formatString(format)).length>0;

function addTaggedToPopup(popup,when,format)
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var tagged=store.getTaggedTiddlers(when.formatString(format));
	if (tagged.length) var e=createTiddlyElement(popup,"div",null,null,"tagged ("+tagged.length+")");
	for(var t=0; t<tagged.length; t++) {
		var link=createTiddlyLink(popup,tagged[t].title,false);

function indexReminders(date,leadtime) // build list of tiddlers with reminders, hash indexed by reminder date
	var reminders = { };
	if(window.findTiddlersWithReminders!=undefined) { // reminder plugin is installed
		// DEBUG var starttime=new Date();
		var t = findTiddlersWithReminders(date, [0,leadtime], null, null, 1);
		for(var i=0; i<t.length; i++) reminders[t[i].matchedDate]=true;
		// DEBUG var out="Found "+t.length+" reminders in "+((new Date())-starttime+1)+"ms\n";
		// DEBUG out+="startdate: "+date.toLocaleDateString()+"\n"+"leadtime: "+leadtime+" days\n\n";
		// DEBUG for(var i=0; i<t.length; i++) { out+=t[i].matchedDate.toLocaleDateString()+" "+t[i].params.title+"\n"; }
		// DEBUG alert(out);
	return reminders;

function hasReminders(date) // returns true if date has reminders
	if (window.reminderCacheForCalendar)
		return window.reminderCacheForCalendar[date]; // use calendar cache
	if (!config.macros.date.reminders)
		config.macros.date.reminders = indexReminders(date,90); // create a 90-day leadtime reminder cache
	return (config.macros.date.reminders[date]);

function addRemindersToPopup(popup,when,format)
	if(window.findTiddlersWithReminders==undefined) return; // reminder plugin not installed

	var indent = String.fromCharCode(160)+String.fromCharCode(160);
	var reminders=findTiddlersWithReminders(when, [0,31],null,null,1);
	createTiddlyElement(popup,"div",null,null,"reminders ("+(reminders.length||"none")+")");
	for(var t=0; t<reminders.length; t++) {
		link = createTiddlyLink(popup,reminders[t].tiddler,false);
		var diff=reminders[t].diff;
		diff=(diff<1)?"Today":((diff==1)?"Tomorrow":diff+" days");
		var txt=(reminders[t].params["title"])?reminders[t].params["title"]:reminders[t].tiddler;
		link.appendChild(document.createTextNode(indent+diff+" - "+txt));
	if (readOnly) return;	// omit "new reminder..." link
	var link = createTiddlyLink(popup,indent+"new reminder...",true); createTiddlyElement(popup,"br");
	var title = when.formatString(format);
	link.title="add a reminder to '"+title+"'";
	link.onclick = function() {
		// show tiddler editor
		story.displayTiddler(null, title, 2, null, null, false, false);
		// find body 'textarea'
		var c =document.getElementById("tiddler" + title).getElementsByTagName("*");
		for (var i=0; i<c.length; i++) if ((c[i].tagName.toLowerCase()=="textarea") && (c[i].getAttribute("edit")=="text")) break;
		// append reminder macro to tiddler content
		if (i<c.length) {
			if (store.tiddlerExists(title)) c[i].value+="\n"; else c[i].value="";
			c[i].value += "<<reminder";
			c[i].value += " day:"+when.getDate();
			c[i].value += " month:"+(when.getMonth()+1);
			c[i].value += " year:"+when.getFullYear();
			c[i].value += ' title:"Enter a title" >>';
[[Simple examples]]
|''Description:''|Support for deprecated functions removed from core|
if(!version.extensions.DeprecatedFunctionsPlugin) {
version.extensions.DeprecatedFunctionsPlugin = {installed:true};

//-- Deprecated code

// @Deprecated: Use createElementAndWikify and this.termRegExp instead
config.formatterHelpers.charFormatHelper = function(w)

// @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
config.formatterHelpers.monospacedByLineHelper = function(w)
	var lookaheadRegExp = new RegExp(this.lookahead,"mg");
	lookaheadRegExp.lastIndex = w.matchStart;
	var lookaheadMatch = lookaheadRegExp.exec(w.source);
	if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
		var text = lookaheadMatch[1];
			text = text.replace(/\n/g,"\r");
		w.nextMatch = lookaheadRegExp.lastIndex;

// @Deprecated: Use <br> or <br /> instead of <<br>>
config.macros.br = {};
config.macros.br.handler = function(place)

// Find an entry in an array. Returns the array index or null
// @Deprecated: Use indexOf instead
Array.prototype.find = function(item)
	var i = this.indexOf(item);
	return i == -1 ? null : i;

// Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
// @Deprecated: Use store.getLoader().internalizeTiddler instead
Tiddler.prototype.loadFromDiv = function(divRef,title)
	return store.getLoader().internalizeTiddler(store,this,title,divRef);

// Format the text for storage in an HTML DIV
// @Deprecated Use store.getSaver().externalizeTiddler instead.
Tiddler.prototype.saveToDiv = function()
	return store.getSaver().externalizeTiddler(store,this);

// @Deprecated: Use store.allTiddlersAsHtml() instead
function allTiddlersAsHtml()
	return store.allTiddlersAsHtml();

// @Deprecated: Use refreshPageTemplate instead
function applyPageTemplate(title)

// @Deprecated: Use story.displayTiddlers instead
function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,unused3)

// @Deprecated: Use story.displayTiddler instead
function displayTiddler(srcElement,title,template,unused1,unused2,animate,unused3)

// @Deprecated: Use functions on right hand side directly instead
var createTiddlerPopup = Popup.create;
var scrollToTiddlerPopup = Popup.show;
var hideTiddlerPopup = Popup.remove;

// @Deprecated: Use right hand side directly instead
var regexpBackSlashEn = new RegExp("\\\\n","mg");
var regexpBackSlash = new RegExp("\\\\","mg");
var regexpBackSlashEss = new RegExp("\\\\s","mg");
var regexpNewLine = new RegExp("\n","mg");
var regexpCarriageReturn = new RegExp("\r","mg");

var DOM0_globals = {
addEventListener: "DOM Method",
alert: "DOM Method",
atob: "DOM Method",
back: "DOM Method",
blur: "DOM Method",
btoa: "DOM Method",
captureEvents: "DOM Method",
clearInterval: "DOM Method",
clearTimeout: "DOM Method",
close: "DOM Method",
closed: "DOM Property",
Components: "DOM Property",
confirm: "DOM Method",
content: "DOM Property",
controllers: "DOM Property",
crypto: "DOM Property",
defaultStatus: "DOM Property",
defaultStatus: "DOM Property",
directories: "DOM Property",
disableExternalCapture: "DOM Method",
dispatchEvent: "DOM Method",
document: "DOM Property",
dump: "DOM Method",
enableExternalCapture: "DOM Method",
escape: "DOM Method",
find: "DOM Method",
focus: "DOM Method",
forward: "DOM Method",
frameElement: "DOM Property",
frames: "DOM Property",
fullScreen: "DOM Property",
getAttention: "DOM Method",
getComputedStyle: "DOM Method",
getSelection: "DOM Method",
history: "DOM Property",
home: "DOM Method",
innerHeight: "DOM Property",
innerWidth: "DOM Property",
length: "DOM Property",
location: "DOM Property",
locationbar: "DOM Property",
menubar: "DOM Property",
moveBy: "DOM Method",
moveTo: "DOM Method",
name: "DOM Property",
navigator: "DOM Property",
open: "DOM Method",
openDialog: "DOM Method",
opener: "DOM Property",
outerHeight: "DOM Property",
outerWidth: "DOM Property",
pageXOffset: "DOM Property",
pageYOffset: "DOM Property",
parent: "DOM Property",
personalbar: "DOM Property",
pkcs11: "DOM Property",
print: "DOM Method",
prompt: "DOM Method",
prompter: "DOM Property",
releaseEvents: "DOM Method",
removeEventListener: "DOM Method",
resizeBy: "DOM Method",
resizeTo: "DOM Method",
routeEvent: "DOM Method",
screen: "DOM Property",
screenX: "DOM Property",
screenY: "DOM Property",
scroll: "DOM Method",
scrollbars: "DOM Property",
scrollBy: "DOM Method",
scrollByLines: "DOM Method",
scrollByPages: "DOM Method",
scrollMaxX: "DOM Property",
scrollMaxY: "DOM Property",
scrollTo: "DOM Method",
scrollX: "DOM Property",
scrollY: "DOM Property",
self: "DOM Property",
setInterval: "DOM Method",
setResizable: "DOM Method",
setTimeout: "DOM Method",
sidebar: "DOM Property",
sizeToContent: "DOM Method",
status: "DOM Property",
statusbar: "DOM Property",
stop: "DOM Method",
toolbar: "DOM Property",
top: "DOM Property",
unescape: "DOM Method",
updateCommands: "DOM Method",
window: "DOM Property",
getInterface: "???"};

var tw_2_0_3_globals = {
version: "TWCore",
config: "TWCore",
store: "TWCore",
story: "TWCore",
formatter: "TWCore",
anim: "TWCore",
readOnly: "TWCore",
highlightHack: "TWCore",
main: "TWCore",
restart: "TWCore",
saveTest: "TWCore",
loadSystemConfig: "TWCore",
processConfig: "TWCore",
invokeMacro: "TWCore",
Formatter: "TWCore",
wikify: "TWCore",
wikifyPlain: "TWCore",
highlightify: "TWCore",
Wikifier: "TWCore",
Tiddler: "TWCore",
regexpBackSlashEn: "TWCore",
regexpBackSlash: "TWCore",
regexpBackSlashEss: "TWCore",
regexpNewLine: "TWCore",
regexpCarriageReturn: "TWCore",
TiddlyWiki: "TWCore",
displayTiddlers: "TWCore",
displayTiddler: "TWCore",
Story: "TWCore",
displayMessage: "TWCore",
clearMessage: "TWCore",
refreshElements: "TWCore",
applyHtmlMacros: "TWCore",
refreshPageTemplate: "TWCore",
applyPageTemplate: "TWCore",
refreshDisplay: "TWCore",
refreshPageTitle: "TWCore",
refreshStyles: "TWCore",
loadOptionsCookie: "TWCore",
saveOptionCookie: "TWCore",
saveUsingSafari: "TWCore",
startSaveArea: "TWCore",
endSaveArea: "TWCore",
checkUnsavedChanges: "TWCore",
saveChanges: "TWCore",
getBackupPath: "TWCore",
generateRss: "TWCore",
allTiddlersAsHtml: "TWCore",
convertUTF8ToUnicode: "TWCore",
manualConvertUTF8ToUnicode: "TWCore",
mozConvertUTF8ToUnicode: "TWCore",
convertUnicodeToUTF8: "TWCore",
manualConvertUnicodeToUTF8: "TWCore",
mozConvertUnicodeToUTF8: "TWCore",
saveFile: "TWCore",
loadFile: "TWCore",
ieSaveFile: "TWCore",
ieLoadFile: "TWCore",
mozillaSaveFile: "TWCore",
mozillaLoadFile: "TWCore",
operaUrlToFilename: "TWCore",
operaSaveFile: "TWCore",
operaLoadFile: "TWCore",
safariFilenameToUrl: "TWCore",
safariLoadFile: "TWCore",
safariSaveFile: "TWCore",
detectPlugin: "TWCore",
createTiddlyButton: "TWCore",
createTiddlyLink: "TWCore",
refreshTiddlyLink: "TWCore",
createExternalLink: "TWCore",
onClickTiddlerLink: "TWCore",
createTagButton: "TWCore",
onClickTag: "TWCore",
onClickTagOpenAll: "TWCore",
createTiddlyError: "TWCore",
Animator: "TWCore",
Zoomer: "TWCore",
Cascade: "TWCore",
Scroller: "TWCore",
Slider: "TWCore",
Popup: "TWCore",
createTiddlerPopup: "TWCore",
scrollToTiddlerPopup: "TWCore",
hideTiddlerPopup: "TWCore",
RGB: "TWCore",
drawGradient: "TWCore",
createTiddlyText: "TWCore",
createTiddlyElement: "TWCore",
addEvent: "TWCore",
removeEvent: "TWCore",
addClass: "TWCore",
removeClass: "TWCore",
hasClass: "TWCore",
resolveTarget: "TWCore",
getPlainText: "TWCore",
ensureVisible: "TWCore",
findWindowWidth: "TWCore",
findWindowHeight: "TWCore",
findScrollX: "TWCore",
findScrollY: "TWCore",
findPosX: "TWCore",
findPosY: "TWCore",
insertSpacer: "TWCore",
removeChildren: "TWCore",
setStylesheet: "TWCore"};

var tw_1_2_39_globals = {
c: "TWCore1.2",
t: "TWCore1.2",
g: "TWCore1.2",
matchingFormatter: "TWCore1.2",
tiddler: "TWCore1.2",
_saveChanges: "TWCore1.2",
onClickToolbarRefresh: "TWCore1.2",
refreshcallback: "TWCore1.2",
remote: "TWCore1.2",
deletecallback: "TWCore1.2",
savecallback: "TWCore1.2",
namesCounter: "TWCore1.2",
pluginsArrayCounter: "TWCore1.2",
onload: "TWCore1.2",
onunload: "TWCore1.2",
getElementText: "TWCore1.2",
findContainingTiddler: "TWCore1.2",
onClickPermaView: "TWCore1.2",
onClickToolbarUndo: "TWCore1.2",
onClickToolbarSave: "TWCore1.2",
onClickToolbarEdit: "TWCore1.2",
onClickAddTagPopup: "TWCore1.2",
onClickAddTag: "TWCore1.2",
onClickToolbarReferences: "TWCore1.2",
onClickToolbarDelete: "TWCore1.2",
onClickToolbarPermaLink: "TWCore1.2",
onClickToolbarClose: "TWCore1.2",
onDblClickTiddler: "TWCore1.2",
onMouseOutTiddler: "TWCore1.2",
onMouseOverTiddler: "TWCore1.2",
onEditKey: "TWCore1.2",
generateEmpty: "TWCore1.2",
refreshSpecialItem: "TWCore1.2",
refreshSidebar: "TWCore1.2",
refreshSubtitle: "TWCore1.2",
refreshTitle: "TWCore1.2",
refreshMenu: "TWCore1.2",
refreshTabs: "TWCore1.2",
refreshStory: "TWCore1.2",
closeAllTiddlers: "TWCore1.2",
scrubIds: "TWCore1.2",
closeTiddler: "TWCore1.2",
deleteTiddler: "TWCore1.2",
blurTiddler: "TWCore1.2",
deselectTiddler: "TWCore1.2",
selectTiddler: "TWCore1.2",
saveTiddler: "TWCore1.2",
createTiddlerEditor: "TWCore1.2",
createTiddlerFooter: "TWCore1.2",
createTiddlerViewer: "TWCore1.2",
createTiddlerToolbar: "TWCore1.2",
createTiddlerTitle: "TWCore1.2",
createTiddlerSkeleton: "TWCore1.2",
refreshTiddler: "TWCore1.2",
createTiddler: "TWCore1.2",
Packages: "TWCore1.2",
sun: "TWCore1.2",
java: "TWCore1.2",
netscape: "TWCore1.2",
XPCNativeWrapper: "TWCore1.2",
GeckoActiveXObject: "TWCore1.2"};

var google_globals = {
google_cust_job: "google",
google_cust_id: "google",
google_cust_interests: "google",
google_cust_gender: "google",
google_cust_age: "google",
google_cpa_choice: "google",
google_bid: "google",
google_ad_section: "google",
google_ad_region: "google",
google_page_location: "google",
google_skip: "google",
google_feedback: "google",
google_image_size: "google",
google_rt: "google",
google_rl_mode: "google",
google_rl_filtering: "google",
google_max_radlink_len: "google",
google_num_radlinks: "google",
google_kw: "google",
google_kw_type: "google",
google_adtest: "google",
google_color_line: "google",
google_color_border: "google",
google_color_url: "google",
google_color_link: "google",
google_color_text: "google",
google_color_bg: "google",
google_alternate_color: "google",
google_alternate_ad_url: "google",
google_contents: "google",
google_max_num_ads: "google",
google_encoding: "google",
google_safe: "google",
google_hints: "google",
google_city: "google",
google_region: "google",
google_country: "google",
google_gl: "google",
google_language: "google",
google_num_ad_slots: "google",
google_ad_url: "google",
google_prev_ad_formats_by_region: "google",
google_num_slots_by_client: "google",
google_num_slots_by_channel: "google",
google_referrer_url: "google",
google_last_modified_time: "google",
google_page_url: "google",
google_ad_output: "google",
google_ad_frameborder: "google",
onerror: "google",
google_org_error_handler: "google",
google_ad_channel: "google",
google_ad_type: "google",
google_ad_format: "global",
google_ad_height: "google",
google_ad_width: "google",
google_ad_client: "google"};

var result = "";
for (var j in window) {
 if (!(tw_2_0_3_globals[j] || DOM0_globals[j] || google_globals[j] || tw_1_2_39_globals[j])) {
 if (typeof window[j] == "function")
 result += j+"()\n";
 result += "["+typeof window[j]+"] "+j+"="+window[j]+"\n";
return "!Global properties/variables that are not DOM nor TiddyWiki core globals\n{{{\n"+result+"\n}}}\n";
This is a ready-to-view sample to show people what you can do with [[ReminderMacros]].  You can find [[Simple examples]], as well as really complicated [[Reminders|Personal Reminders]].  If you want to incorporate this into your TiddlyWiki, read the [[Installation Instructions]].
<<reminder year:2006 month:3 day:9 title:"please enter a title" format:"TITLE - TIDDLER">>
!!!!Simple reminders that fire on the same month/day of every year.
*<<reminder month:1 day:1 title:"New Year's Day" >>
*<<reminder month:2 day:2 title:"Groundhog Day" >>

!!!!Offset reminders
These are all holidays that are specified as the Nth DAYOFWEEK in Month.
*President's Day is the third Monday of February 
**<<reminder month:2 day:15 offsetdayofweek:1 title:"President's Day">>
*Mother's Day is the second Sunday of May
**<<reminder month:5 day:8 offsetdayofweek:0 title:"Mother's Day">>

*Memorial Day is the last Monday of May (note that offsetdayofweek is a negative number, meaning match backwards)
**<<reminder month:5 day:31 offsetdayofweek:-1 title:"Memorial Day">>

!!!Ignore these
These are just here to fill in the common US holidays
*<<reminder month:2 day:14 title:"Valentine's Day" >>
*<<reminder month:4 day:1 title:"April Fool's Day" >>
*<<reminder month:4 day:15  title:"Tax day">>
*<<reminder month:4 day:22 title:"Earth Day">>
*<<reminder month:6 day:14 title:"Flag Day" >>
*Father's Day is the third Sunday of June 
**<<reminder month:6 day:15 offsetdayofweek:0 title:"Father's Day">>
*<<reminder month:7 day:4 title:"Independence Day" >>
*Labor Day is the first Monday of September
**<<reminder month:9 day:1 offsetdayofweek:1 title:"Labor Day">>
*Columbus Day is the second Monday of October
**<<reminder month:10 day:8 offsetdayofweek:1 title:"Columbus Day">>
*<<reminder month:10 day:31 title:"Halloween" >>
*<<reminder month:11 day:11 title:"Veteran's Day" >>
*Thanksgiving(US) is the third Thursday of November
**<<reminder month:11 day:22 offsetdayofweek:4 title:"Thanksgiving (US)">>
*<<reminder month:12 day:25 title:"Christmas Day" >>
''InlineJavascriptPlugin for ~TiddlyWiki version 1.2.x and 2.0''
^^author: Eric Shulman - ELS Design Studios
source: http://www.TiddlyTools.com/#InlineJavascriptPlugin
license: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^

Insert Javascript executable code directly into your tiddler content. Lets you ''call directly into TW core utility routines, define new functions, calculate values, add dynamically-generated TiddlyWiki-formatted output'' into tiddler content, or perform any other programmatic actions each time the tiddler is rendered.
When installed, this plugin adds new wiki syntax for surrounding tiddler content with {{{<script>}}} and {{{</script>}}} markers, so that it can be treated as embedded javascript and executed each time the tiddler is rendered.

''Deferred execution from an 'onClick' link''
By including a label="..." parameter in the initial {{{<script>}}} marker, the plugin will create a link to an 'onclick' script that will only be executed when that specific link is clicked, rather than running the script each time the tiddler is rendered.

''External script source files:''
You can also load javascript from an external source URL, by including a src="..." parameter in the initial {{{<script>}}} marker (e.g., {{{<script src="demo.js"></script>}}}). This is particularly useful when incorporating third-party javascript libraries for use in custom extensions and plugins. The 'foreign' javascript code remains isolated in a separate file that can be easily replaced whenever an updated library file becomes available.

''Defining javascript functions and libraries:''
Although the external javascript file is loaded while the tiddler content is being rendered, any functions it defines will not be available for use until //after// the rendering has been completed. Thus, you cannot load a library and //immediately// use it's functions within the same tiddler. However, once that tiddler has been loaded, the library functions can be freely used in any tiddler (even the one in which it was initially loaded).

To ensure that your javascript functions are always available when needed, you should load the libraries from a tiddler that will be rendered as soon as your TiddlyWiki document is opened. For example, you could put your {{{<script src="..."></script>}}} syntax into a tiddler called LoadScripts, and then add {{{<<tiddler LoadScripts>>}}} in your MainMenu tiddler.

Since the MainMenu is always rendered immediately upon opening your document, the library will always be loaded before any other tiddlers that rely upon the functions it defines. Loading an external javascript library does not produce any direct output in the tiddler, so these definitions should have no impact on the appearance of your MainMenu.

''Creating dynamic tiddler content''
An important difference between this implementation of embedded scripting and conventional embedded javascript techniques for web pages is the method used to produce output that is dynamically inserted into the document:
* In a typical web document, you use the document.write() function to output text sequences (often containing HTML tags) that are then rendered when the entire document is first loaded into the browser window.
* However, in a ~TiddlyWiki document, tiddlers (and other DOM elements) are created, deleted, and rendered "on-the-fly", so writing directly to the global 'document' object does not produce the results you want (i.e., replacing the embedded script within the tiddler content), and completely replaces the entire ~TiddlyWiki document in your browser window.
* To allow these scripts to work unmodified, the plugin automatically converts all occurences of document.write() so that the output is inserted into the tiddler content instead of replacing the entire ~TiddlyWiki document.

If your script does not use document.write() to create dynamically embedded content within a tiddler, your javascript can, as an alternative, explicitly return a text value that the plugin can then pass through the wikify() rendering engine to insert into the tiddler display. For example, using {{{return "thistext"}}} will produce the same output as {{{document.write("thistext")}}}.

//Note: your script code is automatically 'wrapped' inside a function, {{{_out()}}}, so that any return value you provide can be correctly handled by the plugin and inserted into the tiddler. To avoid unpredictable results (and possibly fatal execution errors), this function should never be redefined or called from ''within'' your script code.//

''Accessing the ~TiddlyWiki DOM''
The plugin provides one pre-defined variable, 'place', that is passed in to your javascript code so that it can have direct access to the containing DOM element into which the tiddler output is currently being rendered.

Access to this DOM element allows you to create scripts that can:
* vary their actions based upon the specific location in which they are embedded
* access 'tiddler-relative' information (use findContainingTiddler(place))
* perform direct DOM manipulations (when returning wikified text is not enough)
an "alert" message box:
<script>alert('InlineJavascriptPlugin: this is a demonstration message');</script>
<script>alert('InlineJavascriptPlugin: this is a demonstration message');</script>

dynamic output:
<script>return (new Date()).toString();</script>
<script>return (new Date()).toString();</script>

wikified dynamic output:
<script>return "link to current user: [["+config.options.txtUserName+"]]";</script>
<script>return "link to current user: [["+config.options.txtUserName+"]]";</script>

dynamic output using 'place' to get size information for current tiddler
 if (!window.story) window.story=window;
 var title=story.findContainingTiddler(place).id.substr(7);
 return title+" is using "+store.getTiddlerText(title).length+" bytes";
 if (!window.story) window.story=window;
 var title=story.findContainingTiddler(place).id.substr(7);
 return title+" is using "+store.getTiddlerText(title).length+" bytes";

creating an 'onclick' button/link that runs a script
<script label="click here">
 if (!window.story) window.story=window;
 alert("Hello World!\nlinktext='"+place.firstChild.data+"'\ntiddler='"+story.findContainingTiddler(place).id.substr(7)+"'");
<script label="click here">
 if (!window.story) window.story=window;
 alert("Hello World!\nlinktext='"+place.firstChild.data+"'\ntiddler='"+story.findContainingTiddler(place).id.substr(7)+"'");

loading a script from a source url
<script src="demo.js">return "loading demo.js..."</script>
<script label="click to execute demo() function">demo()</script>
where http://www.TiddlyTools.com/demo.js contains:
>function demo() { alert('this output is from demo(), defined in demo.js') }
>alert('InlineJavascriptPlugin: demo.js has been loaded');
<script src="demo.js">return "loading demo.js..."</script>
<script label="click to execute demo() function">demo()</script>
import (or copy/paste) the following tiddlers into your document:
''InlineJavascriptPlugin'' (tagged with <<tag systemConfig>>)
!!!!!Revision History
''2006.01.05 [1.4.0]''
added support 'onclick' scripts. When label="..." param is present, a button/link is created using the indicated label text, and the script is only executed when the button/link is clicked. 'place' value is set to match the clicked button/link element.
''2005.12.13 [1.3.1]''
when catching eval error in IE, e.description contains the error text, instead of e.toString(). Fixed error reporting so IE shows the correct response text. Based on a suggestion by UdoBorkowski
''2005.11.09 [1.3.0]''
for 'inline' scripts (i.e., not scripts loaded with src="..."), automatically replace calls to 'document.write()' with 'place.innerHTML+=' so script output is directed into tiddler content
Based on a suggestion by BradleyMeck
''2005.11.08 [1.2.0]''
handle loading of javascript from an external URL via src="..." syntax
''2005.11.08 [1.1.0]''
pass 'place' param into scripts to provide direct DOM access 
''2005.11.08 [1.0.0]''
initial release
This feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]]
version.extensions.inlineJavascript= {major: 1, minor: 4, revision: 0, date: new Date(2006,1,5)};

config.formatters.push( {
 name: "inlineJavascript",
 match: "\\<script",
 lookahead: "\\<script(?: src=\\\"((?:.|\\n)*?)\\\")?(?: label=\\\"((?:.|\\n)*?)\\\")?\\>((?:.|\\n)*?)\\</script\\>",

 handler: function(w) {
 var lookaheadRegExp = new RegExp(this.lookahead,"mg");
 lookaheadRegExp.lastIndex = w.matchStart;
 var lookaheadMatch = lookaheadRegExp.exec(w.source)
 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
 if (lookaheadMatch[1]) { // load a script library
 // make script tag, set src, add to body to execute, then remove for cleanup
 var script = document.createElement("script"); script.src = lookaheadMatch[1];
 document.body.appendChild(script); document.body.removeChild(script);
 if (lookaheadMatch[2] && lookaheadMatch[3]) { // create a link to an 'onclick' script
 // add a link, define click handler, save code in link (pass 'place'), set link attributes
 var link=createTiddlyElement(w.output,"a",null,"tiddlyLinkExisting",lookaheadMatch[2]);
 link.code="function _out(place){"+lookaheadMatch[3]+"};_out(this);"
 link.setAttribute("href","javascript:;"); link.setAttribute("title",""); link.style.cursor="pointer";
 else if (lookaheadMatch[3]) { // run inline script code
 var code="function _out(place){"+lookaheadMatch[3]+"};_out(w.output);"
 try { var out = eval(code); } catch(e) { out = e.description?e.description:e.toString(); }
 if (out && out.length) wikify(out,w.output);
 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
} )
# Download the mainline TiddlyWiki from http://www.tiddlywiki.com
# Copy and paste all of the code from [[ReminderMacros]] into a new tiddler with the systemConfig tag
# Save and reload your TiddlyWiki
# Copy and paste the code from [[Simple examples]] or [[Personal Reminders]] into a new tiddler to test it out.
|''Description:''|Support for legacy (pre 2.1) strike through formatting|
|''Date:''|Jul 21, 2006|
|''Author:''|MartinBudden (mjbudden (at) gmail (dot) com)|
|''License:''|[[BSD open source license]]|

// Ensure that the LegacyStrikeThrough Plugin is only installed once.
if(!version.extensions.LegacyStrikeThroughPlugin) {
version.extensions.LegacyStrikeThroughPlugin = {installed:true};

	name: "legacyStrikeByChar",
	match: "==",
	termRegExp: /(==)/mg,
	element: "strike",
	handler: config.formatterHelpers.createElementAndWikify

} //# end of "install only once"
[[Simple examples]]
[[Install|Installation Instructions]]

<<calendar thismonth>>
Days in pink have reminders set.

<<showReminders leadtime:30 format:"|DIFF|TITLE|" >>
!!!!The next two reminders flag the 15th and 27th of every month to pay bills
*The low leadtime keeps these from showing up in the showReminders macro until 2 days before they are due.
**<<reminder day:15 title:"Bill Day" leadtime:2>>
**<<reminder day:27 title:"Bill Day" leadtime:2>>

!!!Reminder that fires once every N days
*This is a reminder that fires every three weeks.  It's imperative to specify a base date with year, month and day if you want this to return consistent dates.
**<<reminder year:2005 month:7 day:31 recurdays:28 title:"Haircut Day" >>

!!!!Tracking the number of years that a reminder has happened
*This is a reminder that uses firstyear to specify when something started.  Very useful for birthdays and anniversaries.
**<<reminder month:9 day:20 title:"TiddlyWiki's First Release Anniversary" leadtime:60 firstyear:2004>>

|''Version:''|2.3.10 (Jun 28, 2007)|
|''Author:''|Jeremy Sheeley(pop1280 [at] excite [dot] com)<<br>>Maintainer: simon.baird@gmail.com|
|''Licence:''|[[BSD open source license]]|
|''Macros:''|reminder, showreminders, displayTiddlersWithReminders, newReminder|
|''Browser:''|Firefox 1.0.4+; InternetExplorer 6.0|

This plugin provides macros for tagging a date with a reminder.  Use the {{{reminder}}} macro to do this.  The {{{showReminders}}} and {{{displayTiddlersWithReminder}}} macros automatically search through all available tiddlers looking for upcoming reminders.

* Create a new tiddler in your tiddlywiki titled ReminderPlugin and give it the {{{systemConfig}}} tag.  The tag is important because it tells TW that this is executable code.
* Double click this tiddler, and copy all the text from the tiddler's body.
* Paste the text into the body of the new tiddler in your TW.
* Save and reload your TW.
* You can copy some examples into your TW as well.  See [[Simple examples]], [[Holidays]], [[showReminders]] and [[Personal Reminders]]

|>|See [[ReminderSyntax]] and [[showRemindersSyntax]]|

!Revision history
* v2.3.10 (Jun 28, 2007)
** Removed window.story = window backwards compatibility hacks since they were breaking TW 2.2
* v2.3.9 (Apr 26, 2007)
** allow bracketed list format in tags param lets you use tags with spaces
* v2.3.8 (Mar 9, 2006)
**Bug fix: A global variable had snuck in, which was killing FF
**Feature: You can now use TIDDLER and TIDDLERNAME in a regular reminder format
* v2.3.6 (Mar 1, 2006)
**Bug fix: Reminders for today weren't being matched sometimes.
**Feature:  Solidified integration with DatePlugin and CalendarPlugin
**Feature:  Recurring reminders will now return multiple hits in showReminders and the calendar.
**Feature:  Added TIDDLERNAME to the replacements for showReminders format, for plugins that need the title without brackets.
* v2.3.5 (Feb 8, 2006)
**Bug fix: Sped up reminders lots.  Added a caching mechanism for reminders that have already been matched.
* v2.3.4 (Feb 7, 2006)
**Bug fix: Cleaned up code to hopefully prevent the Firefox crash that was causing lots of plugins 
to crash Firefox.  Thanks to http://www.jslint.com
* v2.3.3 (Feb 2, 2006)
**Feature: newReminder now has drop down lists instead of text boxes.
**Bug fix:  A trailing space in a title would trigger an infinite loop.
**Bug fix:  using tag:"birthday !reminder" would filter differently than tag:"!reminder birthday"
* v2.3.2 (Jan 21, 2006)
**Feature: newReminder macro, which will let you easily add a reminder to a tiddler. Thanks to Eric Shulman (http://www.elsdesign.com) for the code to do this.
** Bug fix: offsetday was not working sometimes
** Bug fix: when upgrading to 2.0, I included a bit to exclude tiddlers tagged with excludeSearch.  I've reverted back to searching through all tiddlers
* v2.3.1 (Jan 7, 2006)
**Feature: 2.0 compatibility
**Feature AlanH sent some code to make sure that showReminders prints a message if no reminders are found.
* v2.3.0 (Jan 3, 2006)
** Bug Fix:  Using "Last Sunday (-0)" as a offsetdayofweek wasn't working.
** Bug Fix:  Daylight Savings time broke offset based reminders (for example year:2005 month:8 day:23 recurdays:7 would match Monday instead of Tuesday during DST.


//           ReminderPlugin

version.extensions.ReminderPlugin = {major: 2, minor: 3, revision: 8, date: new Date(2006,3,9), source: "http://remindermacros.tiddlyspot.com/"};

// Configuration
// Modify this section to change the defaults for 
// leadtime and display strings

config.macros.reminders = {};
config.macros["reminder"] = {};
config.macros["newReminder"] = {};
config.macros["showReminders"] = {};
config.macros["displayTiddlersWithReminders"] = {};

config.macros.reminders["defaultLeadTime"] = [0,6000];
config.macros.reminders["defaultReminderMessage"] = "DIFF: TITLE on DATE ANNIVERSARY";
config.macros.reminders["defaultShowReminderMessage"] = "DIFF: TITLE on DATE ANNIVERSARY -- TIDDLER";
config.macros.reminders["defaultAnniversaryMessage"] = "(DIFF)";
config.macros.reminders["untitledReminder"] = "Untitled Reminder";
config.macros.reminders["noReminderFound"] = "Couldn't find a match for TITLE in the next LEADTIMEUPPER days."
config.macros.reminders["todayString"] = "Today";
config.macros.reminders["tomorrowString"] = "Tomorrow";
config.macros.reminders["ndaysString"] = "DIFF days";
config.macros.reminders["emtpyShowRemindersString"] = "There are no upcoming events";

//  Code
// You should not need to edit anything 
// below this.  Make sure to edit this tiddler and copy 
// the code from the text box, to make sure that 
// tiddler rendering doesn't interfere with the copy 
// and paste.

//this object will hold the cache of reminders, so that we don't
//recompute the same reminder over again.
var reminderCache = {};

config.macros.showReminders.handler = function showReminders(place,macroName,params)
   var now = new Date().getMidnight();
   var paramHash = {};
   var leadtime = [0,14];
   paramHash = getParamsForReminder(params);
   var bProvidedDate = (paramHash["year"] != null) || 
			(paramHash["month"] != null) || 
			(paramHash["day"] != null) || 
			(paramHash["dayofweek"] != null);
   if (paramHash["leadtime"] != null)
      leadtime = paramHash["leadtime"];
      if (bProvidedDate)
         //If they've entered a day, we need to make 
         //sure to find it.  We'll reset the 
         //leadtime a few lines down.
         paramHash["leadtime"] = [-10000, 10000];
   var matchedDate = now;
   if (bProvidedDate)
      var leadTimeLowerBound = new Date().getMidnight().addDays(paramHash["leadtime"][0]);
      var leadTimeUpperBound = new Date().getMidnight().addDays(paramHash["leadtime"][1]);
      matchedDate = findDateForReminder(paramHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound); 

   var arr = findTiddlersWithReminders(matchedDate, leadtime, paramHash["tag"], paramHash["limit"]);
   var elem = createTiddlyElement(place,"span",null,null, null);
   var mess = "";
   if (arr.length == 0)
      mess += config.macros.reminders.emtpyShowRemindersString; 
   for (var j = 0; j < arr.length; j++)
      if (paramHash["format"] != null)
         arr[j]["params"]["format"] = paramHash["format"];
         arr[j]["params"]["format"] = config.macros.reminders["defaultShowReminderMessage"];
      mess += getReminderMessageForDisplay(arr[j]["diff"], arr[j]["params"], arr[j]["matchedDate"], arr[j]["tiddler"]);
      mess += "\n";
   wikify(mess, elem, null, null);

config.macros.displayTiddlersWithReminders.handler = function displayTiddlersWithReminders(place,macroName,params)
   var now = new Date().getMidnight();
   var paramHash = {};
   var leadtime = [0,14];
   paramHash = getParamsForReminder(params);
   var bProvidedDate = (paramHash["year"] != null) || 
			(paramHash["month"] != null) || 
			(paramHash["day"] != null) || 
			(paramHash["dayofweek"] != null);
   if (paramHash["leadtime"] != null)
      leadtime = paramHash["leadtime"];
      if (bProvidedDate)
         //If they've entered a day, we need to make 
         //sure to find it.  We'll reset the leadtime 
         //a few lines down.
         paramHash["leadtime"] = [-10000,10000];
   var matchedDate = now;
   if (bProvidedDate)
      var leadTimeLowerBound = new Date().getMidnight().addDays(paramHash["leadtime"][0]);
      var leadTimeUpperBound = new Date().getMidnight().addDays(paramHash["leadtime"][1]);
      matchedDate = findDateForReminder(paramHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound); 
   var arr = findTiddlersWithReminders(matchedDate, leadtime, paramHash["tag"], paramHash["limit"]);
   for (var j = 0; j < arr.length; j++)
      displayTiddler(null, arr[j]["tiddler"], 0, null, false, false, false);

config.macros.reminder.handler = function reminder(place,macroName,params)
   var dateHash = getParamsForReminder(params);
   if (dateHash["hidden"] != null)
   var leadTime = dateHash["leadtime"];
   if (leadTime == null)
      leadTime = config.macros.reminders["defaultLeadTime"]; 
   var leadTimeLowerBound = new Date().getMidnight().addDays(leadTime[0]);
   var leadTimeUpperBound = new Date().getMidnight().addDays(leadTime[1]);
   var matchedDate = findDateForReminder(dateHash, new Date().getMidnight(), leadTimeLowerBound, leadTimeUpperBound);
   if (!store.getTiddler) 
      store.getTiddler=function(title) {return this.tiddlers[title];};
   var title = window.story.findContainingTiddler(place).id.substr(7);
   if (matchedDate != null)
      var diff = matchedDate.getDifferenceInDays(new Date().getMidnight());
      var elem = createTiddlyElement(place,"span",null,null, null);
      var mess = getReminderMessageForDisplay(diff, dateHash, matchedDate, title);
      wikify(mess, elem, null, null);
      createTiddlyElement(place,"span",null,null, config.macros.reminders["noReminderFound"].replace("TITLE", dateHash["title"]).replace("LEADTIMEUPPER", leadTime[1]).replace("LEADTIMELOWER", leadTime[0]).replace("TIDDLERNAME", title).replace("TIDDLER", "[[" + title + "]]") );

config.macros.newReminder.handler = function newReminder(place,macroName,params)
  var today=new Date().getMidnight();
  var formstring = '<html><form>Year: <select name="year"><option value="">Every year</option>';
  for (var i = 0; i < 5; i++)
    formstring += '<option' + ((i == 0) ? ' selected' : '') + ' value="' + (today.getFullYear() +i) + '">' + (today.getFullYear() + i) + '</option>';
  formstring += '</select>&nbsp;&nbsp;Month:<select name="month"><option value="">Every month</option>';
  for (i = 0; i < 12; i++)
    formstring += '<option' + ((i == today.getMonth()) ? ' selected' : '') + ' value="' + (i+1) + '">' + config.messages.dates.months[i] + '</option>';
  formstring += '</select>&nbsp;&nbsp;Day:<select name="day"><option value="">Every day</option>';
  for (i = 1; i < 32; i++)
    formstring += '<option' + ((i == (today.getDate() )) ? ' selected' : '') + ' value="' + i + '">' + i + '</option>';

formstring += '</select>&nbsp;&nbsp;Reminder Title:<input type="text" size="40" name="title" value="please enter a title" onfocus="this.select();"><input type="button" value="ok" onclick="addReminderToTiddler(this.form)"></form></html>';

  var panel = config.macros.slider.createSlider(place,null,"New Reminder","Open a form to add a new reminder to this tiddler");
  wikify(formstring ,panel,null,store.getTiddler(params[1]));

// onclick: process input and insert reminder at 'marker'
window.addReminderToTiddler = function(form) {
   if (!store.getTiddler) 
      store.getTiddler=function(title) {return this.tiddlers[title];};
   var title = window.story.findContainingTiddler(form).id.substr(7);
   var tiddler=store.getTiddler(title);
  var txt='\n<<reminder ';
  if (form.year.value != "")
    txt += 'year:'+form.year.value + ' ';
  if (form.month.value != "")
    txt += 'month:'+form.month.value + ' ';
  if (form.day.value != "")
    txt += 'day:'+form.day.value + ' ';
  txt += 'title:"'+form.title.value+'" ';
  txt +='>>';
   tiddler.set(null,tiddler.text + txt);

function hasTag(tiddlerTags, tagFilters)
  //Make sure we respond well to empty tiddlerTaglists or tagFilterlists
  if (tagFilters.length==0 || tiddlerTags.length==0)
    return true;

  var bHasTag = false;
  /*bNoPos says: "'till now there has been no check using a positive filter"
     Imagine a filterlist consisting of 1 negative filter:
         If the filter isn't matched, we want hasTag to be true.
         Yet bHasTag is still false ('cause only positive filters cause bHasTag to change)
     If no positive filters are present bNoPos is true, and no negative filters are matched so we have not returned false
         Thus: hasTag returns true.
      If at any time a positive filter is encountered, we want at least one of the tags to match it, so we turn bNoPos to false, which
      means bHasTag must be true for hasTag to return true*/
  var bNoPos=true;
for (var t3 = 0; t3 < tagFilters.length; t3++)
      for(var t2=0; t2<tiddlerTags.length; t2++)
           if (tagFilters[t3].length > 1 && tagFilters[t3].charAt(0) == '!') 
              if (tiddlerTags[t2] == tagFilters[t3].substring(1))
                 //If at any time a negative filter is matched, we return false
                  return false;
              if (bNoPos)
                 //We encountered the first positive filter
              if (tiddlerTags[t2] == tagFilters[t3])
                  //A positive filter is matched. As long as no negative filter is matched, hasTag will return true
    return (bNoPos || bHasTag);

//This function searches all tiddlers for the reminder  //macro.  It is intended that other plugins (like //calendar) will use this function to query for 
//upcoming reminders.
//The arguments to this function filter out reminders //based on when they will fire.
//baseDate is the date that is used as "now".  
//leadtime is a two element int array, with leadtime[0] 
//         as the lower bound and leadtime[1] as the
//         upper bound.  A reasonable default is [0,14]
//tags is a space-separated list of tags to use to filter 
//         tiddlers.  If a tag name begins with an !, then 
//         only tiddlers which do not have that tag will 
//         be considered.  For example "examples holidays"  
//         will search for reminders in any tiddlers that  
//         are tagged with examples or holidays and 
//         "!examples !holidays" will search for reminders 
//         in any tiddlers that are not tagged with 
//         examples or holidays.  Pass in null to search 
//         all tiddlers.
//limit.  If limit is null, individual reminders can 
//        override the leadtime specified earlier.  
//        Pass in 1 in order to override that behavior.

window.findTiddlersWithReminders = function findTiddlersWithReminders(baseDate, leadtime, tags, limit)
//   var macroPattern = "<<([^>\\]+)(?:\\*)([^>]*)>>";
   var macroPattern = "<<(reminder)(.*)>>";
   var macroRegExp = new RegExp(macroPattern,"mg");
   var matches = store.search(macroRegExp,"title","");
   var arr = [];
   var tagsArray = null;
   if (tags != null)
      // tagsArray = tags.split(" ");
      tagsArray = tags.readBracketedList(); // allows tags with spaces. thanks Robin Summerhill, 4-Oct-06.
   for(var t=matches.length-1; t>=0; t--)
      if (tagsArray != null)
         //If they specified tags to filter on, and this tiddler doesn't 
	 //match, skip it entirely.
         if ( ! hasTag(matches[t].tags, tagsArray))

      var targetText = matches[t].text;
      do {
         // Get the next formatting match
         var formatMatch = macroRegExp.exec(targetText);
         if(formatMatch && formatMatch[1] != null && formatMatch[1].toLowerCase() == "reminder")
            //Find the matching date.
            var params = formatMatch[2] != null ? formatMatch[2].readMacroParams() : {};
            var dateHash = getParamsForReminder(params);
            if (limit != null || dateHash["leadtime"] == null)
               if (leadtime == null)
                   dateHash["leadtime"] = leadtime;
                  dateHash["leadtime"] = [];
                  dateHash["leadtime"][0] = leadtime[0];
                  dateHash["leadtime"][1] = leadtime[1];
	    if (dateHash["leadtime"] == null)
               dateHash["leadtime"] = config.macros.reminders["defaultLeadTime"]; 
            var leadTimeLowerBound = baseDate.addDays(dateHash["leadtime"][0]);
            var leadTimeUpperBound = baseDate.addDays(dateHash["leadtime"][1]);
            var matchedDate = findDateForReminder(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound);
            while (matchedDate != null)
               var hash = {};
               hash["diff"] = matchedDate.getDifferenceInDays(baseDate);
               hash["matchedDate"] = new Date(matchedDate.getFullYear(), matchedDate.getMonth(), matchedDate.getDate(), 0, 0);
               hash["params"] = cloneParams(dateHash);
               hash["tiddler"] = matches[t].title;
               hash["tags"] = matches[t].tags;
	       if (dateHash["recurdays"] != null || (dateHash["year"] == null))
	         leadTimeLowerBound = leadTimeLowerBound.addDays(matchedDate.getDifferenceInDays(leadTimeLowerBound)+ 1);
                 matchedDate = findDateForReminder(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound);
	       else matchedDate = null;
   if(arr.length > 1)  //Sort the array by number of days remaining.
      arr.sort(function (a,b) {if(a["diff"] == b["diff"]) {return(0);} else {return (a["diff"] < b["diff"]) ? -1 : +1; } });
   return arr;

//This function takes the reminder macro parameters and
//generates the string that is used for display.
//This function is not intended to be called by 
//other plugins.
 window.getReminderMessageForDisplay= function getReminderMessageForDisplay(diff, params, matchedDate, tiddlerTitle)
   var anniversaryString = "";
   var reminderTitle = params["title"];
   if (reminderTitle == null)
      reminderTitle = config.macros.reminders["untitledReminder"];
   if (params["firstyear"] != null)
      anniversaryString = config.macros.reminders["defaultAnniversaryMessage"].replace("DIFF", (matchedDate.getFullYear() - params["firstyear"]));
   var mess = "";
   var diffString = "";
   if (diff == 0)
      diffString = config.macros.reminders["todayString"];
   else if (diff == 1)
      diffString = config.macros.reminders["tomorrowString"];
      diffString = config.macros.reminders["ndaysString"].replace("DIFF", diff);
   var format = config.macros.reminders["defaultReminderMessage"];
   if (params["format"] != null)
      format = params["format"];
   mess = format;
//HACK!  -- Avoid replacing DD in TIDDLER with the date
   mess = mess.replace(/TIDDLER/g, "TIDELER");
   mess = matchedDate.formatStringDateOnly(mess);
   mess = mess.replace(/TIDELER/g, "TIDDLER");
   if (tiddlerTitle != null)
      mess = mess.replace(/TIDDLERNAME/g, tiddlerTitle);
      mess = mess.replace(/TIDDLER/g, "[[" + tiddlerTitle + "]]");
   mess = mess.replace("DIFF", diffString).replace("TITLE", reminderTitle).replace("DATE", matchedDate.formatString("DDD MMM DD, YYYY")).replace("ANNIVERSARY", anniversaryString);
   return mess;

// Parse out the macro parameters into a hashtable.  This
// handles the arguments for reminder, showReminders and 
// displayTiddlersWithReminders.
window.getParamsForReminder = function getParamsForReminder(params)
   var dateHash = {};
   var type = "";
   var num = 0;
   var title = "";
   for(var t=0; t<params.length; t++)
      var split = params[t].split(":");
      type = split[0].toLowerCase();
      var value = split[1];
      for (var i=2; i < split.length; i++)
         value += ":" + split[i];
      if (type == "nolinks" || type == "limit" || type == "hidden")
         num = 1;
      else if (type == "leadtime")
         var leads = value.split("...");
         if (leads.length == 1)
            leads[1]= leads[0];
            leads[0] = 0;
         leads[0] = parseInt(leads[0], 10);
         leads[1] = parseInt(leads[1], 10);
         num = leads;
      else if (type == "offsetdayofweek")
          if (value.substr(0,1) == "-")
             dateHash["negativeOffsetDayOfWeek"] = 1;
	     value = value.substr(1);
          num = parseInt(value, 10);
      else if (type != "title" && type != "tag" && type != "format")
         num = parseInt(value, 10);
         title = value;
         while (title.substr(0,1) == '"' && title.substr(title.length - 1,1) != '"' && params[t] != undefined)
            title += " " + params[t++];
         //Trim off the leading and trailing quotes
         if (title.substr(0,1) == "\"" && title.substr(title.length - 1,1)== "\"")
            title = title.substr(1, title.length - 2);
         num = title;
      dateHash[type] = num;
   //date is synonymous with day
   if (dateHash["day"] == null)
      dateHash["day"] = dateHash["date"];
   return dateHash;

//This function finds the date specified in the reminder 
//parameters.  It will return null if no match can be
//found.  This function is not intended to be used by
//other plugins.
window.findDateForReminder= function findDateForReminder( dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound)
   if (baseDate == null)
     baseDate = new Date().getMidnight();
   var hashKey = baseDate.convertToYYYYMMDDHHMM();
   for (var k in dateHash)
      hashKey += "," + k + "|" + dateHash[k];
   hashKey += "," + leadTimeLowerBound.convertToYYYYMMDDHHMM();
   hashKey += "," + leadTimeUpperBound.convertToYYYYMMDDHHMM();
   if (reminderCache[hashKey] == null)
      //If we don't find a match in this run, then we will
      //cache that the reminder can't be matched.
      reminderCache[hashKey] = false;
   else if (reminderCache[hashKey] == false)
      //We've already tried this date and failed
      return null;
      return reminderCache[hashKey];
   var bOffsetSpecified = dateHash["offsetyear"] != null || 
				dateHash["offsetmonth"] != null || 
				dateHash["offsetday"] != null || 
				dateHash["offsetdayofweek"] != null || 
				dateHash["recurdays"] != null;
   // If we are matching the base date for a dayofweek offset, look for the base date a 
   //little further back.
   var tmp1leadTimeLowerBound = leadTimeLowerBound;  
   if ( dateHash["offsetdayofweek"] != null)
      tmp1leadTimeLowerBound = leadTimeLowerBound.addDays(-6);  
   var matchedDate = baseDate.findMatch(dateHash, tmp1leadTimeLowerBound, leadTimeUpperBound);
   if (matchedDate != null)
      var newMatchedDate = matchedDate;
      if (dateHash["recurdays"] != null)
         while (newMatchedDate.getTime() < leadTimeLowerBound.getTime())
            newMatchedDate = newMatchedDate.addDays(dateHash["recurdays"]);
      else if (dateHash["offsetyear"] != null || 
		dateHash["offsetmonth"] != null || 
		dateHash["offsetday"] != null || 
		dateHash["offsetdayofweek"] != null)
         var tmpdateHash = cloneParams(dateHash);
         tmpdateHash["year"] = dateHash["offsetyear"];
         tmpdateHash["month"] = dateHash["offsetmonth"];
         tmpdateHash["day"] = dateHash["offsetday"];
         tmpdateHash["dayofweek"] = dateHash["offsetdayofweek"];
	 var tmpleadTimeLowerBound = leadTimeLowerBound;
	 var tmpleadTimeUpperBound = leadTimeUpperBound;
	 if (tmpdateHash["offsetdayofweek"] != null)
	 	if (tmpdateHash["negativeOffsetDayOfWeek"] == 1)
		   tmpleadTimeLowerBound = matchedDate.addDays(-6);
		   tmpleadTimeUpperBound = matchedDate;

		   tmpleadTimeLowerBound = matchedDate;
		   tmpleadTimeUpperBound = matchedDate.addDays(6);

	 newMatchedDate = matchedDate.findMatch(tmpdateHash, tmpleadTimeLowerBound, tmpleadTimeUpperBound);
         //The offset couldn't be matched.  return null.
         if (newMatchedDate == null)
            return null;
      if (newMatchedDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
         reminderCache[hashKey] = newMatchedDate;
         return newMatchedDate;
   return null;

//This does much the same job as findDateForReminder, but
//this one doesn't deal with offsets or recurring 
Date.prototype.findMatch = function findMatch(dateHash, leadTimeLowerBound, leadTimeUpperBound)

   var bSpecifiedYear =     (dateHash["year"] != null);
   var bSpecifiedMonth =     (dateHash["month"] != null);
   var bSpecifiedDay =     (dateHash["day"] != null);
   var bSpecifiedDayOfWeek =     (dateHash["dayofweek"] != null);
   if (bSpecifiedYear && bSpecifiedMonth && bSpecifiedDay)
      return new Date(dateHash["year"], dateHash["month"]-1, dateHash["day"], 0, 0);
   var bMatchedYear = !bSpecifiedYear;
   var bMatchedMonth = !bSpecifiedMonth;
   var bMatchedDay = !bSpecifiedDay;
   var bMatchedDayOfWeek = !bSpecifiedDayOfWeek;
   if (bSpecifiedDay && bSpecifiedMonth && !bSpecifiedYear && !bSpecifiedDayOfWeek)

      //Shortcut -- First try this year.  If it's too small, try next year.
      var tmpMidnight = this.getMidnight();
      var tmpDate = new Date(this.getFullYear(), dateHash["month"]-1, dateHash["day"], 0,0);
      if (tmpDate.getTime() < leadTimeLowerBound.getTime())
         tmpDate = new Date((this.getFullYear() + 1), dateHash["month"]-1, dateHash["day"], 0,0);
      if ( tmpDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
         return tmpDate;
         return null;

   var newDate = leadTimeLowerBound; 
   while (newDate.isBetween(leadTimeLowerBound, leadTimeUpperBound))
      var tmp = testDate(newDate, dateHash, bSpecifiedYear, bSpecifiedMonth, bSpecifiedDay, bSpecifiedDayOfWeek);
      if (tmp != null)
        return tmp;
      newDate = newDate.addDays(1);

function testDate(testMe, dateHash, bSpecifiedYear, bSpecifiedMonth, bSpecifiedDay, bSpecifiedDayOfWeek)
   var bMatchedYear = !bSpecifiedYear;
   var bMatchedMonth = !bSpecifiedMonth;
   var bMatchedDay = !bSpecifiedDay;
   var bMatchedDayOfWeek = !bSpecifiedDayOfWeek;
   if (bSpecifiedYear)
      bMatchedYear = (dateHash["year"] == testMe.getFullYear());
   if (bSpecifiedMonth)
      bMatchedMonth = ((dateHash["month"] - 1)  == testMe.getMonth() );
   if (bSpecifiedDay)
      bMatchedDay = (dateHash["day"] == testMe.getDate());
   if (bSpecifiedDayOfWeek)
      bMatchedDayOfWeek = (dateHash["dayofweek"] == testMe.getDay());

   if (bMatchedYear && bMatchedMonth && bMatchedDay && bMatchedDayOfWeek)
      return testMe;

//Returns true if the date is in between two given dates
Date.prototype.isBetween = function isBetween(lowerBound, upperBound)
  return (this.getTime() >= lowerBound.getTime() && this.getTime() <= upperBound.getTime());
//Return a new date, with the time set to midnight (0000)
Date.prototype.getMidnight = function getMidnight()
   return new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0);
// Add the specified number of days to a date.
Date.prototype.addDays = function addDays(numberOfDays)
   return new Date(this.getFullYear(), this.getMonth(), this.getDate() + numberOfDays, 0, 0);
//Return the number of days between two dates.
Date.prototype.getDifferenceInDays = function getDifferenceInDays(otherDate)
//I have to do it this way, because this way ignores daylight savings
   var tmpDate = this.addDays(0);
   if (this.getTime() > otherDate.getTime())
      var i = 0;
      for (i = 0; tmpDate.getTime() > otherDate.getTime(); i++)
         tmpDate = tmpDate.addDays(-1);
      return i;
      var i = 0;
      for (i = 0; tmpDate.getTime() < otherDate.getTime(); i++)
         tmpDate = tmpDate.addDays(1);
      return i * -1;
   return 0;
function cloneParams(what) {
    var tmp = {};
    for (var i in what) {
        tmp[i] = what[i];
    return tmp;
// Substitute date components into a string
Date.prototype.formatStringDateOnly = function formatStringDateOnly(template)
	template = template.replace("YYYY",this.getFullYear());
	template = template.replace("YY",String.zeroPad(this.getFullYear()-2000,2));
	template = template.replace("MMM",config.messages.dates.months[this.getMonth()]);
	template = template.replace("0MM",String.zeroPad(this.getMonth()+1,2));
	template = template.replace("MM",this.getMonth()+1);
	template = template.replace("DDD",config.messages.dates.days[this.getDay()]);
	template = template.replace("0DD",String.zeroPad(this.getDate(),2));
	template = template.replace("DD",this.getDate());
	return template;

The reminder macro can take the following arguments.

!!!!date syntax
* @@{{{year:NUMBER}}}@@ - The four digit representation of the year (for example {{{year:2046}}} or {{{year:1999}}}
* @@{{{month:NUMBER}}}@@ - The numerical representation of the month (for example {{{month:1}}} for January, {{{month:12}}} for December)
* @@{{{day:NUMBER}}}@@ - The numerical representation of the day of the month (for example {{{day:15}}} will match the 15th day of the month)
* @@{{{dayofweek:NUMBER}}}@@ - The numerical representation of the day of the week.  Valid values are in the range of 0-6.  {{{dayofweek:0}}} will match Sunday, and {{{dayofweek:6}}} will match Saturday.

* @@{{{offsetdayofweek:NUMBER}}}@@ - The numerical representation of a day of the week.  Valid values are in the range of 0-6.  0 will match Sunday, and 6 will match Saturday.  If offsetdayofweek is specified, the year, month, day and dayofweek will be matched as usual, and the reminder will be set to the next occurence of the day of the week specified by offsetdayofweek. For example, the first Thursday of the month can be specified as {{{day:1 offsetdayofweek:4}}} and the second Thursday can be specified as {{{day:8 offsetdayofweek4}}} If offsetdayofweek is negative, the search will be performed backward.  For example, the last Thursday in August can be found by {{{month:8 day:31 offsetdayofweek:-4}}}
* @@{{{recurdays:NUMBER}}}@@ - If recurdays is set, then the reminder will fire on the base date specified by year, month, day, and dayofweek and also every N days afterward.  For example, if the reminder is specified with {{{year:2005 month:8 day:16 recurdays:2}}} it will match August 16, 18, 20, etc.  Please make sure that you fully specify year, month and day in any recurring reminder.

* @@{{{leadtime:NUMBER}}}@@ - Use this to specify when this reminder will appear in [[showReminders]].  If a reminder has a leadtime of 2, it will only show up in showReminders if it will be matched in the next two days.  Likewise, a reminder with a leadtime of 60 will show up in showReminders even if showReminders has a lower leadtime.  showReminders can override this behavior with the limit argument.

!!!!Reminder display options
* @@{{{title:"STRING"}}}@@ - A string used to identify this reminder when it is shown in a list of reminders. For example, {{{title:"New Year's Day"}}} or {{{title:"Elvis' Birthday"}}}.  You can put standard TiddlyWiki formatting in the title.
* @@{{{format:"STRING"}}}@@ - Use this argument to override the default string used for display.  You can put standard TiddlyWiki formatting in the format.  The following substitutions will be made in the string before it is displayed.
** DIFF will be replaced with the one of the strings "Today", "Tommorrow", or "N days", where N is the number of days between now and the date of the reminder.  
** TITLE will be replaced with the title of the reminder
** DATE will be replaced with the matched date of the reminder.
** ANNIVERSARY will be replaced with the number of years since between the matched date and firstyear
The default string is "DIFF: TITLE on DATE ANNIVERSARY"
* @@{{{firstyear:NUMBER}}}@@ - The first year that a reminder occurred, in four digit format.  For example {{{firstyear:2001}}}.  This is used when calculating the number of years that a reminder has happened.
* @@{{{hidden}}}@@ - If this option is present, the reminder will not be displayed in the regular view of the tiddler.  You can use this to have reminders for [[displayTiddlersWithReminders]] to find, without having the countdown appear.  See [[Season's Greetings example]] for an example.

There is a hidden tag on this reminder means that it doesn't display in the text of the tiddler.  That way, the reminder is there for showReminders and displayTiddlersWithReminders to find, but the normal countdown doesn't display.
<<reminder month:12 day:22 hidden>>
There is a lot of flexibility and power in [[ReminderMacros]].  You can set a reminder to fire on a particular date like August 8th,  or a more non-specific date like "every Friday".  Edit this tiddler and view the source to see the ReminderSyntax
!!!!reminder examples:
<<reminder month:8 day:17 title:"@@color(red):August 17th@@">>
<<reminder dayofweek:5 title:"Next Friday" >>
<<reminder day:13 dayofweek:5 title:"Next Friday the 13th" >>
<<reminder day:8 offsetdayofweek:2 title:"Second Tuesday of the Month">> 
For more advanced examples, see: [[Personal Reminders]] and [[Holidays]]
!!!!newReminder example:
You can use the newReminder macro to bring up a form to fill out.  Hitting Ok will append the reminder to the end of the current tiddler. You can also [[add a newReminder button to each tiddler|Add a newReminder button to each tiddler]].
!!!![[showReminders]] example:
You can also use the showReminders macro to search through all of your tiddlers and list the ones that will fire within a range from today

For more advanced usage, see [[showReminders]] and [[showRemindersSyntax]].

<<showReminders leadtime:30 >>

This is the same showReminders call, but formatted as a table.

<<showReminders leadtime:30 format:"|DIFF|TITLE|TIDDLER|" >>

TiddlyWiki Reminder Plugin
|''Description:''|Sparklines macro|
if(!version.extensions.SparklinePlugin) {
version.extensions.SparklinePlugin = {installed:true};

//-- Sparklines

config.macros.sparkline = {};
config.macros.sparkline.handler = function(place,macroName,params)
	var data = [];
	var min = 0;
	var max = 0;
	var v;
	for(var t=0; t<params.length; t++) {
		v = parseInt(params[t]);
		if(v < min)
			min = v;
		if(v > max)
			max = v;
	if(data.length < 1)
	var box = createTiddlyElement(place,"span",null,"sparkline",String.fromCharCode(160));
	box.title = data.join(",");
	var w = box.offsetWidth;
	var h = box.offsetHeight;
	box.style.paddingRight = (data.length * 2 - w) + "px";
	box.style.position = "relative";
	for(var d=0; d<data.length; d++) {
		var tick = document.createElement("img");
		tick.border = 0;
		tick.className = "sparktick";
		tick.style.position = "absolute";
		tick.src = "data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B";
		tick.style.left = d*2 + "px";
		tick.style.width = "2px";
		v = Math.floor(((data[d] - min)/(max-min)) * h);
		tick.style.top = (h-v) + "px";
		tick.style.height = v + "px";

|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/]]|
|Description|Adds Find/Again keyboard search, autosize, and 'stretch bar' resize for textarea controls|
* ''Control-F'' and ''control-G'' will ''"Find text"'' and ''"find text aGain"'', respectively, allowing you to copy, find, paste, findagain, paste, etc to perform "search-and-replace" actions.  
* ''autosizeEditor'' - toggles the tiddler editor textarea height between fixed-height and "automatically fit the contents".
* ''resizeEditor'' - adds 'grab handle' below textarea to stretch field height
>see [[TextAreaPluginInfo]]
<<option chkTextAreaExtensions>> use control-f (find), control-g (find again) inside text area
<<option chkDisableAutoSelect>> place cursor at start of textarea instead of pre-selecting content
<<option chkResizeEditor>> modify shadow EditTemplate to add resizeable text area (and autosize command)
''2008.01.08 [2.1.9]'' fixed default setting of uninitialized option values so that "false" is not treated as "undefined"
|please see [[TextAreaPluginInfo]] for additional revision details|
''2006.01.22 [1.0.0]'' Moved from temporary "System Tweaks" tiddler into 'real' TextAreaPlugin tiddler.
version.extensions.textAreaPlugin= {major: 2, minor: 1, revision: 9, date: new Date(2008,1,8)};

if (config.options.chkTextAreaExtensions===undefined) config.options.chkTextAreaExtensions=true;
if (config.options.chkDisableAutoSelect===undefined) config.options.chkDisableAutoSelect=true;
if (config.options.chkResizeEditor===undefined) config.options.chkResizeEditor=true;

// automatically tweak shadow EditTemplate to add "autosizeEditor" toolbar command
if (config.options.chkResizeEditor)
	config.shadowTiddlers.EditTemplate=config.shadowTiddlers.EditTemplate.replace(/deleteTiddler/,"deleteTiddler autosizeEditor");
// automatically tweak shadow EditTemplate to add "resizeEditor" macro
if (config.options.chkResizeEditor)
	config.shadowTiddlers.EditTemplate+="<span macro='resizeEditor'></span>";

// Put focus in a specified tiddler field
Story.prototype.focusTiddler = function(title,field)
	this.TextAreaExtensions_focusTiddler.apply(this,arguments); // first call core
	var e = this.getTiddlerField(title,field);
	if (e && config.options.chkDisableAutoSelect) {
		if (e.setSelectionRange) // FF
		else if (e.createTextRange) // IE
			{ var r=e.createTextRange(); r.collapse(true); r.select(); }
	if (e && config.options.chkTextAreaExtensions) addKeyDownHandlers(e);

function addKeyDownHandlers(e)
	// exit if not textarea or element doesn't allow selections
	if (e.tagName.toLowerCase()!="textarea"||!e.setSelectionRange||e.initialized) return;

	// utility function: exits keydown handler and prevents browser from processing the keystroke
	var processed=function(ev) {
		ev.cancelBubble=true; // IE4+
		try{event.keyCode=0;}catch(e){}; // IE5
		if (window.event) ev.returnValue=false; // IE6
		if (ev.preventDefault) ev.preventDefault(); // moz/opera/konqueror
		if (ev.stopPropagation) ev.stopPropagation(); // all
		return false;
	// capture keydown in edit field
	e.saved_onkeydown=e.onkeydown; // save current keydown handler (if any)
	e.onkeydown=function(ev) { if (!ev) var ev=window.event;
		var key=ev.keyCode;
		if (!key) {
			var char=event.which?event.which:event.charCode;
			if (char==102) key=70;
			if (char==103) key=71;
		// process CTRL-F (find matching text) or CTRL-G (find next match)
		if (ev.ctrlKey && (key==70||key==71)) {

			// prompt for text to find
			var defFind=e.findText?e.findText:e.value.substring(e.selectionStart,e.selectionEnd);
			if (key==70||!e.findText||!e.findText.length) // ctrl-f or no saved search text
				{ var f=prompt("find:", defFind); e.focus(); if (f) e.findText=f; }
			if (!e.findText||!e.findText.length) return processed(ev); //  if no search text, exit

			// do case-insensitive match with 'wraparound'...  if not found, alert and exit 
			var newstart=e.value.toLowerCase().indexOf(e.findText.toLowerCase(),e.selectionStart+1);
			if (newstart==-1) newstart=e.value.toLowerCase().indexOf(e.findText.toLowerCase());
			if (newstart==-1) { alert("'"+e.findText+"' not found"); e.focus(); return processed(ev); }

			// set new selection, scroll it into view, and report line position in status bar
			var linecount=e.value.split('\n').length;
			var thisline=e.value.substr(0,e.selectionStart).split('\n').length;
			window.status="line: "+thisline+"/"+linecount;
			return processed(ev);
		if (e.saved_onkeydown) // call previous keydown handler (if any)

// // 'autosize' toolbar command
config.commands.autosizeEditor = {
	text: 'autosize',
	tooltip: 'automatically adjust the editor height to fit the contents',
	text_alt: '[autosize]',
	tooltip_alt: 'reset the editor to the standard height',
	hideReadOnly: false,
	handler: function(event,src,title) {
		var here=story.findContainingTiddler(src); if (!here) return;
		var ta=here.getElementsByTagName('textarea'); if (!ta) return;
		for (i=0;i<ta.length;i++) {
			// only autosize textareas actually used to edit tiddler fields
			if (ta[i].getAttribute("edit")==undefined) continue;
			if (!ta[i].maxed)
		return false;
	on: function(e) {
		if (e.maxed) return; // already autosizing!
		if (e.savedheight==undefined)
		if (e.savedkeyup==undefined) {
			e.onkeyup=function(ev) {
				if (!ev) var ev=window.event; var e=resolveTarget(ev);
				if (e.savedkeyup) e.savedkeyup();
		// IE reports error: "not implemented" for onkeypress
		if (!config.browser.isIE && e.savedkeypress==undefined) {
			e.onkeypress=function(ev) {
				if (!ev) var ev=window.event; var e=resolveTarget(ev);
				if (ev.keyCode==33) { // PGUP
					if (window.scrollByPages) window.scrollByPages(-1);
					return false;
				if (ev.keyCode==34) { // PGDN
					if (window.scrollByPages) window.scrollByPages(1);
					return false;
				if (e.savedkeypress) e.savedkeypress();
	off: function(e,resetHeight) {
		if (resetHeight) e.style.height=e.savedheight;
		// IE reports error: "not implemented" for onkeypress
		if (!config.browser.isIE) e.onkeypress=e.savedkeypress;

// // grab-and-stretch handle
config.macros.resizeEditor = { // add stretch bar to editor textarea
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var here=story.findContainingTiddler(place); if (!here) return;
		var ta=here.getElementsByTagName('textarea');
		if (ta) for (i=0;i<ta.length;i++) {
			// only resize tiddler editor textareas
			if (ta[i].getAttribute("edit")==undefined) continue;
			new window.TextAreaResizer(ta[i]);

config.macros.resizeTiddler = { // add stretch bar to tiddler viewer element
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var here=story.findContainingTiddler(place); if (!here) return;
		var elems=here.getElementsByTagName('div');
		if (elems) for (i=0;i<elems.length;i++) if (hasClass(elems[i],'viewer')) break;
		if (i<elems.length) new window.TextAreaResizer(elems[i]);

config.macros.resizeFrame = { // add stretch bar to iframes
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var here=story.findContainingTiddler(place); if (!here) return;
		var fr=here.getElementsByTagName('iframe');
		if (fr) for (i=0;i<fr.length;i++) new window.TextAreaResizer(fr[i]);

// TextAreaResizer script by Jason Johnston (jj@lojjic.net)
// Created August 2003.  Use freely, but give me credit.
// adds a handle below textareas that the user can drag with the mouse to resize the textarea.
// MODIFIED by ELS for cross-browser (IE) compatibility, including:
//    fixups and adjustments to CSS styles,
//    use 'old style' assignment of mouse event handlers instead of using addEventListener(),
//    use window.event if event param is null,
//    use offsetHeight instead of getComputedStyle()
//    use explicit window.* global scope declaration for functions called from event handlers

window.TextAreaResizer = function(elt) {
	this.element = elt;
window.TextAreaResizer.prototype = {
	create : function() {
		var elt = this.element;
		var thisRef = this;
		var h = this.handle = document.createElement("div");
		h.style.height = "3px"; // was 4px... looked too fat!
		h.style.overflow = "hidden"; // ELS: force IE to trim height to < 1em
		h.style.backgroundColor = "#999"; // ELS: standard mid-tone (dark) gray
		h.style.cursor = "s-resize";
		h.title = "Drag to resize text box";
		elt.parentNode.insertBefore(h, elt.nextSibling);
	dragStart : function(evt) {
		if (!evt) var evt=window.event;
		this.dragStop(evt); // ELS: stop any current drag processing first
		var thisRef = this;
		this.dragStartY = evt.clientY;
		this.dragStartH = this.element.offsetHeight;
	dragMove : function(evt) {
		if (!evt) var evt=window.event;
		// ELS: make sure height is at least 10px
		var h=this.dragStartH+evt.clientY-this.dragStartY;
		if (h<10) h=10; this.element.style.height=h+"px";
		// ELS: match handle to textarea width (which may have changed due to document scrollbars)
		this.handle.style.width=(this.element.offsetWidth-4)+"px"; // 4-pixel fudge factor for textarea border edge
		// ELS: when manually resizing, disable autoresizing (without restoring saved height)
		if (this.element.maxed!=undefined && this.element.maxed)
	dragStop : function(evt) {
		if (!evt) var evt=window.event;
	destroy : function() {
		var elt = this.element;
		elt.style.height = "";
Description: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 = 'remindermacros';

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

'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."

 "| 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]]|"

 "<<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>"

 "tiddlyspot password:",
 "<<option pasUploadPassword>>",

| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 30/03/2008 00:25:29 | YourName | [[/|http://remindermacros.tiddlyspot.com/]] | [[store.cgi|http://remindermacros.tiddlyspot.com/store.cgi]] | . | [[index.html | http://remindermacros.tiddlyspot.com/index.html]] | . |
| 30/03/2008 12:30:12 | YourName | [[/|http://remindermacros.tiddlyspot.com/]] | [[store.cgi|http://remindermacros.tiddlyspot.com/store.cgi]] | . | [[index.html | http://remindermacros.tiddlyspot.com/index.html]] | . |
| 30/03/2008 12:42:51 | YourName | [[/|http://remindermacros.tiddlyspot.com/]] | [[store.cgi|http://remindermacros.tiddlyspot.com/store.cgi]] | . | [[index.html | http://remindermacros.tiddlyspot.com/index.html]] | . |
| 30/03/2008 12:46:20 | YourName | [[/|http://remindermacros.tiddlyspot.com/]] | [[store.cgi|http://remindermacros.tiddlyspot.com/store.cgi]] | . | [[index.html | http://remindermacros.tiddlyspot.com/index.html]] | . |
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Date:''|Apr 19, 2007|
|''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
			// checkbox linked with this password "save this password on this computer"
			// text savePasswordCheckboxLabel
		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]) 
		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

if (!config.options['pasPassword'])
	config.options['pasPassword'] = '';

		pasPassword: "Test password"

|''Description:''|Save to web a TiddlyWiki|
|''Date:''|May 5, 2007|
|''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)|
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)
	var label;
	if (document.location.toString().substr(0,4) == "http") 
		label = this.label.saveLabel;
		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) {
			return false;
		if (config.macros.upload.authenticateUser && (!username || !password)) {
			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();
		var markList = wizard.getElement("markList");
		var listWrapper = document.createElement("div");
		var uploadCaption;
		if (document.location.toString().substr(0,4) == "http") 
			uploadCaption = config.macros.upload.label.saveLabel;
			uploadCaption = config.macros.upload.label.uploadLabel;
				{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 = [
		var opts = [];
		for(i=0; i<uploadOpts.length; i++) {
			var opt = {};
			opt.option = "";
			n = uploadOpts[i];
			opt.name = n;
			opt.lowlight = !config.optionsDesc[n];
			opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
		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) {
	onCancel: function(e)
		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) {
		if (bidix.debugMode) 
		// Locate the storeArea div's 
		var posDiv = locateStoreArea(original);
		if((posDiv[0] == -1) || (posDiv[1] == -1)) {
	if(onlyIfDirty && !store.isDirty())
	// save on localdisk ?
	if (document.location.toString().substr(0,4) == "file") {
		var path = document.location.toString();
		var localPath = getLocalPath(path);
	// 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);
	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")
	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:")));
		} else {
	// 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]);
	} else {

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:")));
			var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
		} else {
	// do uploadMain
	var revised = bidix.upload.updateOriginal(original,posDiv);

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)
		if ((bidix.debugMode) || (responseText.indexOf("Debug mode") >= 0 )) {
			if (responseText.indexOf("Debug mode") >= 0 )
				responseText = responseText.substring(responseText.indexOf("\n\n")+2);
		} else if (responseText.charAt(0) != '0') 
		if (responseText.charAt(0) != '0')
			status = null;
	// 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")
	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)) {
	var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
				store.allTiddlersAsHtml() + "\n" +
	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();
	return this;

bidix.UploadLog.prototype.addText = function(text) {
	if (!this.tiddler)
	// 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)
			this.tiddler.text = textArray.join('\n');		
	// update tiddler fields
	this.tiddler.modifier = config.options.txtUserName;
	this.tiddler.modified = new Date();
	// refresh and notifiy for immediate update
	store.notify(this.tiddler.title, true);

bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir,  backupDir) {
	if (!this.tiddler)
	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 + " |";

bidix.UploadLog.prototype.endUpload = function(status) {
	if (!this.tiddler)
	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) 
	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) 
	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");

	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

/* don't want this for tiddlyspot sites

// Backstage
	uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}



<div class='toolbar' macro='toolbar -closeTiddler closeOthers +editTiddler 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 [[DD MMM YYYY]]'></span> (created <span macro='view created date [[DD MMM YYYY]]'></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='newReminder' macro='newReminder'></div>
This example is coded to always open the [[Season's Greetings example]] when this is opened. You can put a similar macro call in one of your [[DefaultTiddlers]] to have a customized "front page" for your TiddlyWiki.
<<displayTiddlersWithReminders month:12 day:22 tag:frontPageImages >>
!!!!Use the showReminders macro to show upcoming reminders
showReminders searches through all tidders to find reminders that will be matched in the near future.  Edit this tiddler to see the [[showRemindersSyntax]]

Note that leadtime is 14 days by default, but below, it is specified as 30 days.

<<showReminders leadtime:30>>
!!!!Filtering based on tags
You can limit your search to only tiddlers with a certain tag:

<<showReminders leadtime:30 tag:"examples">>
<<showReminders leadtime:30 tag:"!holidays">>
!!!!Limiting the results
Individual reminders can have a lead time that overrides the leadtime in showReminders.  To turn off this behavior, use the limit argument to showReminders

<<showReminders leadtime:5 limit>>
!!!!Advanced formatting
You can use the format parameter to override the default message that is printed.  For example, the following prints out a table of upcoming reminders.

<<showReminders leadtime:30 format:"|DIFF|TITLE|TIDDLER|">>
!!!!Specific Dates
If you want, and I don't know why you would, you can provide showReminders with a date to start from.  This example shows two weeks worth of reminders starting at December 20th.

<<showReminders month:12 day:20 >>
<<showReminders leadtime:21 year:2006 month:1 day:8 format:"|DIFF|TITLE|TIDDLER|">>

<<showReminders leadtime:21 year:2006 month:2 day:15 format:"|DIFF|TITLE|TIDDLER|">>
* @@{{{leadtime:NUMBER}}}@@ or @@{{{leadtime:NUMBER...NUMBER}}}@@ - Use this to specify a lower and upper bound for reminders that will be shown.  If only one number is specified, then it is treated as the upper bound, and zero is assumed for the lower bound.  These bounds can be negative, in order to show past due reminders.  For example, {{{leadtime:-5...-1}}} will show all reminders that matched in the last five days.  If reminders specify a leadtime, then they may show up, even when they don't fit into showReminder's leadtime bounds.  Use the limit argument to showReminders to override this behavior.  If the leadtime parameter is missing, then {{{leadtime:0...14}}} will be assumed.

* @@{{{nolinks}}}@@ - Deprecated.  Override the format argument to control what the output looks like.

* @@{{{limit}}}@@ - By default, individual reminders can override the leadtime specified by showReminders.  Use this argument to override that behavior.

* @@{{{tag:"STRING"}}}@@ - This filters out tiddlers based on the tag applied to them.  Supply a space-separated list of tags.  If a tag name begins with an {{{!}}}, then only tiddlers which do not have that tag will be considered.  For example {{{tag:"examples holidays"}}} will search for reminders in any tiddlers that are tagged with examples or holidays and {{{tag:"!examples !holidays"}}} will search for reminders in any tiddlers that are not tagged with examples or holidays.

* @@{{{format:"STRING"}}}@@ - Use this argument to override the default string used for display.  You can put standard TiddlyWiki formatting in the format.  The following substitutions will be made in the string before it is displayed.
** DIFF will be replaced with the one of the strings "Today", "Tommorrow", or "N days", where N is the number of days between now and the date of the reminder.  
** TITLE will be replaced with the title of the reminder
** DATE will be replaced with the matched date of the reminder.
** ANNIVERSARY will be replaced with the number of years since between the matched date and firstyear
** TIDDLER will be replaced with a link to the tiddler that contains the reminder. For example: [[Hello there]]
** TIDDLERNAME will be replaced with the text of the tiddler title contains the reminder.  For example: Hello there
The default string is "DIFF: TITLE on DATE ANNIVERSARY -- TIDDLER"